Merge branch 'master' into 'preference-switches'

# Conflicts:
#   briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
This commit is contained in:
akwizgran
2018-08-14 09:59:22 +00:00
52 changed files with 632 additions and 119 deletions

View File

@@ -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);

View File

@@ -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(

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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<Boolean> 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<Boolean> 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);
}
});
}
}

View File

@@ -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);

View File

@@ -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<Boolean>(this) {
@Override

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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<ExpiryWarning>(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();

View File

@@ -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);

View File

@@ -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(() -> {

View File

@@ -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());
}
}

View File

@@ -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();

View File

@@ -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<Boolean> 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);
}