Skip to content

Commit

Permalink
own orbothelper & fix receiver visibility (#961)
Browse files Browse the repository at this point in the history
m2049r authored Oct 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 41e84f2 commit c49351a
Showing 2 changed files with 719 additions and 11 deletions.
22 changes: 11 additions & 11 deletions app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@
import java.util.concurrent.TimeUnit;

import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import info.guardianproject.netcipher.proxy.MyOrbotHelper;
import info.guardianproject.netcipher.proxy.SignatureUtils;
import info.guardianproject.netcipher.proxy.StatusCallback;
import lombok.RequiredArgsConstructor;
@@ -75,7 +75,7 @@ public interface OnStatusChangedListener {
}

final private Context context;
final private OrbotHelper orbot;
final private MyOrbotHelper orbot;

@SuppressLint("StaticFieldLeak")
private static NetCipherHelper Instance;
@@ -85,7 +85,7 @@ public static void createInstance(Context context) {
synchronized (NetCipherHelper.class) {
if (Instance == null) {
final Context applicationContext = context.getApplicationContext();
Instance = new NetCipherHelper(applicationContext, OrbotHelper.get(context).statusTimeout(5000));
Instance = new NetCipherHelper(applicationContext, MyOrbotHelper.get(context).statusTimeout(5000));
}
}
}
@@ -99,9 +99,9 @@ public static NetCipherHelper getInstance() {
private OkHttpClient client;

private void createTorClient(Intent statusIntent) {
String orbotStatus = statusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS);
String orbotStatus = statusIntent.getStringExtra(MyOrbotHelper.EXTRA_STATUS);
if (orbotStatus == null) throw new IllegalStateException("status is null");
if (!orbotStatus.equals(OrbotHelper.STATUS_ON))
if (!orbotStatus.equals(MyOrbotHelper.STATUS_ON))
throw new IllegalStateException("Orbot is not ON");
try {
final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder()
@@ -146,7 +146,7 @@ public static void register(OnStatusChangedListener listener) {
.addStatusCallback(me);

// deal with org.torproject.android.intent.action.STATUS = STARTS_DISABLED
ContextCompat.registerReceiver(me.context, orbotStatusReceiver, new IntentFilter(OrbotHelper.ACTION_STATUS), ContextCompat.RECEIVER_NOT_EXPORTED);
ContextCompat.registerReceiver(me.context, orbotStatusReceiver, new IntentFilter(MyOrbotHelper.ACTION_STATUS), ContextCompat.RECEIVER_EXPORTED);

me.startTor();
}
@@ -272,7 +272,7 @@ private boolean isOrbotInstalled() {
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");

return null != SignatureUtils.validateBroadcastIntent(context,
OrbotHelper.getOrbotStartIntent(context),
MyOrbotHelper.getOrbotStartIntent(context),
hashes, false);
}

@@ -382,16 +382,16 @@ private void setTorPref(Status status) {
private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
if (OrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(OrbotHelper.EXTRA_STATUS))) {
Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS));
if (MyOrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
if (MyOrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS))) {
getInstance().onNotEnabled();
}
}
}
};

public void installOrbot(Activity host) {
host.startActivity(OrbotHelper.getOrbotInstallIntent(context));
host.startActivity(MyOrbotHelper.getOrbotInstallIntent(context));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,708 @@
/*
* Copyright 2014-2016 Hans-Christoph Steiner
* Copyright 2012-2016 Nathan Freitas
* Portions Copyright (c) 2016 CommonsWare, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package info.guardianproject.netcipher.proxy;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

/**
* Utility class to simplify setting up a proxy connection
* to Orbot.
* <p>
* If you are using classes in the info.guardianproject.netcipher.client
* package, call OrbotHelper.get(this).init(); from onCreate()
* of a custom Application subclass, or from some other guaranteed
* entry point to your app. At that point, the
* info.guardianproject.netcipher.client classes will be ready
* for use.
*/
public class MyOrbotHelper implements ProxyHelper {

private final static int REQUEST_CODE_STATUS = 100;

public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
public final static String ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
+ ORBOT_PACKAGE_NAME;
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
+ ORBOT_PACKAGE_NAME;

public final static String DEFAULT_PROXY_HOST = "localhost";//"127.0.0.1";
public final static int DEFAULT_PROXY_HTTP_PORT = 8118;
public final static int DEFAULT_PROXY_SOCKS_PORT = 9050;

/**
* A request to Orbot to transparently start Tor services
*/
public final static String ACTION_START = "org.torproject.android.intent.action.START";

/**
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
* included as an {@link #EXTRA_STATUS} {@code String}. Your app should
* always receive {@code ACTION_STATUS Intent}s since any other app could
* start Orbot. Also, user-triggered starts and stops will also cause
* {@code ACTION_STATUS Intent}s to be broadcast.
*/
public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";

/**
* {@code String} that contains a status constant: {@link #STATUS_ON},
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
* {@link #STATUS_STOPPING}
*/
public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
/**
* A {@link String} {@code packageName} for Orbot to direct its status reply
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
*/
public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";

public final static String EXTRA_PROXY_PORT_HTTP = "org.torproject.android.intent.extra.HTTP_PROXY_PORT";
public final static String EXTRA_PROXY_PORT_SOCKS = "org.torproject.android.intent.extra.SOCKS_PROXY_PORT";


/**
* All tor-related services and daemons are stopped
*/
public final static String STATUS_OFF = "OFF";
/**
* All tor-related services and daemons have completed starting
*/
public final static String STATUS_ON = "ON";
public final static String STATUS_STARTING = "STARTING";
public final static String STATUS_STOPPING = "STOPPING";
/**
* The user has disabled the ability for background starts triggered by
* apps. Fallback to the old Intent that brings up Orbot.
*/
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";

public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
public final static String ACTION_REQUEST_HS = "org.torproject.android.REQUEST_HS_PORT";
public final static int START_TOR_RESULT = 0x9234;
public final static int HS_REQUEST_CODE = 9999;


/*
private OrbotHelper() {
// only static utility methods, do not instantiate
}
*/

/**
* Test whether a {@link URL} is a Tor Hidden Service host name, also known
* as an ".onion address".
*
* @return whether the host name is a Tor .onion address
*/
public static boolean isOnionAddress(URL url) {
return url.getHost().endsWith(".onion");
}

/**
* Test whether a URL {@link String} is a Tor Hidden Service host name, also known
* as an ".onion address".
*
* @return whether the host name is a Tor .onion address
*/
public static boolean isOnionAddress(String urlString) {
try {
return isOnionAddress(new URL(urlString));
} catch (MalformedURLException e) {
return false;
}
}

/**
* Test whether a {@link Uri} is a Tor Hidden Service host name, also known
* as an ".onion address".
*
* @return whether the host name is a Tor .onion address
*/
public static boolean isOnionAddress(Uri uri) {
return uri.getHost().endsWith(".onion");
}

/**
* Check if the tor process is running. This method is very
* brittle, and is therefore deprecated in favor of using the
* {@link #ACTION_STATUS} {@code Intent} along with the
* {@link #requestStartTor(Context)} method.
*/
@Deprecated
public static boolean isOrbotRunning(Context context) {
int procId = TorServiceUtils.findProcessId(context);

return (procId != -1);
}

public static boolean isOrbotInstalled(Context context) {
return isAppInstalled(context, ORBOT_PACKAGE_NAME);
}

private static boolean isAppInstalled(Context context, String uri) {
try {
PackageManager pm = context.getPackageManager();
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}

public static void requestHiddenServiceOnPort(Activity activity, int port) {
Intent intent = new Intent(ACTION_REQUEST_HS);
intent.setPackage(ORBOT_PACKAGE_NAME);
intent.putExtra("hs_port", port);

activity.startActivityForResult(intent, HS_REQUEST_CODE);
}

/**
* First, checks whether Orbot is installed. If Orbot is installed, then a
* broadcast {@link Intent} is sent to request Orbot to start
* transparently in the background. When Orbot receives this {@code
* Intent}, it will immediately reply to the app that called this method
* with an {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
* {@code packageName} of the provided {@link Context} (i.e. {@link
* Context#getPackageName()}.
* <p>
* That reply {@link #ACTION_STATUS} {@code Intent} could say that the user
* has disabled background starts with the status
* {@link #STATUS_STARTS_DISABLED}. That means that Orbot ignored this
* request. To directly prompt the user to start Tor, use
* {@link #requestShowOrbotStart(Activity)}, which will bring up
* Orbot itself for the user to manually start Tor. Orbot always broadcasts
* it's status, so your app will receive those no matter how Tor gets
* started.
*
* @param context the app {@link Context} will receive the reply
* @return whether the start request was sent to Orbot
* @see #requestShowOrbotStart(Activity activity)
*/
public static boolean requestStartTor(Context context) {
if (MyOrbotHelper.isOrbotInstalled(context)) {
Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
Intent intent = getOrbotStartIntent(context);
context.sendBroadcast(intent);
return true;
}
return false;
}

/**
* Gets an {@link Intent} for starting Orbot. Orbot will reply with the
* current status to the {@code packageName} of the app in the provided
* {@link Context} (i.e. {@link Context#getPackageName()}.
*/
public static Intent getOrbotStartIntent(Context context) {
Intent intent = new Intent(ACTION_START);
intent.setPackage(ORBOT_PACKAGE_NAME);
intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
return intent;
}

/**
* Gets a barebones {@link Intent} for starting Orbot. This is deprecated
* in favor of {@link #getOrbotStartIntent(Context)}.
*/
@Deprecated
public static Intent getOrbotStartIntent() {
Intent intent = new Intent(ACTION_START);
intent.setPackage(ORBOT_PACKAGE_NAME);
return intent;
}

/**
* First, checks whether Orbot is installed, then checks whether Orbot is
* running. If Orbot is installed and not running, then an {@link Intent} is
* sent to request the user to start Orbot, which will show the main Orbot screen.
* The result will be returned in
* {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
* with a {@code requestCode} of {@code START_TOR_RESULT}
* <p>
* Orbot will also always broadcast the status of starting Tor via the
* {@link #ACTION_STATUS} Intent, no matter how it is started.
*
* @param activity the {@code Activity} that gets the result of the
* {@link #START_TOR_RESULT} request
* @return whether the start request was sent to Orbot
* @see #requestStartTor(Context context)
*/
public static boolean requestShowOrbotStart(Activity activity) {
if (MyOrbotHelper.isOrbotInstalled(activity)) {
if (!MyOrbotHelper.isOrbotRunning(activity)) {
Intent intent = getShowOrbotStartIntent();
activity.startActivityForResult(intent, START_TOR_RESULT);
return true;
}
}
return false;
}

public static Intent getShowOrbotStartIntent() {
Intent intent = new Intent(ACTION_START_TOR);
intent.setPackage(ORBOT_PACKAGE_NAME);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}

public static Intent getOrbotInstallIntent(Context context) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(ORBOT_MARKET_URI));

PackageManager pm = context.getPackageManager();
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);

String foundPackageName = null;
for (ResolveInfo r : resInfos) {
Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
foundPackageName = r.activityInfo.packageName;
break;
}
}

