Remember that app entered doze mode and inform user when returning

This commit is contained in:
Torsten Grote
2017-11-20 17:44:46 -02:00
parent 4267800db2
commit ec2f372933
6 changed files with 170 additions and 30 deletions

View File

@@ -3,12 +3,16 @@ package org.briarproject.briar.android;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
@@ -28,12 +32,15 @@ import javax.inject.Inject;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
public class BriarService extends Service {
@@ -45,6 +52,9 @@ public class BriarService extends Service {
private final AtomicBoolean created = new AtomicBoolean(false);
private final Binder binder = new BriarBinder();
@Nullable
private BriarBroadcastReceiver receiver = null;
private boolean hasDozed = false;
@Inject
protected DatabaseConfig databaseConfig;
@@ -84,7 +94,7 @@ public class BriarService extends Service {
Intent i = new Intent(this, NavDrawerActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
if (Build.VERSION.SDK_INT >= 21) {
if (SDK_INT >= 21) {
b.setCategory(CATEGORY_SERVICE);
b.setVisibility(VISIBILITY_SECRET);
}
@@ -109,6 +119,7 @@ public class BriarService extends Service {
}
}
}.start();
registerBroadcastReceiver();
}
private void showStartupFailureNotification(StartResult result) {
@@ -153,6 +164,7 @@ public class BriarService extends Service {
public void onDestroy() {
super.onDestroy();
LOG.info("Destroyed");
if (receiver != null) unregisterReceiver(receiver);
stopForeground(true);
// Stop the services in a background thread
new Thread() {
@@ -170,6 +182,21 @@ public class BriarService extends Service {
// FIXME: Work out what to do about it
}
private void registerBroadcastReceiver() {
if (SDK_INT < 23) return;
IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
if (receiver == null) receiver = new BriarBroadcastReceiver();
registerReceiver(receiver, filter);
}
public boolean hasDozed() {
return hasDozed;
}
public void resetDozeFlag() {
hasDozed = false;
}
/**
* Waits for all services to start before returning.
*/
@@ -225,4 +252,15 @@ public class BriarService extends Service {
return binder;
}
}
public class BriarBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (SDK_INT < 23 || !needsDozeWhitelisting(getApplicationContext()))
return;
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (pm.isDeviceIdleMode()) hasDozed = true;
}
}
}

View File

@@ -4,15 +4,18 @@ 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 +28,9 @@ 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 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,6 +64,13 @@ public abstract class BriarActivity extends BaseActivity {
if (!briarController.hasEncryptionKey() && !isFinishing()) {
Intent i = new Intent(this, PasswordActivity.class);
startActivityForResult(i, REQUEST_PASSWORD);
} else {
briarController.hasDozed(new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(Boolean result) {
if (result) showDozeDialog();
}
});
}
}
@@ -97,6 +109,29 @@ public abstract class BriarActivity extends BaseActivity {
return toolbar;
}
private void showDozeDialog() {
AlertDialog.Builder b =
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
b.setMessage(getString(R.string.warning_dozed,
getString(R.string.app_name)));
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.doNotNotifyWhenDozed();
});
b.show();
}
protected void signOut(boolean removeFromRecentApps) {
if (briarController.hasEncryptionKey()) {
// Don't use UiResultHandler because we want the result even if

View File

@@ -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<Boolean> handler);
void doNotNotifyWhenDozed();
void signOut(ResultHandler<Void> eventHandler);
}

View File

@@ -6,30 +6,48 @@ 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 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());
private static final String HAS_DOZED_ASK_AGAIN = "hasDozedAskAgain";
private final BriarServiceConnection serviceConnection;
private final DatabaseConfig databaseConfig;
@DatabaseExecutor
private final Executor databaseExecutor;
private final SettingsManager settingsManager;
private final Activity activity;
private boolean bound = false;
@Inject
BriarControllerImpl(BriarServiceConnection serviceConnection,
DatabaseConfig databaseConfig, Activity activity) {
DatabaseConfig databaseConfig,
@DatabaseExecutor Executor databaseExecutor,
SettingsManager settingsManager, Activity activity) {
this.serviceConnection = serviceConnection;
this.databaseConfig = databaseConfig;
this.databaseExecutor = databaseExecutor;
this.settingsManager = settingsManager;
this.activity = activity;
}
@@ -65,6 +83,50 @@ public class BriarControllerImpl implements BriarController {
return databaseConfig.getEncryptionKey() != null;
}
@Override
public void hasDozed(ResultHandler<Boolean> handler) {
// check this first, to hit the DbThread only when really necessary
if (!needsDozeWhitelisting(activity)) {
handler.onResult(false);
return;
}
databaseExecutor.execute(() -> {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
boolean ask = settings.getBoolean(HAS_DOZED_ASK_AGAIN, true);
if (!ask) {
handler.onResult(false);
return;
}
IBinder binder = serviceConnection.waitForBinder();
BriarService service =
((BriarService.BriarBinder) binder).getService();
handler.onResult(service.hasDozed());
service.resetDozeFlag();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for service");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
});
}
@Override
public void doNotNotifyWhenDozed() {
databaseExecutor.execute(() -> {
try {
Settings settings = new Settings();
settings.putBoolean(HAS_DOZED_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<Void> eventHandler) {
new Thread() {

View File

@@ -158,42 +158,37 @@ public class NavDrawerControllerImpl extends DbControllerImpl
}
@Override
public void askDozeWhitelisting(final Context ctx,
final ResultHandler<Boolean> handler) {
public void askDozeWhitelisting(Context ctx,
ResultHandler<Boolean> handler) {
// check this first, to hit the DbThread only when really necessary
if (!needsDozeWhitelisting(ctx)) {
handler.onResult(false);
return;
}
runOnDbThread(new Runnable() {
@Override
public void run() {
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);
}
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 void doNotAskAgainForDozeWhiteListing() {
runOnDbThread(new Runnable() {
@Override
public void run() {
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);
}
runOnDbThread(() -> {
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);
}
});
}