diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 652ef527d..763c50332 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -26,6 +26,7 @@ import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.android.reporting.BriarReportSender; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogPostFactory; @@ -140,6 +141,8 @@ public interface AndroidComponent TestDataCreator testDataCreator(); + DozeWatchdog dozeWatchdog(); + @IoExecutor Executor ioExecutor(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 725a69047..c0e068976 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -15,6 +15,7 @@ import org.briarproject.bramble.api.reporting.DevConfig; import org.briarproject.bramble.api.ui.UiCallback; import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.ReferenceManager; import org.briarproject.briar.api.android.ScreenFilterMonitor; @@ -40,6 +41,8 @@ public class AppModule { AndroidNotificationManager androidNotificationManager; @Inject NetworkUsageLogger networkUsageLogger; + @Inject + DozeWatchdog dozeWatchdog; } private final Application application; @@ -183,4 +186,12 @@ public class AppModule { lifecycleManager.registerService(networkUsageLogger); return networkUsageLogger; } + + @Provides + @Singleton + DozeWatchdog provideDozeWatchdog(LifecycleManager lifecycleManager) { + DozeWatchdogImpl dozeWatchdog = new DozeWatchdogImpl(application); + lifecycleManager.registerService(dozeWatchdog); + return dozeWatchdog; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java new file mode 100644 index 000000000..e6cf8e227 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java @@ -0,0 +1,57 @@ +package org.briarproject.briar.android; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PowerManager; + +import org.briarproject.bramble.api.lifecycle.Service; +import org.briarproject.bramble.api.lifecycle.ServiceException; +import org.briarproject.briar.api.android.DozeWatchdog; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static android.content.Context.POWER_SERVICE; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; + +class DozeWatchdogImpl implements DozeWatchdog, Service { + + private final Context appContext; + private final AtomicBoolean dozed = new AtomicBoolean(false); + private final BroadcastReceiver receiver = new DozeBroadcastReceiver(); + + DozeWatchdogImpl(Context appContext) { + this.appContext = appContext; + } + + @Override + public boolean getAndResetDozeFlag() { + return dozed.getAndSet(false); + } + + @Override + public void startService() throws ServiceException { + if (SDK_INT < 23) return; + IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); + appContext.registerReceiver(receiver, filter); + } + + @Override + public void stopService() throws ServiceException { + if (SDK_INT < 23) return; + appContext.unregisterReceiver(receiver); + } + + private class DozeBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (SDK_INT < 23) return; + PowerManager pm = + (PowerManager) appContext.getSystemService(POWER_SERVICE); + if (pm.isDeviceIdleMode()) dozed.set(true); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java index e98a63cfa..d00730f24 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java @@ -2,17 +2,19 @@ package org.briarproject.briar.android.activity; import android.annotation.SuppressLint; import android.content.Intent; -import android.os.Build; import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.transition.Slide; import android.transition.Transition; import android.view.Gravity; import android.view.Window; +import android.widget.CheckBox; import org.briarproject.briar.R; import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.DbController; +import org.briarproject.briar.android.controller.handler.UiResultHandler; import org.briarproject.briar.android.login.PasswordActivity; import org.briarproject.briar.android.panic.ExitActivity; @@ -25,7 +27,10 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; +import static android.os.Build.VERSION.SDK_INT; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; +import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent; @SuppressLint("Registered") public abstract class BriarActivity extends BaseActivity { @@ -59,11 +64,21 @@ public abstract class BriarActivity extends BaseActivity { if (!briarController.hasEncryptionKey() && !isFinishing()) { Intent i = new Intent(this, PasswordActivity.class); startActivityForResult(i, REQUEST_PASSWORD); + } else if (SDK_INT >= 23) { + briarController.hasDozed(new UiResultHandler(this) { + @Override + public void onResultUi(Boolean result) { + if (result) { + showDozeDialog(getString(R.string.warning_dozed, + getString(R.string.app_name))); + } + } + }); } } public void setSceneTransitionAnimation() { - if (Build.VERSION.SDK_INT < 21) return; + if (SDK_INT < 21) return; Transition slide = new Slide(Gravity.RIGHT); slide.excludeTarget(android.R.id.statusBarBackground, true); slide.excludeTarget(android.R.id.navigationBarBackground, true); @@ -97,6 +112,28 @@ public abstract class BriarActivity extends BaseActivity { return toolbar; } + protected void showDozeDialog(String message) { + AlertDialog.Builder b = + new AlertDialog.Builder(this, R.style.BriarDialogTheme); + b.setMessage(message); + b.setView(R.layout.checkbox); + b.setPositiveButton(R.string.fix, + (dialog, which) -> { + Intent i = getDozeWhitelistingIntent(BriarActivity.this); + startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + dialog.dismiss(); + }); + b.setNegativeButton(R.string.cancel, + (dialog, which) -> dialog.dismiss()); + b.setOnDismissListener(dialog -> { + CheckBox checkBox = (CheckBox) ((AlertDialog) dialog) + .findViewById(R.id.checkbox); + if (checkBox.isChecked()) + briarController.doNotAskAgainForDozeWhiteListing(); + }); + b.show(); + } + protected void signOut(boolean removeFromRecentApps) { if (briarController.hasEncryptionKey()) { // Don't use UiResultHandler because we want the result even if @@ -123,7 +160,7 @@ public abstract class BriarActivity extends BaseActivity { } private void finishAndExit() { - if (Build.VERSION.SDK_INT >= 21) finishAndRemoveTask(); + if (SDK_INT >= 21) finishAndRemoveTask(); else supportFinishAfterTransition(); LOG.info("Exiting"); System.exit(0); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java index 38c1ca93b..350faeaba 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java @@ -8,5 +8,13 @@ public interface BriarController extends ActivityLifecycleController { boolean hasEncryptionKey(); + /** + * Returns true via the handler when the app has dozed + * without being white-listed. + */ + void hasDozed(ResultHandler handler); + + void doNotAskAgainForDozeWhiteListing(); + void signOut(ResultHandler eventHandler); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java index ff010ecac..a8d90dd0c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java @@ -6,30 +6,52 @@ import android.os.IBinder; import android.support.annotation.CallSuper; import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.android.BriarService; import org.briarproject.briar.android.BriarService.BriarServiceConnection; import org.briarproject.briar.android.controller.handler.ResultHandler; +import org.briarproject.briar.api.android.DozeWatchdog; +import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.inject.Inject; +import static java.util.logging.Level.WARNING; +import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; + public class BriarControllerImpl implements BriarController { private static final Logger LOG = Logger.getLogger(BriarControllerImpl.class.getName()); + public static final String DOZE_ASK_AGAIN = "dozeAskAgain"; + private final BriarServiceConnection serviceConnection; private final DatabaseConfig databaseConfig; + @DatabaseExecutor + private final Executor databaseExecutor; + private final SettingsManager settingsManager; + private final DozeWatchdog dozeWatchdog; private final Activity activity; private boolean bound = false; @Inject BriarControllerImpl(BriarServiceConnection serviceConnection, - DatabaseConfig databaseConfig, Activity activity) { + DatabaseConfig databaseConfig, + @DatabaseExecutor Executor databaseExecutor, + SettingsManager settingsManager, DozeWatchdog dozeWatchdog, + Activity activity) { this.serviceConnection = serviceConnection; this.databaseConfig = databaseConfig; + this.databaseExecutor = databaseExecutor; + this.settingsManager = settingsManager; + this.dozeWatchdog = dozeWatchdog; this.activity = activity; } @@ -65,6 +87,40 @@ public class BriarControllerImpl implements BriarController { return databaseConfig.getEncryptionKey() != null; } + @Override + public void hasDozed(ResultHandler handler) { + if (!dozeWatchdog.getAndResetDozeFlag() + || !needsDozeWhitelisting(activity)) { + handler.onResult(false); + return; + } + databaseExecutor.execute(() -> { + try { + Settings settings = + settingsManager.getSettings(SETTINGS_NAMESPACE); + boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true); + handler.onResult(ask); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + }); + } + + @Override + public void doNotAskAgainForDozeWhiteListing() { + databaseExecutor.execute(() -> { + try { + Settings settings = new Settings(); + settings.putBoolean(DOZE_ASK_AGAIN, false); + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + }); + } + @Override public void signOut(ResultHandler eventHandler) { new Thread() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java index 68dc24345..005c600d8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java @@ -26,6 +26,7 @@ public class DozeFragment extends SetupFragment { private Button dozeButton; private ProgressBar progressBar; + private boolean secondAttempt = false; public static DozeFragment newInstance() { return new DozeFragment(); @@ -64,10 +65,11 @@ public class DozeFragment extends SetupFragment { public void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); if (request == REQUEST_DOZE_WHITELISTING) { - if (!setupController.needsDozeWhitelisting()) { + if (!setupController.needsDozeWhitelisting() || secondAttempt) { dozeButton.setEnabled(false); onClick(dozeButton); } else { + secondAttempt = true; showOnboardingDialog(getContext(), getHelpText()); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index e0ad7d8c9..c5e4a5b12 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.navdrawer; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; @@ -12,7 +11,6 @@ import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.MenuItem; @@ -53,12 +51,10 @@ import static android.support.v4.view.GravityCompat.START; import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; import static android.view.View.GONE; import static android.view.View.VISIBLE; -import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE; import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry; -import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent; -import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; public class NavDrawerActivity extends BriarActivity implements BaseFragmentListener, TransportStateListener, @@ -151,7 +147,23 @@ public class NavDrawerActivity extends BriarActivity implements if (expiry != NO) showExpiryWarning(expiry); } }); - if (needsDozeWhitelisting(this)) requestDozeWhitelisting(); + } + + @Override + protected void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_PASSWORD && result == RESULT_OK) { + controller.shouldAskForDozeWhitelisting(this, + new UiResultHandler(this) { + @Override + public void onResultUi(Boolean ask) { + if (ask) { + showDozeDialog( + getString(R.string.setup_doze_intro)); + } + } + }); + } } private void exitIfStartupFailed(Intent intent) { @@ -313,20 +325,6 @@ public class NavDrawerActivity extends BriarActivity implements expiryWarning.setVisibility(VISIBLE); } - @TargetApi(23) - private void requestDozeWhitelisting() { - new AlertDialog.Builder(this, R.style.BriarDialogTheme) - .setMessage(R.string.setup_doze_intro) - .setPositiveButton(R.string.ok, - (dialog, which) -> { - Intent i = getDozeWhitelistingIntent( - NavDrawerActivity.this); - startActivityForResult(i, - REQUEST_DOZE_WHITELISTING); - }) - .show(); - } - private void initializeTransports(LayoutInflater inflater) { transports = new ArrayList<>(3); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerController.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerController.java index 4b7c5a52a..d2d883e90 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerController.java @@ -1,5 +1,7 @@ package org.briarproject.briar.android.navdrawer; +import android.content.Context; + import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.briar.android.controller.ActivityLifecycleController; @@ -16,4 +18,7 @@ public interface NavDrawerController extends ActivityLifecycleController { void expiryWarningDismissed(); + void shouldAskForDozeWhitelisting(Context ctx, + ResultHandler handler); + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java index 00a62705c..ba5a22f2c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.navdrawer; import android.app.Activity; +import android.content.Context; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; @@ -28,10 +29,12 @@ import javax.inject.Inject; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE; +import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.SHOW; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -154,6 +157,28 @@ public class NavDrawerControllerImpl extends DbControllerImpl }); } + @Override + public void shouldAskForDozeWhitelisting(Context ctx, + ResultHandler handler) { + // check this first, to hit the DbThread only when really necessary + if (!needsDozeWhitelisting(ctx)) { + handler.onResult(false); + return; + } + runOnDbThread(() -> { + try { + Settings settings = + settingsManager.getSettings(SETTINGS_NAMESPACE); + boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true); + handler.onResult(ask); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onResult(true); + } + }); + } + @Override public boolean isTransportRunning(TransportId transportId) { Plugin plugin = pluginManager.getPlugin(transportId); diff --git a/briar-android/src/main/java/org/briarproject/briar/api/android/DozeWatchdog.java b/briar-android/src/main/java/org/briarproject/briar/api/android/DozeWatchdog.java new file mode 100644 index 000000000..9b37aebff --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/api/android/DozeWatchdog.java @@ -0,0 +1,6 @@ +package org.briarproject.briar.api.android; + +public interface DozeWatchdog { + + boolean getAndResetDozeFlag(); +} diff --git a/briar-android/src/main/res/layout/checkbox.xml b/briar-android/src/main/res/layout/checkbox.xml new file mode 100644 index 000000000..eb3dfe6a4 --- /dev/null +++ b/briar-android/src/main/res/layout/checkbox.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 98c44d56c..fa95e33a8 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -19,6 +19,7 @@ Passwords do not match Create Account More Information + Don\'t ask again Enter your password: @@ -94,6 +95,8 @@ The entered text is too long Show Help Dialog + %s was unable to run in the background + Fix It seems that you are new here and have no contacts yet.\n\nTap the + icon at the top and follow the instructions to add some friends to your list.\n\nPlease remember: You can only add new contacts face-to-face to prevent anyone from impersonating you or reading your messages in the future. @@ -390,7 +393,7 @@ Screen overlay detected Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nTry turning off the following apps when using Briar:\n\n%1$s - + Camera permission To scan the QR code, Briar needs access to the camera. You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.