if (foundPackageName == null) {
intent.setData(Uri.parse(ORBOT_FDROID_URI));
} else {
intent.setPackage(foundPackageName);
}
return intent;
}

@Override
public boolean isInstalled(Context context) {
return isOrbotInstalled(context);
}

@Override
public void requestStatus(Context context) {
isOrbotRunning(context);
}

@Override
public boolean requestStart(Context context) {
return requestStartTor(context);
}

@Override
public Intent getInstallIntent(Context context) {
return getOrbotInstallIntent(context);
}

@Override
public Intent getStartIntent(Context context) {
return getOrbotStartIntent();
}

@Override
public String getName() {
return "Orbot";
}

/* MLM additions */

private final Context context;
private final Handler handler;
private boolean isInstalled = false;
@Nullable
private Intent lastStatusIntent = null;
private Set<StatusCallback> statusCallbacks =
newSetFromMap(new WeakHashMap<StatusCallback, Boolean>());
private Set<InstallCallback> installCallbacks =
newSetFromMap(new WeakHashMap<InstallCallback, Boolean>());
private long statusTimeoutMs = 30000L;
private long installTimeoutMs = 60000L;
private boolean validateOrbot = true;

abstract public static class SimpleStatusCallback
implements StatusCallback {
@Override
public void onEnabled(Intent statusIntent) {
// no-op; extend and override if needed
}

@Override
public void onStarting() {
// no-op; extend and override if needed
}

@Override
public void onStopping() {
// no-op; extend and override if needed
}

@Override
public void onDisabled() {
// no-op; extend and override if needed
}

@Override
public void onNotYetInstalled() {
// no-op; extend and override if needed
}
}

