diff --git a/briar-android/src/org/briarproject/android/BriarActivity.java b/briar-android/src/org/briarproject/android/BriarActivity.java index 3b4e7c4c6289f910a3b957a1fa35b41a420aecc5..8043b1a14038cb473963917a06bfe2e427b97edc 100644 --- a/briar-android/src/org/briarproject/android/BriarActivity.java +++ b/briar-android/src/org/briarproject/android/BriarActivity.java @@ -41,7 +41,8 @@ public abstract class BriarActivity extends BaseActivity { new BriarServiceConnection(); @Inject - DatabaseConfig databaseConfig; + protected DatabaseConfig databaseConfig; + private boolean bound = false; // Fields that are accessed from background threads must be volatile @@ -94,6 +95,7 @@ public abstract class BriarActivity extends BaseActivity { } protected void signOut(final boolean removeFromRecentApps) { + // Use a new thread to avoid deadlock with executor tasks new Thread() { @Override public void run() { diff --git a/briar-android/src/org/briarproject/android/SettingsActivity.java b/briar-android/src/org/briarproject/android/SettingsActivity.java index 0ec6c916ce5dd461d705c61fa36c14c5593378f8..e270578a74065285f7ee43ee4f7dabc0ca8cffa2 100644 --- a/briar-android/src/org/briarproject/android/SettingsActivity.java +++ b/briar-android/src/org/briarproject/android/SettingsActivity.java @@ -5,6 +5,7 @@ import android.support.v7.app.ActionBar; import android.view.MenuItem; import org.briarproject.R; +import org.briarproject.android.api.AndroidExecutor; import org.briarproject.api.event.EventBus; import org.briarproject.api.settings.SettingsManager; @@ -12,8 +13,12 @@ import javax.inject.Inject; public class SettingsActivity extends BriarActivity { - @Inject protected SettingsManager settingsManager; - @Inject protected EventBus eventBus; + @Inject + protected AndroidExecutor androidExecutor; + @Inject + protected SettingsManager settingsManager; + @Inject + protected EventBus eventBus; @Override public void onCreate(Bundle bundle) { @@ -33,6 +38,10 @@ public class SettingsActivity extends BriarActivity { component.inject(this); } + public AndroidExecutor getAndroidExecutor() { + return androidExecutor; + } + public SettingsManager getSettingsManager() { return settingsManager; } diff --git a/briar-android/src/org/briarproject/android/SplashScreenActivity.java b/briar-android/src/org/briarproject/android/SplashScreenActivity.java index c4cac86f5457f8d9e01c25064b82261f13c9d638..b7d1aad25197ceb4313f5ab7bbc5d36b13ef81bc 100644 --- a/briar-android/src/org/briarproject/android/SplashScreenActivity.java +++ b/briar-android/src/org/briarproject/android/SplashScreenActivity.java @@ -9,6 +9,7 @@ import android.os.StrictMode.VmPolicy; import android.support.v7.preference.PreferenceManager; import org.briarproject.R; +import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.util.AndroidUtils; import org.briarproject.api.db.DatabaseConfig; @@ -29,6 +30,8 @@ public class SplashScreenActivity extends BaseActivity { @Inject protected DatabaseConfig dbConfig; + @Inject + protected AndroidExecutor androidExecutor; public SplashScreenActivity() { Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL); @@ -87,12 +90,11 @@ public class SplashScreenActivity extends BaseActivity { } private void setPreferencesDefaults() { - new Thread() { - @Override + androidExecutor.execute(new Runnable() { public void run() { PreferenceManager.setDefaultValues(SplashScreenActivity.this, R.xml.panic_preferences, false); } - }.start(); + }); } } diff --git a/briar-android/src/org/briarproject/android/api/AndroidExecutor.java b/briar-android/src/org/briarproject/android/api/AndroidExecutor.java index 5ed8ef0f89e9d19ce60bd932e4e91bc3cac91157..047a74c14701e85a174f0acc9e78a16038aab638 100644 --- a/briar-android/src/org/briarproject/android/api/AndroidExecutor.java +++ b/briar-android/src/org/briarproject/android/api/AndroidExecutor.java @@ -10,11 +10,13 @@ import java.util.concurrent.Future; public interface AndroidExecutor { /** - * Runs the given task on the main UI thread and returns a Future for - * getting the result. + * Runs the given task on a background thread with a message queue and + * returns a Future for getting the result. */ <V> Future<V> submit(Callable<V> c); - /** Runs the given task on the main UI thread. */ + /** + * Runs the given task on a background thread with a message queue. + */ void execute(Runnable r); } diff --git a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java index 2e7280ae5f605b97393ab55fdca8597b32ec6c9a..b7750dc17a91f3f96f480126eb31397c602376f8 100644 --- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java @@ -18,7 +18,7 @@ import android.view.ViewGroup; import org.acra.ACRA; import org.briarproject.R; import org.briarproject.android.SettingsActivity; -import org.briarproject.android.util.AndroidUtils; +import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.util.UserFeedback; import org.briarproject.android.widget.PreferenceDividerDecoration; import org.briarproject.api.db.DbException; @@ -55,6 +55,7 @@ public class SettingsFragment extends PreferenceFragmentCompat Logger.getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; + private AndroidExecutor androidExecutor; private ListPreference enableBluetooth; private ListPreference torOverMobile; private CheckBoxPreference notifyPrivateMessages; @@ -74,6 +75,7 @@ public class SettingsFragment extends PreferenceFragmentCompat try { listener = (SettingsActivity) context; + androidExecutor = listener.getAndroidExecutor(); settingsManager = listener.getSettingsManager(); eventBus = listener.getEventBus(); } catch (ClassCastException e) { @@ -218,23 +220,19 @@ public class SettingsFragment extends PreferenceFragmentCompat } private void triggerFeedback() { - new Thread(new Runnable() { - @Override + androidExecutor.execute(new Runnable() { public void run() { - ACRA.getErrorReporter() - .handleException(new UserFeedback(), false); + ACRA.getErrorReporter().handleException(new UserFeedback(), + false); } - }).start(); + }); } @Override public boolean onPreferenceChange(Preference preference, Object o) { if (preference == enableBluetooth) { bluetoothSetting = Boolean.valueOf((String) o); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - AndroidUtils.enableBluetooth(adapter, bluetoothSetting); - } + enableOrDisableBluetooth(bluetoothSetting); storeBluetoothSettings(); } else if (preference == torOverMobile) { torSetting = Boolean.valueOf((String) o); @@ -255,6 +253,18 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; } + private void enableOrDisableBluetooth(final boolean enable) { + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + androidExecutor.execute(new Runnable() { + public void run() { + if (enable) adapter.enable(); + else adapter.disable(); + } + }); + } + } + private void storeTorSettings() { listener.runOnDbThread(new Runnable() { public void run() { diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java index df18a12fce80d8cf024b0599269ed2e6f4470460..4f0937d39c2b78ca77068a7c7282ba9a25ebacd0 100644 --- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java @@ -24,8 +24,8 @@ import com.google.zxing.Result; import org.briarproject.R; import org.briarproject.android.AndroidComponent; +import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.fragment.BaseEventFragment; -import org.briarproject.android.util.AndroidUtils; import org.briarproject.android.util.CameraView; import org.briarproject.android.util.QrCodeDecoder; import org.briarproject.android.util.QrCodeUtils; @@ -70,6 +70,8 @@ public class ShowQrCodeFragment extends BaseEventFragment protected PayloadEncoder payloadEncoder; @Inject protected PayloadParser payloadParser; + @Inject + protected AndroidExecutor androidExecutor; private LinearLayout qrLayout; private CameraView cameraView; @@ -81,7 +83,6 @@ public class ShowQrCodeFragment extends BaseEventFragment private boolean gotRemotePayload; private volatile KeyAgreementTask task; - private volatile BluetoothAdapter adapter; private volatile boolean waitingForBluetooth; public static ShowQrCodeFragment newInstance() { @@ -130,8 +131,6 @@ public class ShowQrCodeFragment extends BaseEventFragment Display display = getActivity().getWindowManager().getDefaultDisplay(); boolean portrait = display.getWidth() < display.getHeight(); qrLayout.setOrientation(portrait ? VERTICAL : HORIZONTAL); - - adapter = BluetoothAdapter.getDefaultAdapter(); } @Override @@ -145,11 +144,17 @@ public class ShowQrCodeFragment extends BaseEventFragment getActivity().registerReceiver(receiver, filter); // Enable BT adapter if it is not already on. + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null && !adapter.isEnabled()) { waitingForBluetooth = true; - AndroidUtils.enableBluetooth(adapter, true); - } else + androidExecutor.execute(new Runnable() { + public void run() { + adapter.enable(); + } + }); + } else { startListening(); + } } @Override diff --git a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java index 29f271f0e4113f82d950aea02b0e63aed166b247..87ba49e7023eada4ff2051e63baed477500d75a4 100644 --- a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java +++ b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java @@ -9,9 +9,9 @@ import android.support.v7.preference.PreferenceManager; import org.briarproject.android.AndroidComponent; import org.briarproject.android.BriarActivity; +import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.util.AndroidUtils; import org.briarproject.api.db.DatabaseConfig; -import org.briarproject.util.FileUtils; import org.iilab.IilabEngineeringRSA2048Pin; import java.util.logging.Logger; @@ -23,6 +23,7 @@ import info.guardianproject.panic.Panic; import info.guardianproject.panic.PanicResponder; import info.guardianproject.trustedintents.TrustedIntents; +import static android.content.Intent.ACTION_DELETE; import static org.briarproject.android.panic.PanicPreferencesFragment.KEY_LOCK; import static org.briarproject.android.panic.PanicPreferencesFragment.KEY_PURGE; import static org.briarproject.android.panic.PanicPreferencesFragment.KEY_UNINSTALL; @@ -31,7 +32,11 @@ public class PanicResponderActivity extends BriarActivity { private static final Logger LOG = Logger.getLogger(PanicResponderActivity.class.getName()); - @Inject protected DatabaseConfig databaseConfig; + + @Inject + protected DatabaseConfig databaseConfig; + @Inject + protected AndroidExecutor androidExecutor; @Override public void onCreate(Bundle savedInstanceState) { @@ -62,16 +67,14 @@ public class PanicResponderActivity extends BriarActivity { deleteAllData(); LOG.info("Uninstalling..."); - Intent uninstall = new Intent(Intent.ACTION_DELETE); + Intent uninstall = new Intent(ACTION_DELETE); uninstall.setData( Uri.parse("package:" + getPackageName())); startActivity(uninstall); - } - else if (sharedPref.getBoolean(KEY_PURGE, false)) { + } else if (sharedPref.getBoolean(KEY_PURGE, false)) { LOG.info("Purging all data..."); deleteAllData(); - } - else if (sharedPref.getBoolean(KEY_LOCK, true)) { + } else if (sharedPref.getBoolean(KEY_LOCK, true)) { LOG.info("Signing out..."); signOut(true); } @@ -107,8 +110,7 @@ public class PanicResponderActivity extends BriarActivity { } private void deleteAllData() { - new Thread() { - @Override + androidExecutor.execute(new Runnable() { public void run() { clearSharedPrefs(); // TODO somehow delete/shred the database more thoroughly @@ -120,7 +122,6 @@ public class PanicResponderActivity extends BriarActivity { LOG.info("Signing out..."); signOut(true); } - }.start(); + }); } - } \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/util/AndroidUtils.java b/briar-android/src/org/briarproject/android/util/AndroidUtils.java index 841dba8f6520fc24d7bec04fe3976121ee2c9de1..f3b33196cb7a9c5bbabf456472dcc37be9c34184 100644 --- a/briar-android/src/org/briarproject/android/util/AndroidUtils.java +++ b/briar-android/src/org/briarproject/android/util/AndroidUtils.java @@ -48,17 +48,6 @@ public class AndroidUtils { til.setError(null); } - public static void enableBluetooth(final BluetoothAdapter adapter, - final boolean enable) { - new Thread() { - @Override - public void run() { - if (enable) adapter.enable(); - else adapter.disable(); - } - }.start(); - } - public static String getBluetoothAddress(Context ctx, BluetoothAdapter adapter) { // Return the adapter's address if it's valid and not fake diff --git a/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java b/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java index 0b9674c25274f1f7341f00b8d683374522ebf949..07b6e69e5e89e656e0b61290c551dd1d3b09b3b8 100644 --- a/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java +++ b/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java @@ -1,46 +1,59 @@ package org.briarproject.system; -import android.app.Application; -import android.content.Context; import android.os.Handler; import android.os.Looper; -import android.os.Message; import org.briarproject.android.api.AndroidExecutor; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.inject.Inject; class AndroidExecutorImpl implements AndroidExecutor { - private final Handler handler; + private final Runnable loop; + private final AtomicBoolean started = new AtomicBoolean(false); + private final CountDownLatch startLatch = new CountDownLatch(1); + + private volatile Handler handler = null; + + @Inject + AndroidExecutorImpl() { + loop = new Runnable() { + public void run() { + Looper.prepare(); + handler = new Handler(); + startLatch.countDown(); + Looper.loop(); + } + }; + } - AndroidExecutorImpl(Application app) { - Context ctx = app.getApplicationContext(); - handler = new FutureTaskHandler(ctx.getMainLooper()); + private void startIfNecessary() { + if (started.getAndSet(true)) return; + Thread t = new Thread(loop, "AndroidExecutor"); + t.setDaemon(true); + t.start(); + try { + startLatch.await(); + } catch (InterruptedException e) { + throw new RejectedExecutionException(e); + } } public <V> Future<V> submit(Callable<V> c) { - Future<V> f = new FutureTask<V>(c); - handler.sendMessage(Message.obtain(handler, 0, f)); + FutureTask<V> f = new FutureTask<>(c); + execute(f); return f; } public void execute(Runnable r) { + startIfNecessary(); handler.post(r); } - - private static class FutureTaskHandler extends Handler { - - private FutureTaskHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message m) { - ((FutureTask<?>) m.obj).run(); - } - } } - diff --git a/briar-android/src/org/briarproject/system/AndroidSystemModule.java b/briar-android/src/org/briarproject/system/AndroidSystemModule.java index 74e39e06af0bd375d43098fbedc3c5bf295711b5..f6fb19f16d9ea7b73dec8bee6fad856c96c22100 100644 --- a/briar-android/src/org/briarproject/system/AndroidSystemModule.java +++ b/briar-android/src/org/briarproject/system/AndroidSystemModule.java @@ -6,6 +6,8 @@ import org.briarproject.android.api.AndroidExecutor; import org.briarproject.api.system.LocationUtils; import org.briarproject.api.system.SeedProvider; +import javax.inject.Singleton; + import dagger.Module; import dagger.Provides; @@ -23,7 +25,8 @@ public class AndroidSystemModule { } @Provides - public AndroidExecutor providePlatformExecutor(Application app) { - return new AndroidExecutorImpl(app); + @Singleton + public AndroidExecutor provideAndroidExecutor() { + return new AndroidExecutorImpl(); } }