diff --git a/briar-android/build.gradle b/briar-android/build.gradle index edfe7ba99..b5e256f8e 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -40,9 +40,9 @@ dependencies { testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput') - testImplementation 'org.robolectric:robolectric:3.5.1' - testImplementation 'org.robolectric:shadows-support-v4:3.0' - testImplementation 'org.mockito:mockito-core:2.8.9' + testImplementation 'org.robolectric:robolectric:3.8' + testImplementation 'org.robolectric:shadows-support-v4:3.3.2' + testImplementation 'org.mockito:mockito-core:2.13.0' testImplementation 'junit:junit:4.12' testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2" @@ -165,8 +165,8 @@ dependencyVerification { 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'nekohtml:nekohtml:1.9.6.2:nekohtml-1.9.6.2.jar:fdff6cfa9ed9cc911c842a5d2395f209ec621ef1239d46810e9e495809d3ae09', 'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438', - 'net.bytebuddy:byte-buddy-agent:1.6.14:byte-buddy-agent-1.6.14.jar:c141a2d6809c3eeff4a43d25992826abccebdd4b793af3e7a5f346e88ae73a33', - 'net.bytebuddy:byte-buddy:1.6.14:byte-buddy-1.6.14.jar:917758b3c651e278a15a029ba1d42dbf802d8b0e1fe2aa4b81c5750c64f461c1', + 'net.bytebuddy:byte-buddy-agent:1.7.9:byte-buddy-agent-1.7.9.jar:ac1a993befb528c3271a83a9ad9c42d363d399e9deb26e0470e3c4962066c550', + 'net.bytebuddy:byte-buddy:1.7.9:byte-buddy-1.7.9.jar:2ea2ada12b790d16ac7f6e6c065cb55cbcdb6ba519355f5958851159cad3b16a', 'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', @@ -215,8 +215,8 @@ dependencyVerification { 'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4', - 'org.mockito:mockito-core:2.8.9:mockito-core-2.8.9.jar:a2bb9b8b40d81bb02ccb84259524c0f4911f73c6577bfc7ddd940b8fc729b6e6', - 'org.objenesis:objenesis:2.5:objenesis-2.5.jar:293328e1b0d31ed30bb89fca542b6c52fac00989bb0e62eb9d98d630c4dd6b7c', + 'org.mockito:mockito-core:2.13.0:mockito-core-2.13.0.jar:92a746b37cf8c5730a5e7b35fd7d8cd72700089435ff92ee03ed8384d4eb3377', + 'org.objenesis:objenesis:2.6:objenesis-2.6.jar:5e168368fbc250af3c79aa5fef0c3467a2d64e5a7bd74005f25d8399aeb0708d', 'org.ow2.asm:asm-analysis:5.1:asm-analysis-5.1.jar:a34658f5c5de4b573eef21131cc32cc25f7b66407944f312b28ec2e56abb1fa9', 'org.ow2.asm:asm-commons:5.0.1:asm-commons-5.0.1.jar:fb1cb7fa27d892712ced8fbf8d027eb5052ecd3999dba1ba47824357accb40e7', 'org.ow2.asm:asm-commons:5.1:asm-commons-5.1.jar:97b3786e1f55e74bddf8ad102bf50e33bbcbc1f6b7fd7b36f0bbbb25cd4981be', @@ -225,15 +225,15 @@ dependencyVerification { 'org.ow2.asm:asm-util:5.1:asm-util-5.1.jar:ee032c39ae5e3cd099148fbba9a2124f9ed613e5cb93e03ee0fa8808ce364040', 'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220', 'org.ow2.asm:asm:5.1:asm-5.1.jar:d2da399a9967c69f0a21739256fa79d284222c223082cacadc17372244764b54', - 'org.robolectric:annotations:3.5.1:annotations-3.5.1.jar:14db0f7d2299c5400ff7764bb37b4fa80306582d8965fdf6999091723e2384ce', - 'org.robolectric:junit:3.5.1:junit-3.5.1.jar:b2e81b7d5a22755f2ea76aa9bbbd4359d61c4cb9577193ccfbb8f97378ed293b', - 'org.robolectric:resources:3.5.1:resources-3.5.1.jar:22a5564590c8bfd8df7efb2b0c7d9942b46a0beb59ba38899d59c1270f293b1c', - 'org.robolectric:robolectric:3.5.1:robolectric-3.5.1.jar:603cf898f93b854f18021fab452aca3fe482368eeb2e720988ae82212ebcf4b6', - 'org.robolectric:sandbox:3.5.1:sandbox-3.5.1.jar:beff8c3c1e840e0f7f78aadef170f347bae349f098babfc176765f499a4bcbb5', - 'org.robolectric:shadowapi:3.5.1:shadowapi-3.5.1.jar:6d574f9ae0922791eb8f06979f0010997d4b862c7aec96d485ae797ddfc13278', - 'org.robolectric:shadows-framework:3.5.1:shadows-framework-3.5.1.jar:597b54cc1a494799d783921c6ac04352f33e94fca8e00f299d4ca192db79e3fc', - 'org.robolectric:shadows-support-v4:3.0:shadows-support-v4-3.0.jar:66bcc3257b037d72998e860d67b1bc58215b7eeac8ad860fcc3e613332d88619', - 'org.robolectric:utils:3.5.1:utils-3.5.1.jar:d7d77326867e6d903156ebb18c244819b26aebe3aa82a1c57081081a0b6c4f63', + 'org.robolectric:annotations:3.8:annotations-3.8.jar:8eab08facfe2a8cd22f6a09f4378f012a5985c0d4f4ad4e203e00f75b5568458', + 'org.robolectric:junit:3.8:junit-3.8.jar:042575dbc95dc82ec046d13438ccda578917ce786d2f464cba0eb18da1f412cb', + 'org.robolectric:resources:3.8:resources-3.8.jar:6d11e6d39df8eda837c52319cf8d6bef424df45be6f29b3a731707832eb6ffc1', + 'org.robolectric:robolectric:3.8:robolectric-3.8.jar:34908fc858e6e4113be2cf97fe55d3ffa4462bf7183d466542582ca1898ce60a', + 'org.robolectric:sandbox:3.8:sandbox-3.8.jar:cc257dc75c5af9e62a43572ac89dff4d8520427307cf973e6b663c88fb000720', + 'org.robolectric:shadowapi:3.8:shadowapi-3.8.jar:28bc24cb5c4b4030852cebebfd5f12536d03088892cb3c8b1d1379297087aca8', + 'org.robolectric:shadows-framework:3.8:shadows-framework-3.8.jar:83548db7249edf1af87e1a1f4d8f4eec3e85d6220161da601e6f6398476911b2', + 'org.robolectric:shadows-support-v4:3.3.2:shadows-support-v4-3.3.2.jar:6f689264738266e70fe08db7c04b7b5a75155994f4e3f7f311960d90486bf005', + 'org.robolectric:utils:3.8:utils-3.8.jar:e945d04d40e37554e02d4be1bc3abf9bede45375c843aa36d10ccb6b63edbf34', 'tools.fastlane:screengrab:1.1.0:screengrab-1.1.0.aar:03ce3868ee8a0082d14e7a1de0999f91531c0cc794392688beb08ee9bc4495fd', 'uk.co.samuelwall:material-tap-target-prompt:2.8.0:material-tap-target-prompt-2.8.0.aar:ac70770c05bbc4675a1d5712c0e53d46ee4fa961b74947589fce50d8003065ec', 'xmlpull:xmlpull:1.1.3.1:xmlpull-1.1.3.1.jar:34e08ee62116071cbb69c0ed70d15a7a5b208d62798c59f2120bb8929324cb63', diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 33899a202..82d4b83cf 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -402,5 +402,11 @@ android:theme="@android:style/Theme.NoDisplay"> + + 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 75b25b099..e531f295c 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 @@ -28,6 +28,7 @@ import org.briarproject.briar.android.login.SignInReminderReceiver; 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.LockManager; import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogPostFactory; @@ -146,6 +147,8 @@ public interface AndroidComponent AccountManager accountManager(); + LockManager lockManager(); + void inject(SignInReminderReceiver briarService); void inject(BriarService briarService); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java index d3ee12a94..d88368f7c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java @@ -2,6 +2,7 @@ package org.briarproject.briar.android; import android.annotation.TargetApi; import android.app.Application; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; @@ -12,7 +13,6 @@ import android.support.annotation.StringRes; import android.support.annotation.UiThread; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; -import android.support.v4.content.ContextCompat; import org.briarproject.bramble.api.Multiset; import org.briarproject.bramble.api.contact.ContactId; @@ -65,7 +65,6 @@ import javax.inject.Inject; import static android.app.Notification.DEFAULT_LIGHTS; import static android.app.Notification.DEFAULT_SOUND; import static android.app.Notification.DEFAULT_VIBRATE; -import static android.app.Notification.VISIBILITY_SECRET; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.content.Context.NOTIFICATION_SERVICE; @@ -73,8 +72,12 @@ 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.support.v4.app.NotificationCompat.CATEGORY_MESSAGE; +import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE; import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL; import static android.support.v4.app.NotificationCompat.PRIORITY_LOW; +import static android.support.v4.app.NotificationCompat.PRIORITY_MIN; +import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; +import static android.support.v4.content.ContextCompat.getColor; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; @@ -173,8 +176,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, nc.setLockscreenVisibility(VISIBILITY_SECRET); nc.enableVibration(true); nc.enableLights(true); - nc.setLightColor( - ContextCompat.getColor(appContext, R.color.briar_green_light)); + nc.setLightColor(getColor(appContext, R.color.briar_green_light)); notificationManager.createNotificationChannel(nc); } @@ -271,6 +273,47 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, }); } + @UiThread + @Override + public Notification getForegroundNotification() { + return getForegroundNotification(false); + } + + @UiThread + private Notification getForegroundNotification(boolean locked) { + int title = locked ? R.string.lock_is_locked : + R.string.ongoing_notification_title; + int text = locked ? R.string.lock_tap_to_unlock : + R.string.ongoing_notification_text; + int icon = locked ? R.drawable.startup_lock : + R.drawable.notification_ongoing; + // Ongoing foreground notification that shows BriarService is running + NotificationCompat.Builder b = + new NotificationCompat.Builder(appContext, ONGOING_CHANNEL_ID); + b.setSmallIcon(icon); + b.setColor(getColor(appContext, R.color.briar_primary)); + b.setContentTitle(appContext.getText(title)); + b.setContentText(appContext.getText(text)); + b.setWhen(0); // Don't show the time + b.setOngoing(true); + Intent i = new Intent(appContext, NavDrawerActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); + b.setContentIntent(PendingIntent.getActivity(appContext, 0, i, 0)); + if (SDK_INT >= 21) { + b.setCategory(CATEGORY_SERVICE); + b.setVisibility(VISIBILITY_SECRET); + } + b.setPriority(PRIORITY_MIN); + return b.build(); + } + + @UiThread + @Override + public void updateForegroundNotification(boolean locked) { + Notification n = getForegroundNotification(locked); + notificationManager.notify(ONGOING_NOTIFICATION_ID, n); + } + private void showContactNotification(ContactId c) { androidExecutor.runOnUiThread(() -> { if (blockContacts) return; @@ -636,7 +679,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, NotificationCompat.Builder b = new NotificationCompat.Builder(appContext, REMINDER_CHANNEL_ID); b.setSmallIcon(R.drawable.ic_signout); - b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary)); + b.setColor(getColor(appContext, R.color.briar_primary)); b.setContentTitle( appContext.getText(R.string.reminder_notification_title)); b.setContentText( 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 9f4ba2d14..3c3512281 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 @@ -29,8 +29,10 @@ import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.StringUtils; +import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.DozeWatchdog; +import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.ScreenFilterMonitor; import java.io.File; @@ -200,4 +202,13 @@ public class AppModule { return dozeWatchdog; } + @Provides + @Singleton + LockManager provideLockManager(LifecycleManager lifecycleManager, + EventBus eventBus, LockManagerImpl lockManager) { + lifecycleManager.registerService(lockManager); + eventBus.addListener(lockManager); + return lockManager; + } + } 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 022fa3195..7aa0371fe 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 @@ -2,6 +2,7 @@ package org.briarproject.briar.android; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; @@ -15,7 +16,6 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.support.v4.app.NotificationCompat; -import android.support.v4.content.ContextCompat; import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.crypto.SecretKey; @@ -25,6 +25,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor; 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 java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -44,8 +45,6 @@ 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 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.INFO; import static java.util.logging.Level.WARNING; @@ -74,6 +73,8 @@ public class BriarService extends Service { @Nullable private BroadcastReceiver receiver = null; + @Inject + AndroidNotificationManager notificationManager; @Inject AccountManager accountManager; @@ -121,24 +122,9 @@ public class BriarService extends Service { failureChannel.setLockscreenVisibility(VISIBILITY_SECRET); nm.createNotificationChannel(failureChannel); } - // Show an ongoing notification that the service is running - NotificationCompat.Builder b = - new NotificationCompat.Builder(this, ONGOING_CHANNEL_ID); - b.setSmallIcon(R.drawable.notification_ongoing); - b.setColor(ContextCompat.getColor(this, R.color.briar_primary)); - b.setContentTitle(getText(R.string.ongoing_notification_title)); - b.setContentText(getText(R.string.ongoing_notification_text)); - b.setWhen(0); // Don't show the time - b.setOngoing(true); - 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 (SDK_INT >= 21) { - b.setCategory(CATEGORY_SERVICE); - b.setVisibility(VISIBILITY_SECRET); - } - b.setPriority(PRIORITY_MIN); - startForeground(ONGOING_NOTIFICATION_ID, b.build()); + Notification foregroundNotification = + notificationManager.getForegroundNotification(); + startForeground(ONGOING_NOTIFICATION_ID, foregroundNotification); // Start the services in a background thread new Thread(() -> { StartResult result = lifecycleManager.startServices(dbKey); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java b/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java index ea67d50ec..bbcab474b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java @@ -40,4 +40,9 @@ public interface TestingConstants { * Feature flag for enabling the sign-in reminder in release builds. */ boolean FEATURE_FLAG_SIGN_IN_REMINDER = IS_DEBUG_BUILD; + + /** + * Feature flag for enabling the PIN lock in release builds. + */ + boolean FEATURE_FLAG_PIN_LOCK = IS_DEBUG_BUILD; } 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 new file mode 100644 index 000000000..562ea9588 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java @@ -0,0 +1,132 @@ +package org.briarproject.briar.android.account; + +import android.app.Application; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.content.Context; +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.lifecycle.Service; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +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.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.android.LockManager; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.concurrent.ThreadSafe; +import javax.inject.Inject; + +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.SETTINGS_NAMESPACE; +import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; + +@ThreadSafe +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class LockManagerImpl implements LockManager, Service, EventListener { + + private static final Logger LOG = + Logger.getLogger(LockManagerImpl.class.getName()); + + private final Context appContext; + private final SettingsManager settingsManager; + private final AndroidNotificationManager notificationManager; + @DatabaseExecutor + private final Executor dbExecutor; + + private volatile boolean locked = false; + private volatile boolean lockableSetting = false; + private final MutableLiveData lockable = new MutableLiveData<>(); + + @Inject + public LockManagerImpl(Application app, SettingsManager settingsManager, + AndroidNotificationManager notificationManager, + @DatabaseExecutor Executor dbExecutor) { + this.appContext = app.getApplicationContext(); + this.settingsManager = settingsManager; + this.notificationManager = notificationManager; + this.dbExecutor = dbExecutor; + + // setting this in the constructor makes #getValue() @NonNull + this.lockable.setValue(false); + } + + @Override + public void startService() { + // only load the setting here, because database isn't open before + loadLockableSetting(); + } + + @Override + public void stopService() { + } + + @Override + public LiveData isLockable() { + return lockable; + } + + @UiThread + @Override + public void checkIfLockable() { + boolean oldValue = lockable.getValue(); + boolean newValue = hasScreenLock(appContext) && lockableSetting; + if (oldValue != newValue) { + this.lockable.setValue(newValue); + } + } + + @Override + public boolean isLocked() { + if (locked && !hasScreenLock(appContext)) { + lockable.postValue(false); + locked = false; + } + return locked; + } + + @Override + public void setLocked(boolean locked) { + this.locked = locked; + notificationManager.updateForegroundNotification(locked); + } + + @Override + public void eventOccurred(Event event) { + if (event instanceof SettingsUpdatedEvent) { + SettingsUpdatedEvent e = (SettingsUpdatedEvent) event; + String namespace = e.getNamespace(); + if (namespace.equals(SETTINGS_NAMESPACE)) { + loadLockableSetting(); + } + } + } + + private void loadLockableSetting() { + dbExecutor.execute(() -> { + try { + Settings settings = + settingsManager.getSettings(SETTINGS_NAMESPACE); + lockableSetting = settings.getBoolean(PREF_SCREEN_LOCK, false); + boolean newValue = hasScreenLock(appContext) && lockableSetting; + lockable.postValue(newValue); + } catch (DbException e) { + logException(LOG, WARNING, e); + lockableSetting = false; + lockable.postValue(false); + } + }); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 1d92a31f3..8a8abb9f8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -37,6 +37,7 @@ import org.briarproject.briar.android.login.OpenDatabaseActivity; import org.briarproject.briar.android.login.PasswordActivity; import org.briarproject.briar.android.login.PasswordFragment; import org.briarproject.briar.android.login.SetupActivity; +import org.briarproject.briar.android.login.UnlockActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.panic.PanicPreferencesActivity; import org.briarproject.briar.android.panic.PanicResponderActivity; @@ -163,6 +164,8 @@ public interface ActivityComponent { void inject(StartupFailureActivity activity); + void inject(UnlockActivity activity); + // Fragments void inject(AuthorNameFragment fragment); 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 cf71839f9..77c6b4132 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 @@ -16,7 +16,9 @@ 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.login.UnlockActivity; import org.briarproject.briar.android.logout.ExitActivity; +import org.briarproject.briar.api.android.LockManager; import java.util.logging.Logger; @@ -30,6 +32,7 @@ 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.activity.RequestCodes.REQUEST_UNLOCK; import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent; import static org.briarproject.briar.android.util.UiUtils.isSamsung7; @@ -44,26 +47,43 @@ public abstract class BriarActivity extends BaseActivity { @Inject BriarController briarController; - @Deprecated @Inject DbController dbController; + @Inject + protected LockManager lockManager; @Override protected void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); - if (request == REQUEST_PASSWORD) { - if (result == RESULT_OK) briarController.startAndBindService(); - else supportFinishAfterTransition(); + if (request == REQUEST_PASSWORD && result == RESULT_OK) { + // PasswordActivity finishes when password was entered correctly. + // When back button is pressed there, it will bring itself back, + // so that we never arrive here with a result that is not OK. + briarController.startAndBindService(); + } else if (request == REQUEST_UNLOCK && result != RESULT_OK) { + // We arrive here, if the user presses 'back' + // in the Keyguard unlock screen, because UnlockActivity finishes. + // If we don't finish here, isFinishing will be false in onResume() + // and we launch a new UnlockActivity causing a loop. + supportFinishAfterTransition(); + // If the result is OK, we don't need to do anything here + // and can resume normally. } } @Override - public void onStart() { - super.onStart(); - if (!briarController.accountSignedIn() && !isFinishing()) { + public void onResume() { + super.onResume(); + if (!briarController.accountSignedIn()) { Intent i = new Intent(this, PasswordActivity.class); startActivityForResult(i, REQUEST_PASSWORD); + } else if (lockManager.isLocked() && !isFinishing()) { + // Also check that the activity isn't finishing already. + // This is possible if finishing in onActivityResult(). + // Failure to do this check would cause an UnlockActivity loop. + Intent i = new Intent(this, UnlockActivity.class); + startActivityForResult(i, REQUEST_UNLOCK); } else if (SDK_INT >= 23) { briarController.hasDozed(new UiResultHandler(this) { @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index 4b8caa3d2..f3d72d17c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -12,5 +12,7 @@ public interface RequestCodes { int REQUEST_PERMISSION_CAMERA = 8; int REQUEST_DOZE_WHITELISTING = 9; int REQUEST_ENABLE_BLUETOOTH = 10; + int REQUEST_UNLOCK = 11; + int REQUEST_KEYGUARD_UNLOCK = 12; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java index 5326cf3db..629d0fc97 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java @@ -23,8 +23,6 @@ import org.briarproject.briar.api.android.AndroidNotificationManager; import javax.inject.Inject; -import static android.content.Intent.ACTION_MAIN; -import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.view.View.INVISIBLE; @@ -109,10 +107,9 @@ public class PasswordActivity extends BaseActivity { @Override public void onBackPressed() { - // Show the home screen rather than another password prompt - Intent intent = new Intent(ACTION_MAIN); - intent.addCategory(CATEGORY_HOME); - startActivity(intent); + // Move task and activity to the background instead of showing another + // password prompt. onActivityResult() won't be called in BriarActivity + moveTaskToBack(true); } private void deleteAccount() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java new file mode 100644 index 000000000..49286c21f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java @@ -0,0 +1,123 @@ +package org.briarproject.briar.android.login; + +import android.app.KeyguardManager; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.widget.Button; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.activity.BaseActivity; +import org.briarproject.briar.api.android.LockManager; + +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static android.os.Build.VERSION.SDK_INT; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK; + +@RequiresApi(21) +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class UnlockActivity extends BaseActivity { + + private static final Logger LOG = + Logger.getLogger(UnlockActivity.class.getName()); + private static final String KEYGUARD_SHOWN = "keyguardShown"; + + @Inject + LockManager lockManager; + + private boolean keyguardShown = false; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + overridePendingTransition(0, 0); + setContentView(R.layout.activity_unlock); + + Button button = findViewById(R.id.unlock); + button.setOnClickListener(view -> requestKeyguardUnlock()); + + keyguardShown = state != null && state.getBoolean(KEYGUARD_SHOWN); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // Saving whether we've shown the keyguard already is necessary + // for Android 6 when this activity gets destroyed. + // + // This will not help Android 5. + // There the system will show the keyguard once again. + // So if this activity was destroyed, the user needs to enter PIN twice. + outState.putBoolean(KEYGUARD_SHOWN, keyguardShown); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_KEYGUARD_UNLOCK) { + if (resultCode == RESULT_OK) unlock(); + else { + finish(); + overridePendingTransition(0, 0); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + // Show keyguard after onActivityResult() as been called. + // Check if app is still locked, lockable + // and not finishing (which is possible if recreated) + if (!keyguardShown && lockManager.isLocked() && !isFinishing()) { + requestKeyguardUnlock(); + } else if (!lockManager.isLocked()) { + setResult(RESULT_OK); + finish(); + } + } + + @Override + public void onBackPressed() { + moveTaskToBack(true); + } + + private void requestKeyguardUnlock() { + KeyguardManager keyguardManager = + (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + if (keyguardManager == null) throw new AssertionError(); + Intent intent = keyguardManager.createConfirmDeviceCredentialIntent( + SDK_INT < 23 ? getString(R.string.lock_unlock_verbose) : + getString(R.string.lock_unlock), null); + if (intent == null) { + // the user must have removed the screen lock since locked + LOG.warning("Unlocking without keyguard"); + unlock(); + } else { + keyguardShown = true; + startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK); + overridePendingTransition(0, 0); + } + } + + private void unlock() { + lockManager.setLocked(false); + setResult(RESULT_OK); + finish(); + overridePendingTransition(0, 0); + } + +} 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 e7800f1db..9b7727ff9 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 @@ -7,6 +7,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; @@ -136,6 +137,8 @@ public class NavDrawerActivity extends BriarActivity implements initializeTransports(getLayoutInflater()); transportsView.setAdapter(transportsAdapter); + lockManager.isLockable().observe(this, this::setLockVisible); + if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) { showSignOutFragment(); } else if (state == null) { @@ -152,6 +155,7 @@ public class NavDrawerActivity extends BriarActivity implements public void onStart() { super.onStart(); updateTransports(); + lockManager.checkIfLockable(); controller.showExpiryWarning(new UiResultHandler(this) { @Override public void onResultUi(ExpiryWarning expiry) { @@ -213,9 +217,15 @@ public class NavDrawerActivity extends BriarActivity implements public boolean onNavigationItemSelected(@NonNull MenuItem item) { drawerLayout.closeDrawer(START); clearBackStack(); - loadFragment(item.getItemId()); - // Don't display the Settings item as checked - return item.getItemId() != R.id.nav_btn_settings; + if (item.getItemId() == R.id.nav_btn_lock) { + lockManager.setLocked(true); + ActivityCompat.finishAfterTransition(this); + return false; + } else { + loadFragment(item.getItemId()); + // Don't display the Settings item as checked + return item.getItemId() != R.id.nav_btn_settings; + } } @Override @@ -301,6 +311,11 @@ public class NavDrawerActivity extends BriarActivity implements // Do nothing for now } + private void setLockVisible(boolean visible) { + MenuItem item = navigation.getMenu().findItem(R.id.nav_btn_lock); + if (item != null) item.setVisible(visible); + } + @SuppressWarnings("ConstantConditions") private void showExpiryWarning(ExpiryWarning expiry) { int daysUntilExpiry = getDaysUntilExpiry(); 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 6d5105f1e..1f52854f3 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 @@ -82,10 +82,12 @@ import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.StringUtils.join; import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_DARK_THEME; +import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_PIN_LOCK; import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_SIGN_IN_REMINDER; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT; +import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID; @@ -109,6 +111,7 @@ public class SettingsFragment extends PreferenceFragmentCompat 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 NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; public static final String TOR_LOCATION = "pref_key_tor_location"; @@ -120,13 +123,13 @@ public class SettingsFragment extends PreferenceFragmentCompat private ListPreference enableBluetooth; private ListPreference torNetwork; private SwitchPreference torBlocked; + private SwitchPreference screenLock; private SwitchPreference notifyPrivateMessages; private SwitchPreference notifyGroupMessages; private SwitchPreference notifyForumPosts; private SwitchPreference notifyBlogPosts; private SwitchPreference notifyVibration; private SwitchPreference notifyLockscreen; - private Preference notifySound; // Fields that are accessed from background threads must be volatile @@ -162,6 +165,7 @@ public class SettingsFragment extends PreferenceFragmentCompat setBlockedCountries(); SwitchPreference notifySignIn = (SwitchPreference) findPreference(NOTIFY_SIGN_IN); + screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK); notifyPrivateMessages = (SwitchPreference) findPreference( "pref_key_notify_private_messages"); notifyGroupMessages = (SwitchPreference) findPreference( @@ -176,8 +180,6 @@ public class SettingsFragment extends PreferenceFragmentCompat "pref_key_notify_lock_screen"); notifySound = findPreference("pref_key_notify_sound"); - setSettingsEnabled(false); - language.setOnPreferenceChangeListener(this); theme.setOnPreferenceChangeListener((preference, newValue) -> { if (getActivity() != null) { @@ -200,6 +202,7 @@ public class SettingsFragment extends PreferenceFragmentCompat enableBluetooth.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this); torBlocked.setOnPreferenceChangeListener(this); + screenLock.setOnPreferenceChangeListener(this); if (SDK_INT >= 21) { notifyLockscreen.setVisible(true); notifyLockscreen.setOnPreferenceChangeListener(this); @@ -220,6 +223,7 @@ public class SettingsFragment extends PreferenceFragmentCompat } else { theme.setVisible(FEATURE_FLAG_DARK_THEME); notifySignIn.setVisible(FEATURE_FLAG_SIGN_IN_REMINDER); + screenLock.setVisible(FEATURE_FLAG_PIN_LOCK); findPreference("pref_key_explode").setVisible(false); findPreference("pref_key_test_data").setVisible(false); @@ -229,7 +233,6 @@ public class SettingsFragment extends PreferenceFragmentCompat testing.setVisible(false); } - loadSettings(); } @Override @@ -246,6 +249,8 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onStart() { super.onStart(); eventBus.addListener(this); + setSettingsEnabled(false); + loadSettings(); } @Override @@ -354,6 +359,7 @@ public class SettingsFragment extends PreferenceFragmentCompat enableBluetooth.setValue(Boolean.toString(btSetting)); torNetwork.setValue(Integer.toString(torNetworkSetting)); torBlocked.setChecked(torBlockedSetting); + displayScreenLockSetting(); if (SDK_INT < 26) { notifyPrivateMessages.setChecked(settings.getBoolean( @@ -411,9 +417,12 @@ public class SettingsFragment extends PreferenceFragmentCompat // preferences not needed here, because handled by SharedPreferences: // - pref_key_theme // - pref_key_notify_sign_in + // preferences partly needed here, because they have their own logic + // - pref_key_lock (screenLock -> displayScreenLockSetting()) enableBluetooth.setEnabled(enabled); torNetwork.setEnabled(enabled); torBlocked.setEnabled(enabled); + if (!enabled) screenLock.setEnabled(false); notifyPrivateMessages.setEnabled(enabled); notifyGroupMessages.setEnabled(enabled); notifyForumPosts.setEnabled(enabled); @@ -423,6 +432,23 @@ public class SettingsFragment extends PreferenceFragmentCompat notifySound.setEnabled(enabled); } + private void displayScreenLockSetting() { + if (SDK_INT < 21) { + screenLock.setVisible(false); + } else { + if (getActivity() != null && hasScreenLock(getActivity())) { + screenLock.setEnabled(true); + screenLock.setChecked( + settings.getBoolean(PREF_SCREEN_LOCK, false)); + screenLock.setSummary(R.string.pref_lock_summary); + } else { + screenLock.setEnabled(false); + screenLock.setChecked(false); + screenLock.setSummary(R.string.pref_lock_disabled_summary); + } + } + } + @TargetApi(26) private void setupNotificationPreference(SwitchPreference pref, String channelId, @StringRes int summary) { @@ -478,6 +504,10 @@ public class SettingsFragment extends PreferenceFragmentCompat } else if (preference == torBlocked) { boolean torBlockedSetting = (Boolean) newValue; storeTorBlockedSetting(torBlockedSetting); + } else if (preference == screenLock) { + Settings s = new Settings(); + s.putBoolean(PREF_SCREEN_LOCK, (Boolean) 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/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java index e878d2376..cf890f89e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java @@ -14,6 +14,8 @@ import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.login.OpenDatabaseActivity; import org.briarproject.briar.android.login.SetupActivity; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.api.android.LockManager; import java.util.logging.Logger; @@ -29,6 +31,8 @@ public class SplashScreenActivity extends BaseActivity { @Inject protected AccountManager accountManager; @Inject + protected LockManager lockManager; + @Inject protected AndroidExecutor androidExecutor; @Override @@ -44,7 +48,16 @@ public class SplashScreenActivity extends BaseActivity { setContentView(R.layout.splash); if (accountManager.hasDatabaseKey()) { - startActivity(new Intent(this, OpenDatabaseActivity.class)); + Intent i; + if (lockManager.isLocked()) { + // The database needs to be opened for the app to be locked. + // Start main activity right away. It will open UnlockActivity. + // Otherwise, we would end up with two screen unlock inputs. + i = new Intent(this, NavDrawerActivity.class); + } else { + i = new Intent(this, OpenDatabaseActivity.class); + } + startActivity(i); finish(); } else { new Handler().postDelayed(() -> { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index ee30d8d37..60c6db311 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -2,6 +2,7 @@ package org.briarproject.briar.android.util; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface.OnClickListener; import android.content.Intent; @@ -36,6 +37,7 @@ import org.briarproject.briar.android.widget.LinkDialogFragment; import javax.annotation.Nullable; +import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Context.POWER_SERVICE; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -229,4 +231,15 @@ public class UiUtils { return ContextCompat.getColor(ctx, color); } + public static boolean hasScreenLock(Context ctx) { + if (SDK_INT < 21) return false; + KeyguardManager keyguardManager = + (KeyguardManager) ctx.getSystemService(KEYGUARD_SERVICE); + if (keyguardManager == null) return false; + // check if there's a lock mechanism we can use + // first one is true if SIM card is locked, so use second if available + return (SDK_INT < 23 && keyguardManager.isKeyguardSecure()) || + (SDK_INT >= 23 && keyguardManager.isDeviceSecure()); + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/api/android/AndroidNotificationManager.java b/briar-android/src/main/java/org/briarproject/briar/api/android/AndroidNotificationManager.java index 5de4b066f..fdca9f595 100644 --- a/briar-android/src/main/java/org/briarproject/briar/api/android/AndroidNotificationManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/api/android/AndroidNotificationManager.java @@ -1,5 +1,7 @@ package org.briarproject.briar.api.android; +import android.app.Notification; + import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.sync.GroupId; @@ -52,6 +54,10 @@ public interface AndroidNotificationManager { // Actions for pending intents String ACTION_DISMISS_REMINDER = "dismissReminder"; + Notification getForegroundNotification(); + + void updateForegroundNotification(boolean locked); + void clearContactNotification(ContactId c); void clearAllContactNotifications(); 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 new file mode 100644 index 000000000..968d4e522 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/api/android/LockManager.java @@ -0,0 +1,33 @@ +package org.briarproject.briar.api.android; + +import android.arch.lifecycle.LiveData; +import android.support.annotation.UiThread; + +public interface LockManager { + + /** + * Returns an observable LiveData to indicate whether the app can be locked. + */ + LiveData isLockable(); + + /** + * Updates the LiveData returned by {@link #isLockable()}. + * It checks whether a device screen lock is available and + * whether the app setting is checked. + */ + @UiThread + void checkIfLockable(); + + /** + * Returns true if app is currently locked, false otherwise. + * If the device's screen lock was removed while the app was locked, + * calling this will unlock the app automatically. + */ + boolean isLocked(); + + /** + * Locks the app if true is passed, otherwise unlocks the app. + */ + void setLocked(boolean locked); + +} diff --git a/briar-android/src/main/res/layout/activity_unlock.xml b/briar-android/src/main/res/layout/activity_unlock.xml new file mode 100644 index 000000000..48413a198 --- /dev/null +++ b/briar-android/src/main/res/layout/activity_unlock.xml @@ -0,0 +1,49 @@ + + + + + + + +