mirror of https://github.com/m2049r/xmrwallet.git
own orbothelper & fix receiver visibility (#961)
This commit is contained in:
parent
41e84f2e29
commit
c49351a8a9
|
@ -41,7 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder;
|
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.SignatureUtils;
|
||||||
import info.guardianproject.netcipher.proxy.StatusCallback;
|
import info.guardianproject.netcipher.proxy.StatusCallback;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
@ -75,7 +75,7 @@ public class NetCipherHelper implements StatusCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
final private Context context;
|
final private Context context;
|
||||||
final private OrbotHelper orbot;
|
final private MyOrbotHelper orbot;
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private static NetCipherHelper Instance;
|
private static NetCipherHelper Instance;
|
||||||
|
@ -85,7 +85,7 @@ public class NetCipherHelper implements StatusCallback {
|
||||||
synchronized (NetCipherHelper.class) {
|
synchronized (NetCipherHelper.class) {
|
||||||
if (Instance == null) {
|
if (Instance == null) {
|
||||||
final Context applicationContext = context.getApplicationContext();
|
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 class NetCipherHelper implements StatusCallback {
|
||||||
private OkHttpClient client;
|
private OkHttpClient client;
|
||||||
|
|
||||||
private void createTorClient(Intent statusIntent) {
|
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 == 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");
|
throw new IllegalStateException("Orbot is not ON");
|
||||||
try {
|
try {
|
||||||
final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder()
|
final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder()
|
||||||
|
@ -146,7 +146,7 @@ public class NetCipherHelper implements StatusCallback {
|
||||||
.addStatusCallback(me);
|
.addStatusCallback(me);
|
||||||
|
|
||||||
// deal with org.torproject.android.intent.action.STATUS = STARTS_DISABLED
|
// 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();
|
me.startTor();
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ public class NetCipherHelper implements StatusCallback {
|
||||||
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");
|
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,
|
return null != SignatureUtils.validateBroadcastIntent(context,
|
||||||
OrbotHelper.getOrbotStartIntent(context),
|
MyOrbotHelper.getOrbotStartIntent(context),
|
||||||
hashes, false);
|
hashes, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,9 +382,9 @@ public class NetCipherHelper implements StatusCallback {
|
||||||
private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
|
private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
|
Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS));
|
||||||
if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
|
if (MyOrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
|
||||||
if (OrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(OrbotHelper.EXTRA_STATUS))) {
|
if (MyOrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS))) {
|
||||||
getInstance().onNotEnabled();
|
getInstance().onNotEnabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,6 +392,6 @@ public class NetCipherHelper implements StatusCallback {
|
||||||
};
|
};
|
||||||
|
|
||||||
public void installOrbot(Activity host) {
|
public void installOrbot(Activity host) {
|
||||||
host.startActivity(OrbotHelper.getOrbotInstallIntent(context));
|
host.startActivity(MyOrbotHelper.getOrbotInstallIntent(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue