diff --git a/briar-android-tests/build.gradle b/briar-android-tests/build.gradle index a9cc235c3ed6faaecc19492dca26a498e749a90a..51ac97f65a8b8a8a46dd644afbb512386f439b1c 100644 --- a/briar-android-tests/build.gradle +++ b/briar-android-tests/build.gradle @@ -37,7 +37,7 @@ dependencies { compile project(':briar-core') testCompile 'junit:junit:4.12' testCompile 'net.jodah:concurrentunit:0.4.2' - compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:appcompat-v7:23.2.1' testApt 'com.google.dagger:dagger-compiler:2.0.2' provided 'javax.annotation:jsr250-api:1.0' testCompile project(':briar-tests') @@ -45,8 +45,10 @@ dependencies { dependencyVerification { verify = [ - 'com.android.support:appcompat-v7:0a8762214382b7e8d4b989b4ac10b5c846b957d767ccb7bccbc6be5afa885a82', - 'com.android.support:support-v4:5c7dceb6c824089fe80f502e5206264048ef8bffa4e8ddeab180b81723e79b7f', - 'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82', + 'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d', + 'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448', + 'com.android.support:animated-vector-drawable:06d1963b85aa917099d7757e6a7b3e4dc06889413dc747f625ae8683606db3a1', + 'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1', + 'com.android.support:support-annotations:786ab0d060774fb95cfdaf4878771e14b85733b1af9d72a4aae762dc7c1dff9f', ] } diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 355304c37b931b002d711255a05a9e2af662b7f8..6ff778d86d62aa63fe035f7a0f434455f2dea65f 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -11,7 +11,7 @@ repositories { } dependencies { - def supportVersion = '23.1.1' + def supportVersion = '23.2.1' compile project(':briar-api') compile project(':briar-core') compile fileTree(dir: 'libs', include: '*.jar') @@ -53,13 +53,15 @@ dependencyVerification { 'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e', 'de.hdodenhof:circleimageview:c76d936395b50705a3f98c9220c22d2599aeb9e609f559f6048975cfc1f686b8', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', - 'com.android.support:support-v4:5c7dceb6c824089fe80f502e5206264048ef8bffa4e8ddeab180b81723e79b7f', - 'com.android.support:appcompat-v7:0a8762214382b7e8d4b989b4ac10b5c846b957d767ccb7bccbc6be5afa885a82', - 'com.android.support:preference-v7:4b6dabaa4400cbed885c7edc885aa6372468f48d628cc0d4a04b9ccd128ed324', - 'com.android.support:preference-v14:a69906c2b29b315ac3c1fdf01537a7557660a65b8ea1cf891baa8665e1197459', - 'com.android.support:design:41a9cd75ca78f25df5f573db7cedf8bb66beae00c330943923ba9f3e2051736d', - 'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82', - 'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd', + 'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448', + 'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d', + 'com.android.support:preference-v7:775101bd07bd052e455761c5c5d9523d7ad59f2f320e3e8cbde241fd6b1d6025', + 'com.android.support:preference-v14:44881bb46094e86d0bc2426f205419674a5b4eb514b44b5a4659b5de29f71eb7', + 'com.android.support:design:003e0c0bea0a6891f8b2bc43f20ae7af2a49a17363e5bb10df5ee0bae12fa686', + 'com.android.support:support-annotations:786ab0d060774fb95cfdaf4878771e14b85733b1af9d72a4aae762dc7c1dff9f', + 'com.android.support:animated-vector-drawable:06d1963b85aa917099d7757e6a7b3e4dc06889413dc747f625ae8683606db3a1', + 'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1', + 'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe', ] } 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 a42bd2fcef02e1eb2984cb4c8a8368f360d989dc..0ba0d47b9479aa1b8e59432ce4f7993cf199c0f4 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..ee505a59ea2c90d359972a89b8d5bdd6c9fd375d 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; @@ -40,8 +40,10 @@ import org.briarproject.api.keyagreement.KeyAgreementTaskFactory; import org.briarproject.api.keyagreement.Payload; import org.briarproject.api.keyagreement.PayloadEncoder; import org.briarproject.api.keyagreement.PayloadParser; +import org.briarproject.api.lifecycle.IoExecutor; import java.io.IOException; +import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.inject.Inject; @@ -70,6 +72,11 @@ public class ShowQrCodeFragment extends BaseEventFragment protected PayloadEncoder payloadEncoder; @Inject protected PayloadParser payloadParser; + @Inject + protected AndroidExecutor androidExecutor; + @Inject + @IoExecutor + protected Executor ioExecutor; private LinearLayout qrLayout; private CameraView cameraView; @@ -81,7 +88,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 +136,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 +149,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 @@ -174,21 +184,21 @@ public class ShowQrCodeFragment extends BaseEventFragment private void startListening() { task = keyAgreementTaskFactory.getTask(); gotRemotePayload = false; - new Thread(new Runnable() { + ioExecutor.execute(new Runnable() { @Override public void run() { task.listen(); } - }).start(); + }); } private void stopListening() { - new Thread(new Runnable() { + ioExecutor.execute(new Runnable() { @Override public void run() { task.stopListening(); } - }).start(); + }); } private void openCamera() { 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(); } } diff --git a/briar-tests/build.gradle b/briar-tests/build.gradle index 749cb503fb650d0bf6cac560fb9df39bbfece4eb..4cc733f8cf035cf3a75d4fb81bb0e286cc1f563f 100644 --- a/briar-tests/build.gradle +++ b/briar-tests/build.gradle @@ -27,6 +27,7 @@ dependencyVerification { 'org.jmock:jmock:75d4bdaf636879f0215830c5e6ab99407069a625eaffde5d57b32d887b75dc14', 'org.jmock:jmock-junit4:81e3fff46ed56738a6f3f5147525d1d85cda591ce5df007cc193e735cee31113', 'org.jmock:jmock-legacy:19c76059eb254775ba884fc8039bc5c7d1700dc68cc55ad3be5b405a2a8a1819', + 'org.jmock:jmock-testjar:c3642147a5980771dde19d5f1d782d4790a7f9b1521bf9c8cd2b4c23f6384730', 'org.hamcrest:hamcrest-library:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-core:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'cglib:cglib-nodep:3a9ab1f5de15b49dedc7e96d35363c4c43a2b2cd42275f5d4731846042f3fcf2',