/**
* Callback interface used for reporting the results of an
* attempt to install Orbot
*/
public interface InstallCallback {
void onInstalled();

void onInstallTimeout();
}

private static volatile MyOrbotHelper instance;

/**
* Retrieves the singleton, initializing if if needed
*
* @param context any Context will do, as we will hold onto
* the Application
* @return the singleton
*/
synchronized public static MyOrbotHelper get(Context context) {
if (instance == null) {
instance = new MyOrbotHelper(context);
}

return (instance);
}

/**
* Standard constructor
*
* @param context any Context will do; OrbotInitializer will hold
* onto the Application context
*/
private MyOrbotHelper(Context context) {
this.context = context.getApplicationContext();
this.handler = new Handler(Looper.getMainLooper());
}

/**
* Adds a StatusCallback to be called when we find out that
* Orbot is ready. If Orbot is ready for use, your callback
* will be called with onEnabled() immediately, before this
* method returns.
*
* @param cb a callback
* @return the singleton, for chaining
*/
public MyOrbotHelper addStatusCallback(StatusCallback cb) {
statusCallbacks.add(cb);

if (lastStatusIntent != null) {
String status =
lastStatusIntent.getStringExtra(MyOrbotHelper.EXTRA_STATUS);

if (status.equals(MyOrbotHelper.STATUS_ON)) {
cb.onEnabled(lastStatusIntent);
}
}

return (this);
}

/**
* Removes an existing registered StatusCallback.
*
* @param cb the callback to remove
* @return the singleton, for chaining
*/
public MyOrbotHelper removeStatusCallback(StatusCallback cb) {
statusCallbacks.remove(cb);

return (this);
}


/**
* Adds an InstallCallback to be called when we find out that
* Orbot is installed
*
* @param cb a callback
* @return the singleton, for chaining
*/
public MyOrbotHelper addInstallCallback(InstallCallback cb) {
installCallbacks.add(cb);

return (this);
}

/**
* Removes an existing registered InstallCallback.
*
* @param cb the callback to remove
* @return the singleton, for chaining
*/
public MyOrbotHelper removeInstallCallback(InstallCallback cb) {
installCallbacks.remove(cb);

return (this);
}

/**
* Sets how long of a delay, in milliseconds, after trying
* to get a status from Orbot before we give up.
* Defaults to 30000ms = 30 seconds = 0.000347222 days
*
* @param timeoutMs delay period in milliseconds
* @return the singleton, for chaining
*/
public MyOrbotHelper statusTimeout(long timeoutMs) {
statusTimeoutMs = timeoutMs;

return (this);
}

/**
* Sets how long of a delay, in milliseconds, after trying
* to install Orbot do we assume that it's not happening.
* Defaults to 60000ms = 60 seconds = 1 minute = 1.90259e-6 years
*
* @param timeoutMs delay period in milliseconds
* @return the singleton, for chaining
*/
public MyOrbotHelper installTimeout(long timeoutMs) {
installTimeoutMs = timeoutMs;

return (this);
}

/**
* By default, NetCipher ensures that the Orbot on the
* device is one of the official builds. Call this method
* to skip that validation. Mostly, this is for developers
* who have their own custom Orbot builds (e.g., for
* dedicated hardware).
*
* @return the singleton, for chaining
*/
public MyOrbotHelper skipOrbotValidation() {
validateOrbot = false;

return (this);
}

/**
* @return true if Orbot is installed (the last time we checked),
* false otherwise
*/
public boolean isInstalled() {
return (isInstalled);
}

