diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java index 7aa0371fe2ad439b5a05902648db838065da4b5e..05d9a5012f9b296821615a7c16454330cf9e4fe5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java @@ -26,6 +26,7 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.logout.HideUiActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.android.LockManager; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -54,6 +55,7 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.FAIL import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID; +import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK; public class BriarService extends Service { @@ -77,6 +79,8 @@ public class BriarService extends Service { AndroidNotificationManager notificationManager; @Inject AccountManager accountManager; + @Inject + LockManager lockManager; // Fields that are accessed from background threads must be volatile @Inject @@ -189,6 +193,9 @@ public class BriarService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { + if (ACTION_LOCK.equals(intent.getAction())) { + lockManager.setLocked(true); + } return START_NOT_STICKY; // Don't restart automatically if killed } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java index 562ea958849a8bc69a3d605d8d8ddf95536581fa..5b9ae87f84fc8821e8a6f88f45f4a58c6aaec2ab 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java @@ -1,9 +1,12 @@ package org.briarproject.briar.android.account; +import android.app.AlarmManager; import android.app.Application; +import android.app.PendingIntent; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MutableLiveData; import android.content.Context; +import android.content.Intent; import android.support.annotation.UiThread; import org.briarproject.bramble.api.db.DatabaseExecutor; @@ -16,6 +19,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarService; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.LockManager; @@ -25,9 +30,15 @@ import java.util.logging.Logger; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static android.app.AlarmManager.ELAPSED_REALTIME; +import static android.app.PendingIntent.getService; +import static android.content.Context.ALARM_SERVICE; +import static android.os.SystemClock.elapsedRealtime; +import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK; +import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK_TIMEOUT; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; @@ -44,9 +55,14 @@ public class LockManagerImpl implements LockManager, Service, EventListener { private final AndroidNotificationManager notificationManager; @DatabaseExecutor private final Executor dbExecutor; + private final AlarmManager alarmManager; + private final PendingIntent lockIntent; + private final int timeoutNever, timeoutDefault; private volatile boolean locked = false; private volatile boolean lockableSetting = false; + private volatile int timeoutMinutes; + private int activitiesRunning = 0; private final MutableLiveData<Boolean> lockable = new MutableLiveData<>(); @Inject @@ -57,6 +73,16 @@ public class LockManagerImpl implements LockManager, Service, EventListener { this.settingsManager = settingsManager; this.notificationManager = notificationManager; this.dbExecutor = dbExecutor; + this.alarmManager = + (AlarmManager) appContext.getSystemService(ALARM_SERVICE); + Intent i = + new Intent(ACTION_LOCK, null, appContext, BriarService.class); + this.lockIntent = getService(appContext, 0, i, 0); + this.timeoutNever = Integer.valueOf( + appContext.getString(R.string.pref_lock_timeout_value_never)); + this.timeoutDefault = Integer.valueOf( + appContext.getString(R.string.pref_lock_timeout_value_default)); + this.timeoutMinutes = timeoutNever; // setting this in the constructor makes #getValue() @NonNull this.lockable.setValue(false); @@ -72,6 +98,26 @@ public class LockManagerImpl implements LockManager, Service, EventListener { public void stopService() { } + @UiThread + @Override + public void onActivityStart() { + activitiesRunning++; + alarmManager.cancel(lockIntent); + } + + @UiThread + @Override + public void onActivityStop() { + activitiesRunning--; + if (activitiesRunning == 0 && !locked && + timeoutMinutes != timeoutNever && lockable.getValue()) { + alarmManager.cancel(lockIntent); + long triggerAt = + elapsedRealtime() + MINUTES.toMillis(timeoutMinutes); + alarmManager.set(ELAPSED_REALTIME, triggerAt, lockIntent); + } + } + @Override public LiveData<Boolean> isLockable() { return lockable; @@ -118,9 +164,13 @@ public class LockManagerImpl implements LockManager, Service, EventListener { try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE); + // is the app lockable? lockableSetting = settings.getBoolean(PREF_SCREEN_LOCK, false); boolean newValue = hasScreenLock(appContext) && lockableSetting; lockable.postValue(newValue); + // what is the timeout in minutes? + timeoutMinutes = settings.getInt(PREF_SCREEN_LOCK_TIMEOUT, + timeoutDefault); } catch (DbException e) { logException(LOG, WARNING, e); lockableSetting = false; 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 77c6b41327dc553dee54005f89914e69944a1e22..292325ae885c9606bc408998b1f3d7f4d1361642 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 @@ -53,6 +53,12 @@ public abstract class BriarActivity extends BaseActivity { @Inject protected LockManager lockManager; + @Override + public void onStart() { + super.onStart(); + lockManager.onActivityStart(); + } + @Override protected void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); @@ -97,6 +103,12 @@ public abstract class BriarActivity extends BaseActivity { } } + @Override + protected void onStop() { + super.onStop(); + lockManager.onActivityStop(); + } + public void setSceneTransitionAnimation() { if (SDK_INT < 21) return; // workaround for #1007 diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index 1f52854f3dcca85417655260a7df0c87b8817bc1..5a7efc830a03417eca6583c90c3a4bb168ebf13d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -15,6 +15,7 @@ import android.support.v4.content.ContextCompat; import android.support.v4.text.TextUtilsCompat; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.PreferenceGroup; import android.view.LayoutInflater; @@ -105,13 +106,15 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF @MethodsNotNullByDefault @ParametersNotNullByDefault public class SettingsFragment extends PreferenceFragmentCompat - implements EventListener, Preference.OnPreferenceChangeListener { + implements EventListener, OnPreferenceChangeListener { public static final String SETTINGS_NAMESPACE = "android-ui"; public static final String BT_NAMESPACE = BluetoothConstants.ID.getString(); public static final String TOR_NAMESPACE = TorConstants.ID.getString(); public static final String LANGUAGE = "pref_key_language"; public static final String PREF_SCREEN_LOCK = "pref_key_lock"; + public static final String PREF_SCREEN_LOCK_TIMEOUT = + "pref_key_lock_timeout"; public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; public static final String TOR_LOCATION = "pref_key_tor_location"; @@ -124,6 +127,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private ListPreference torNetwork; private SwitchPreference torBlocked; private SwitchPreference screenLock; + private ListPreference screenLockTimeout; private SwitchPreference notifyPrivateMessages; private SwitchPreference notifyGroupMessages; private SwitchPreference notifyForumPosts; @@ -166,6 +170,8 @@ public class SettingsFragment extends PreferenceFragmentCompat SwitchPreference notifySignIn = (SwitchPreference) findPreference(NOTIFY_SIGN_IN); screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK); + screenLockTimeout = + (ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT); notifyPrivateMessages = (SwitchPreference) findPreference( "pref_key_notify_private_messages"); notifyGroupMessages = (SwitchPreference) findPreference( @@ -203,6 +209,7 @@ public class SettingsFragment extends PreferenceFragmentCompat torNetwork.setOnPreferenceChangeListener(this); torBlocked.setOnPreferenceChangeListener(this); screenLock.setOnPreferenceChangeListener(this); + screenLockTimeout.setOnPreferenceChangeListener(this); if (SDK_INT >= 21) { notifyLockscreen.setVisible(true); notifyLockscreen.setOnPreferenceChangeListener(this); @@ -224,6 +231,7 @@ public class SettingsFragment extends PreferenceFragmentCompat theme.setVisible(FEATURE_FLAG_DARK_THEME); notifySignIn.setVisible(FEATURE_FLAG_SIGN_IN_REMINDER); screenLock.setVisible(FEATURE_FLAG_PIN_LOCK); + screenLockTimeout.setVisible(FEATURE_FLAG_PIN_LOCK); findPreference("pref_key_explode").setVisible(false); findPreference("pref_key_test_data").setVisible(false); @@ -419,6 +427,7 @@ public class SettingsFragment extends PreferenceFragmentCompat // - pref_key_notify_sign_in // preferences partly needed here, because they have their own logic // - pref_key_lock (screenLock -> displayScreenLockSetting()) + // - pref_key_lock_timeout (screenLockTimeout) enableBluetooth.setEnabled(enabled); torNetwork.setEnabled(enabled); torBlocked.setEnabled(enabled); @@ -435,6 +444,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private void displayScreenLockSetting() { if (SDK_INT < 21) { screenLock.setVisible(false); + screenLockTimeout.setVisible(false); } else { if (getActivity() != null && hasScreenLock(getActivity())) { screenLock.setEnabled(true); @@ -446,6 +456,11 @@ public class SettingsFragment extends PreferenceFragmentCompat screenLock.setChecked(false); screenLock.setSummary(R.string.pref_lock_disabled_summary); } + // timeout depends on screenLock and gets disabled automatically + int timeout = settings.getInt(PREF_SCREEN_LOCK_TIMEOUT, + Integer.valueOf(getString( + R.string.pref_lock_timeout_value_default))); + screenLockTimeout.setValue(String.valueOf(timeout)); } } @@ -508,6 +523,11 @@ public class SettingsFragment extends PreferenceFragmentCompat Settings s = new Settings(); s.putBoolean(PREF_SCREEN_LOCK, (Boolean) newValue); storeSettings(s); + } else if (preference == screenLockTimeout) { + Settings s = new Settings(); + s.putInt(PREF_SCREEN_LOCK_TIMEOUT, + Integer.valueOf((String) newValue)); + storeSettings(s); } else if (preference == notifyPrivateMessages) { Settings s = new Settings(); s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) newValue); diff --git a/briar-android/src/main/java/org/briarproject/briar/api/android/LockManager.java b/briar-android/src/main/java/org/briarproject/briar/api/android/LockManager.java index 968d4e5228339442b8b6a2d4d5a87ba2e706100b..d4255abb733cbb2a3edf0366ea7afe7e5b2bad2b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/api/android/LockManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/api/android/LockManager.java @@ -1,10 +1,27 @@ package org.briarproject.briar.api.android; +import android.app.Activity; import android.arch.lifecycle.LiveData; import android.support.annotation.UiThread; public interface LockManager { + String ACTION_LOCK = "lock"; + + /** + * Stops the inactivity timer when the user interacts with the app. + * Should typically be called by {@link Activity#onStart()} + */ + @UiThread + void onActivityStart(); + + /** + * Starts the inactivity timer which will lock the app. + * Should typically be called by {@link Activity#onStop()} + */ + @UiThread + void onActivityStop(); + /** * Returns an observable LiveData to indicate whether the app can be locked. */ diff --git a/briar-android/src/main/res/values/arrays.xml b/briar-android/src/main/res/values/arrays.xml index d4001a3c9ae19ca54dbb014535dc2273c07508ea..5c0a98fab10487b2b10e8af5102ad6b32cff7095 100644 --- a/briar-android/src/main/res/values/arrays.xml +++ b/briar-android/src/main/res/values/arrays.xml @@ -67,4 +67,23 @@ <item>@string/pref_theme_auto_value</item> <item>@string/pref_theme_system_value</item> </string-array> + + <string-array name="pref_key_lock_timeout_entries"> + <item>@string/pref_lock_timeout_never</item> + <item>@string/pref_lock_timeout_1</item> + <item>@string/pref_lock_timeout_5</item> + <item>@string/pref_lock_timeout_15</item> + <item>@string/pref_lock_timeout_30</item> + <item>@string/pref_lock_timeout_60</item> + </string-array> + <string name="pref_lock_timeout_value_default">5</string> + <string name="pref_lock_timeout_value_never">-1</string> + <string-array name="pref_key_lock_timeout_values"> + <item>@string/pref_lock_timeout_value_never</item> + <item>1</item> + <item>5</item> + <item>15</item> + <item>30</item> + <item>60</item> + </string-array> </resources> diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 45c9ce6e72693cc99e4233c84bc349003095b01d..fecb77032f6e1e223b38ed6df89bef74dc79359d 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -360,6 +360,15 @@ <string name="pref_lock_title">Screen Lock</string> <string name="pref_lock_summary">Use the device\'s screen lock to protect Briar while signed in</string> <string name="pref_lock_disabled_summary">Set up a screen lock for your device to protect Briar while signed in</string> + <string name="pref_lock_timeout_title">Screen Lock Inactivity Timeout</string> + <string name="pref_lock_timeout_summary">When not using Briar, automatically lock it %s</string> + <string name="pref_lock_timeout_never">never</string> + <string name="pref_lock_timeout_1">after 1 Minute</string> + <string name="pref_lock_timeout_5">after 5 Minutes</string> + <string name="pref_lock_timeout_15">after 15 Minutes</string> + <string name="pref_lock_timeout_30">after 30 Minutes</string> + <string name="pref_lock_timeout_60">after 1 Hour</string> + <string name="change_password">Change password</string> <string name="current_password">Current password</string> <string name="choose_new_password">New password</string> diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 1238f65e9cddb9eaee763d6691c637523df0e3fa..5faaebbd28c76a9c03666f9db0d89910687b525b 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -60,8 +60,17 @@ android:key="pref_key_lock" android:persistent="false" android:summary="@string/pref_lock_summary" - android:title="@string/pref_lock_title" - android:enabled="false"/> + android:title="@string/pref_lock_title"/> + + <ListPreference + android:defaultValue="@string/pref_lock_timeout_value_default" + android:dependency="pref_key_lock" + android:entries="@array/pref_key_lock_timeout_entries" + android:entryValues="@array/pref_key_lock_timeout_values" + android:key="pref_key_lock_timeout" + android:persistent="false" + android:summary="@string/pref_lock_timeout_summary" + android:title="@string/pref_lock_timeout_title"/> <Preference android:key="pref_key_change_password"