diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f0e7bfa30c..75e147c44b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,6 +19,7 @@
+
@@ -262,10 +263,8 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/rounded_rectangle_white.xml b/res/drawable/rounded_rectangle_white.xml
new file mode 100644
index 0000000000..337bd7d8dc
--- /dev/null
+++ b/res/drawable/rounded_rectangle_white.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/prompt_passphrase_activity.xml b/res/layout/prompt_passphrase_activity.xml
index 68de241ad3..fe49d9b01e 100644
--- a/res/layout/prompt_passphrase_activity.xml
+++ b/res/layout/prompt_passphrase_activity.xml
@@ -1,28 +1,76 @@
-
+
+
+
+
+ android:background="?login_top_background"
+ android:layout_above="@id/shim">
-
+
-
+
-
+
+
+
+
+
+ android:layout_marginLeft="30dp"
+ android:layout_marginRight="30dp"
+ android:orientation="vertical"
+ android:background="?login_floating_background"
+ android:layout_centerInParent="true"
+ android:padding="20dp"
+ android:elevation="10dp">
+
+
+
+
+
+
-
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e55e02204b..cf044a6035 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -134,6 +134,9 @@
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fe226aac13..15bbcb2210 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -39,7 +39,7 @@
off
Off
SMS %1$s, MMS %2$s
- Passphrase %1$s, Screen security %2$s
+ Screen lock %1$s, Screen security %2$s
Theme %1$s, Language %2$s
@@ -1360,6 +1360,11 @@
Wrong number?
Never
Unknown
+ Unlock Signal
+ Screen lock
+ Lock Signal access with Android screen lock or fingerprint
+ Screen lock inactivity timeout
+ None
diff --git a/res/values/themes.xml b/res/values/themes.xml
index b1cbdfe2e7..f174636de2 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -58,23 +58,28 @@
+
diff --git a/res/xml/preferences_app_protection.xml b/res/xml/preferences_app_protection.xml
index 771c365753..36f541de89 100644
--- a/res/xml/preferences_app_protection.xml
+++ b/res/xml/preferences_app_protection.xml
@@ -2,6 +2,17 @@
+
+
+
+
+
= Build.VERSION_CODES.JELLY_BEAN && !keyguardManager.isKeyguardSecure()) {
+ Log.w(TAG ,"Keyguard not secure...");
+ handleAuthenticated();
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= 16 && fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
+ Log.w(TAG, "Listening for fingerprints...");
+ fingerprintCancellationSignal = new CancellationSignal();
+ fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
+ } else if (Build.VERSION.SDK_INT >= 21){
+ Log.w(TAG, "firing intent...");
+ Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Signal", "");
+ startActivityForResult(intent, 1);
+ } else {
+ Log.w(TAG, "Not compatible...");
+ handleAuthenticated();
+ }
+ }
+
+ private void pauseScreenLock() {
+ if (Build.VERSION.SDK_INT >= 16 && fingerprintCancellationSignal != null) {
+ fingerprintCancellationSignal.cancel();
+ }
}
private class PassphraseActionListener implements TextView.OnEditorActionListener {
@@ -202,4 +310,57 @@ public class PassphrasePromptActivity extends PassphraseActivity {
this.passphraseText.setText("");
System.gc();
}
+
+ private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ Log.w(TAG, "Authentication error: " + errMsgId + " " + errString);
+ onAuthenticationFailed();
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
+ Log.w(TAG, "onAuthenticationSucceeded");
+ fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
+ fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ handleAuthenticated();
+
+ fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
+ }
+ }).start();
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ Log.w(TAG, "onAuthenticatoinFailed()");
+ FingerprintManagerCompat.AuthenticationCallback callback = this;
+
+ fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
+
+ TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
+ shake.setDuration(50);
+ shake.setRepeatCount(7);
+ shake.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
+ fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ });
+
+ fingerprintPrompt.startAnimation(shake);
+ }
+
+ }
}
diff --git a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
index 358ca0cc96..92c321d4f3 100644
--- a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
+++ b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
@@ -1,8 +1,10 @@
package org.thoughtcrime.securesms.preferences;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
+import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
@@ -18,6 +20,7 @@ import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.BlockedContactsActivity;
import org.thoughtcrime.securesms.PassphraseChangeActivity;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
import org.thoughtcrime.securesms.service.KeyCachingService;
@@ -37,16 +40,16 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
- this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF)
- .setOnPreferenceClickListener(new ChangePassphraseClickListener());
- this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
- .setOnPreferenceClickListener(new PassphraseIntervalClickListener());
- this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF)
- .setOnPreferenceChangeListener(new ReadReceiptToggleListener());
- this.findPreference(PREFERENCE_CATEGORY_BLOCKED)
- .setOnPreferenceClickListener(new BlockedContactsClickListener());
- disablePassphrase
- .setOnPreferenceChangeListener(new DisablePassphraseClickListener());
+ this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
+ this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
+
+ this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
+ this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
+ this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
+ this.findPreference(PREFERENCE_CATEGORY_BLOCKED).setOnPreferenceClickListener(new BlockedContactsClickListener());
+ disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
+
+ initializeVisibility();
}
@Override
@@ -59,17 +62,88 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__privacy);
- initializeTimeoutSummary();
+ if (!TextSecurePreferences.isPasswordDisabled(getContext())) initializePassphraseTimeoutSummary();
+ else initializeScreenLockTimeoutSummary();
disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity()));
}
- private void initializeTimeoutSummary() {
+ private void initializePassphraseTimeoutSummary() {
int timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(getActivity());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
.setSummary(getResources().getQuantityString(R.plurals.AppProtectionPreferenceFragment_minutes, timeoutMinutes, timeoutMinutes));
}
+ private void initializeScreenLockTimeoutSummary() {
+ long timeoutSeconds = TextSecurePreferences.getScreenLockTimeout(getContext());
+ long hours = TimeUnit.SECONDS.toHours(timeoutSeconds);
+ long minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - (TimeUnit.SECONDS.toHours(timeoutSeconds) * 60 );
+ long seconds = TimeUnit.SECONDS.toSeconds(timeoutSeconds) - (TimeUnit.SECONDS.toMinutes(timeoutSeconds) * 60);
+
+ findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT)
+ .setSummary(timeoutSeconds <= 0 ? getString(R.string.AppProtectionPreferenceFragment_none) :
+ String.format("%02d:%02d:%02d", hours, minutes, seconds));
+ }
+
+ private void initializeVisibility() {
+ if (TextSecurePreferences.isPasswordDisabled(getContext())) {
+ findPreference("pref_enable_passphrase_temporary").setVisible(false);
+ findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setVisible(false);
+ findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setVisible(false);
+ findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF).setVisible(false);
+
+ KeyguardManager keyguardManager = (KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE);
+ if (Build.VERSION.SDK_INT < 16 || !keyguardManager.isKeyguardSecure()) {
+ ((SwitchPreferenceCompat)findPreference(TextSecurePreferences.SCREEN_LOCK)).setChecked(false);
+ findPreference(TextSecurePreferences.SCREEN_LOCK).setEnabled(false);
+ }
+ } else {
+ findPreference(TextSecurePreferences.SCREEN_LOCK).setVisible(false);
+ findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setVisible(false);
+ }
+ }
+
+ private class ScreenLockListener implements Preference.OnPreferenceChangeListener {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ TextSecurePreferences.setScreenLockEnabled(getContext(), (Boolean)newValue);
+
+ Intent intent = new Intent(getContext(), KeyCachingService.class);
+ intent.setAction(KeyCachingService.CLEAR_KEY_EVENT);
+ getContext().startService(intent);
+
+ return true;
+ }
+ }
+
+ private class ScreenLockTimeoutListener implements Preference.OnPreferenceClickListener, HmsPickerDialogFragment.HmsPickerDialogHandler {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ int[] attributes = {R.attr.app_protect_timeout_picker_color};
+ TypedArray hmsStyle = getActivity().obtainStyledAttributes(attributes);
+
+ new HmsPickerBuilder().setFragmentManager(getFragmentManager())
+ .setStyleResId(hmsStyle.getResourceId(0, R.style.BetterPickersDialogFragment_Light))
+ .addHmsPickerDialogHandler(this)
+ .show();
+
+ hmsStyle.recycle();
+
+ return true;
+ }
+
+ @Override
+ public void onDialogHmsSet(int reference, int hours, int minutes, int seconds) {
+ long timeoutSeconds = Math.max(TimeUnit.HOURS.toSeconds(hours) +
+ TimeUnit.MINUTES.toSeconds(minutes) +
+ TimeUnit.SECONDS.toSeconds(seconds), 60);
+
+ TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds);
+ initializeScreenLockTimeoutSummary();
+ }
+ }
+
private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -118,7 +192,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
(int)TimeUnit.SECONDS.toMinutes(seconds), 1);
TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes);
- initializeTimeoutSummary();
+ initializePassphraseTimeoutSummary();
}
}
@@ -142,6 +216,8 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
Intent intent = new Intent(getActivity(), KeyCachingService.class);
intent.setAction(KeyCachingService.DISABLE_ACTION);
getActivity().startService(intent);
+
+ initializeVisibility();
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
@@ -171,7 +247,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on);
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
- if (TextSecurePreferences.isPasswordDisabled(context)) {
+ if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
if (TextSecurePreferences.isScreenSecurityEnabled(context)) {
return context.getString(privacySummaryResId, offRes, onRes);
} else {
diff --git a/src/org/thoughtcrime/securesms/service/KeyCachingService.java b/src/org/thoughtcrime/securesms/service/KeyCachingService.java
index 6861f2b8a5..fd78b45cd5 100644
--- a/src/org/thoughtcrime/securesms/service/KeyCachingService.java
+++ b/src/org/thoughtcrime/securesms/service/KeyCachingService.java
@@ -83,7 +83,7 @@ public class KeyCachingService extends Service {
}
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
- if (masterSecret == null && TextSecurePreferences.isPasswordDisabled(context)) {
+ if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
Intent intent = new Intent(context, KeyCachingService.class);
@@ -146,7 +146,7 @@ public class KeyCachingService extends Service {
this.pending = PendingIntent.getService(this, 0, new Intent(PASSPHRASE_EXPIRED_EVENT, null,
this, KeyCachingService.class), 0);
- if (TextSecurePreferences.isPasswordDisabled(this)) {
+ if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
@@ -210,8 +210,11 @@ public class KeyCachingService extends Service {
}
private void handleDisableService() {
- if (TextSecurePreferences.isPasswordDisabled(this))
+ if (TextSecurePreferences.isPasswordDisabled(this) &&
+ !TextSecurePreferences.isScreenLockEnabled(this))
+ {
stopForeground(true);
+ }
}
private void handleLocaleChanged() {
@@ -221,10 +224,19 @@ public class KeyCachingService extends Service {
private void startTimeoutIfAppropriate() {
boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(this);
+ long screenTimeout = TextSecurePreferences.getScreenLockTimeout(this);
- if ((activitiesRunning == 0) && (KeyCachingService.masterSecret != null) && timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(this)) {
- long timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(this);
- long timeoutMillis = TimeUnit.MINUTES.toMillis(timeoutMinutes);
+ if ((activitiesRunning == 0) && (KeyCachingService.masterSecret != null) &&
+ (timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(this)) ||
+ (screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(this)))
+ {
+ long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(this);
+ long screenLockTimeoutSeconds = TextSecurePreferences.getScreenLockTimeout(this);
+
+ long timeoutMillis;
+
+ if (!TextSecurePreferences.isPasswordDisabled(this)) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes);
+ else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds);
Log.w("KeyCachingService", "Starting timeout: " + timeoutMillis);
@@ -280,7 +292,7 @@ public class KeyCachingService extends Service {
}
private void foregroundService() {
- if (TextSecurePreferences.isPasswordDisabled(this)) {
+ if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
stopForeground(true);
return;
}
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index e98669d0d1..21c06c6846 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -62,7 +62,7 @@ public class TextSecurePreferences {
public static final String ALL_MMS_PREF = "pref_all_mms";
public static final String ALL_SMS_PREF = "pref_all_sms";
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval";
- private static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
+ public static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
public static final String SCREEN_SECURITY_PREF = "pref_screen_security";
private static final String ENTER_SENDS_PREF = "pref_enter_sends";
private static final String ENTER_PRESENT_PREF = "pref_enter_key";
@@ -142,6 +142,25 @@ public class TextSecurePreferences {
private static final String BACKUP_TIME = "pref_backup_next_time";
public static final String BACKUP_NOW = "pref_backup_create";
+ public static final String SCREEN_LOCK = "pref_android_screen_lock";
+ public static final String SCREEN_LOCK_TIMEOUT = "pref_android_screen_lock_timeout";
+
+ public static boolean isScreenLockEnabled(@NonNull Context context) {
+ return getBooleanPreference(context, SCREEN_LOCK, false);
+ }
+
+ public static void setScreenLockEnabled(@NonNull Context context, boolean value) {
+ setBooleanPreference(context, SCREEN_LOCK, value);
+ }
+
+ public static long getScreenLockTimeout(@NonNull Context context) {
+ return getLongPreference(context, SCREEN_LOCK_TIMEOUT, 0);
+ }
+
+ public static void setScreenLockTimeout(@NonNull Context context, long value) {
+ setLongPreference(context, SCREEN_LOCK_TIMEOUT, value);
+ }
+
public static void setBackupPassphrase(@NonNull Context context, @Nullable String passphrase) {
setStringPreference(context, BACKUP_PASSPHRASE, passphrase);
}