/**
* Initializes the connection to Orbot, revalidating that it is installed
* and requesting fresh status broadcasts. This is best run in your app's
* {@link android.app.Application} subclass, in its
* {@link android.app.Application#onCreate()} method.
*
* @return true if initialization is proceeding, false if Orbot is not installed,
* or version of Orbot with a unofficial signing key is present.
*/
public boolean init() {
Intent orbot = MyOrbotHelper.getOrbotStartIntent(context);

if (validateOrbot) {
ArrayList<String> hashes = new ArrayList<String>();

// Tor Project signing key
hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B");
// f-droid.org signing key
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");

orbot =
SignatureUtils.validateBroadcastIntent(context, orbot,
hashes, false);
}

if (orbot != null) {
isInstalled = true;
handler.postDelayed(onStatusTimeout, statusTimeoutMs);
ContextCompat.registerReceiver(context, orbotStatusReceiver, new IntentFilter(MyOrbotHelper.ACTION_STATUS), ContextCompat.RECEIVER_EXPORTED);
context.sendBroadcast(orbot);
} else {
isInstalled = false;

for (StatusCallback cb : statusCallbacks) {
cb.onNotYetInstalled();
}
}

return (isInstalled);
}

/**
* Given that init() returned false, calling installOrbot()
* will trigger an attempt to install Orbot from an available
* distribution channel (e.g., the Play Store). Only call this
* if the user is expecting it, such as in response to tapping
* a dialog button or an action bar item.
* <p>
* Note that installation may take a long time, even if
* the user is proceeding with the installation, due to network
* speeds, waiting for user input, and so on. Either specify
* a long timeout, or consider the timeout to be merely advisory
* and use some other user input to cause you to try
* init() again after, presumably, Orbot has been installed
* and configured by the user.
* <p>
* If the user does install Orbot, we will attempt init()
* again automatically. Hence, you will probably need user input
* to tell you when the user has gotten Orbot up and going.
*
* @param host the Activity that is triggering this work
*/
public void installOrbot(Activity host) {
handler.postDelayed(onInstallTimeout, installTimeoutMs);

IntentFilter filter =
new IntentFilter(Intent.ACTION_PACKAGE_ADDED);

filter.addDataScheme("package");

context.registerReceiver(orbotInstallReceiver, filter);
host.startActivity(MyOrbotHelper.getOrbotInstallIntent(context));
}

private BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(),
MyOrbotHelper.ACTION_STATUS)) {
String status = intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS);

if (status.equals(MyOrbotHelper.STATUS_ON)) {
lastStatusIntent = intent;
handler.removeCallbacks(onStatusTimeout);

for (StatusCallback cb : statusCallbacks) {
cb.onEnabled(intent);
}
} else if (status.equals(MyOrbotHelper.STATUS_OFF)) {
for (StatusCallback cb : statusCallbacks) {
cb.onDisabled();
}
} else if (status.equals(MyOrbotHelper.STATUS_STARTING)) {
for (StatusCallback cb : statusCallbacks) {
cb.onStarting();
}
} else if (status.equals(MyOrbotHelper.STATUS_STOPPING)) {
for (StatusCallback cb : statusCallbacks) {
cb.onStopping();
}
}
}
}
};

private Runnable onStatusTimeout = new Runnable() {
@Override
public void run() {
context.unregisterReceiver(orbotStatusReceiver);

for (StatusCallback cb : statusCallbacks) {
cb.onStatusTimeout();
}
}
};

private BroadcastReceiver orbotInstallReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(),
Intent.ACTION_PACKAGE_ADDED)) {
String pkgName = intent.getData().getEncodedSchemeSpecificPart();

if (MyOrbotHelper.ORBOT_PACKAGE_NAME.equals(pkgName)) {
isInstalled = true;
handler.removeCallbacks(onInstallTimeout);
context.unregisterReceiver(orbotInstallReceiver);

for (InstallCallback cb : installCallbacks) {
cb.onInstalled();
}

init();
}
}
}
};

private Runnable onInstallTimeout = new Runnable() {
@Override
public void run() {
context.unregisterReceiver(orbotInstallReceiver);

for (InstallCallback cb : installCallbacks) {
cb.onInstallTimeout();
}
}
};

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
if (map.isEmpty()) {
return new SetFromMap<E>(map);
}
throw new IllegalArgumentException("map not empty");
}
}

0 comments on commit c49351a

Please sign in to comment.