Migrate to Android fingerprints and auth for Signal screen lock

This commit is contained in:
Moxie Marlinspike 2018-03-04 11:35:37 -08:00
parent 3970a30e14
commit d28dc670ea
17 changed files with 416 additions and 60 deletions

View File

@ -19,6 +19,7 @@
<uses-feature android:name="android.hardware.portrait" android:required="false"/> <uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/> <uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/> <uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.WRITE_PROFILE"/> <uses-permission android:name="android.permission.WRITE_PROFILE"/>
@ -262,10 +263,8 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphrasePromptActivity" <activity android:name=".PassphrasePromptActivity"
android:label="@string/AndroidManifest__enter_passphrase"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme" android:theme="@style/TextSecure.LightIntroTheme"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".NewConversationActivity" <activity android:name=".NewConversationActivity"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@color/gray70"/>
<stroke android:width="1dp" android:color="@color/gray70"/>
<corners android:radius="10dp"/>
<padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@color/white"/>
<stroke android:width="1dp" android:color="@color/white"/>
<corners android:radius="10dp"/>
<padding android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp"/>
</shape>

View File

@ -1,28 +1,76 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scroll_parent" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/prompt_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View android:id="@+id/shim"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_centerVertical="true"
android:visibility="invisible"/>
<FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true"> android:background="?login_top_background"
android:layout_above="@id/shim">
<RelativeLayout android:id="@+id/prompt_layout" </FrameLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView android:id="@+id/watermark" <android.support.v7.widget.Toolbar
android:layout_width="wrap_content" android:id="@+id/toolbar"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_centerHorizontal="true" android:layout_height="?attr/actionBarSize"
android:layout_marginBottom="50dp" android:layout_marginTop="20dp">
android:src="?lockscreen_watermark"
android:contentDescription="@string/PassphrasePromptActivity_watermark_content_description"
android:layout_marginTop="30dp"/>
<RelativeLayout <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_transparent"
android:layout_gravity="center"/>
</android.support.v7.widget.Toolbar>
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/watermark"> 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">
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:text="@string/prompt_passphrase_activity__unlock_signal"
android:gravity="center_horizontal"
android:textSize="25sp"/>
<ImageView android:id="@+id/fingerprint_auth_container"
android:src="@drawable/ic_fingerprint_white_48dp"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/signal_primary"
android:padding="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="60dp"
tools:visibility="visible"/>
<RelativeLayout android:id="@+id/password_auth_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
tools:visibility="gone">
<EditText android:id="@+id/passphrase_edit" <EditText android:id="@+id/passphrase_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -83,6 +131,5 @@
android:paddingBottom="5dp"/> android:paddingBottom="5dp"/>
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </LinearLayout>
</RelativeLayout>
</ScrollView>

View File

@ -134,6 +134,9 @@
<attr name="contact_list_divider" format="reference"/> <attr name="contact_list_divider" format="reference"/>
<attr name="login_top_background" format="color"/>
<attr name="login_floating_background" format="reference"/>
<declare-styleable name="ColorPreference"> <declare-styleable name="ColorPreference">
<attr name="itemLayout" format="reference" /> <attr name="itemLayout" format="reference" />
<attr name="choices" format="reference" /> <attr name="choices" format="reference" />

View File

@ -39,7 +39,7 @@
<string name="ApplicationPreferencesActivity_off">off</string> <string name="ApplicationPreferencesActivity_off">off</string>
<string name="ApplicationPreferencesActivity_Off">Off</string> <string name="ApplicationPreferencesActivity_Off">Off</string>
<string name="ApplicationPreferencesActivity_sms_mms_summary">SMS %1$s, MMS %2$s</string> <string name="ApplicationPreferencesActivity_sms_mms_summary">SMS %1$s, MMS %2$s</string>
<string name="ApplicationPreferencesActivity_privacy_summary">Passphrase %1$s, Screen security %2$s</string> <string name="ApplicationPreferencesActivity_privacy_summary">Screen lock %1$s, Screen security %2$s</string>
<string name="ApplicationPreferencesActivity_appearance_summary">Theme %1$s, Language %2$s</string> <string name="ApplicationPreferencesActivity_appearance_summary">Theme %1$s, Language %2$s</string>
<!-- AppProtectionPreferenceFragment --> <!-- AppProtectionPreferenceFragment -->
@ -1360,6 +1360,11 @@
<string name="RegistrationActivity_wrong_number">Wrong number?</string> <string name="RegistrationActivity_wrong_number">Wrong number?</string>
<string name="BackupUtil_never">Never</string> <string name="BackupUtil_never">Never</string>
<string name="BackupUtil_unknown">Unknown</string> <string name="BackupUtil_unknown">Unknown</string>
<string name="prompt_passphrase_activity__unlock_signal">Unlock Signal</string>
<string name="preferences_app_protection__screen_lock">Screen lock</string>
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Lock Signal access with Android screen lock or fingerprint</string>
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Screen lock inactivity timeout</string>
<string name="AppProtectionPreferenceFragment_none">None</string>
<!-- EOF --> <!-- EOF -->

View File

@ -58,23 +58,28 @@
</style> </style>
<style name="TextSecure.LightIntroTheme" parent="@style/Theme.AppCompat.Light"> <style name="TextSecure.LightIntroTheme" parent="@style/Theme.AppCompat.Light">
<!--<item name="colorPrimary">@android:color/transparent</item>--> <item name="windowActionBar">false</item>
<item name="actionBarStyle">@style/TextSecure.IntroActionBar</item> <item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item> <item name="windowActionModeOverlay">true</item>
<item name="colorAccent">@color/signal_primary</item> <item name="colorAccent">@color/signal_primary</item>
<item name="android:textColorHint">#cc000000</item> <item name="android:textColorHint">#cc000000</item>
<item name="centered_app_title_color">#55000000</item> <item name="centered_app_title_color">#55000000</item>
<item name="ic_arrow_forward">@drawable/ic_arrow_forward_light</item> <item name="ic_arrow_forward">@drawable/ic_arrow_forward_light</item>
<item name="lockscreen_watermark">@drawable/lockscreen_watermark_light</item> <item name="lockscreen_watermark">@drawable/lockscreen_watermark_light</item>
<item name="android:windowBackground">@color/gray5</item> <item name="android:windowBackground">@color/white</item>
<item name="ic_visibility">@drawable/ic_visibility_grey600_24dp</item> <item name="ic_visibility">@drawable/ic_visibility_grey600_24dp</item>
<item name="ic_visibility_off">@drawable/ic_visibility_off_grey600_24dp</item> <item name="ic_visibility_off">@drawable/ic_visibility_off_grey600_24dp</item>
<item name="login_top_background">@color/signal_primary</item>
<item name="login_floating_background">@drawable/rounded_rectangle_white</item>
</style> </style>
<style name="TextSecure.DarkIntroTheme" parent="@style/Theme.AppCompat"> <style name="TextSecure.DarkIntroTheme" parent="@style/Theme.AppCompat">
<item name="actionBarStyle">@style/TextSecure.IntroActionBar</item> <item name="windowActionBar">false</item>
<item name="android:windowContentOverlay">@null</item> <item name="windowNoTitle">true</item>
<item name="windowActionModeOverlay">true</item>
<item name="colorAccent">@color/signal_primary_dark</item> <item name="colorAccent">@color/signal_primary_dark</item>
<item name="android:textColorHint">@color/white</item> <item name="android:textColorHint">@color/white</item>
@ -84,6 +89,9 @@
<item name="android:windowBackground">@color/black</item> <item name="android:windowBackground">@color/black</item>
<item name="ic_visibility">@drawable/ic_visibility_white_24dp</item> <item name="ic_visibility">@drawable/ic_visibility_white_24dp</item>
<item name="ic_visibility_off">@drawable/ic_visibility_off_white_24dp</item> <item name="ic_visibility_off">@drawable/ic_visibility_off_white_24dp</item>
<item name="login_top_background">@color/black</item>
<item name="login_floating_background">@drawable/rounded_rectangle_dark</item>
</style> </style>
<style name="PopupAnimation" parent="@android:style/Animation"> <style name="PopupAnimation" parent="@android:style/Animation">
@ -352,4 +360,5 @@
<style name="RationaleDialog" parent="Theme.AppCompat.Light.Dialog.Alert"> <style name="RationaleDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item> <item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item>
</style> </style>
</resources> </resources>

View File

@ -2,6 +2,17 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences_app_protection__app_access"> <PreferenceCategory android:title="@string/preferences_app_protection__app_access">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:key="pref_android_screen_lock"
android:defaultValue="false"
android:title="@string/preferences_app_protection__screen_lock"
android:summary="@string/preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint"/>
<Preference android:title="@string/preferences_app_protection__screen_lock_inactivity_timeout"
android:key="pref_android_screen_lock_timeout"
android:dependency="pref_android_screen_lock"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat <org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:key="pref_enable_passphrase_temporary" android:key="pref_enable_passphrase_temporary"
android:defaultValue="true" android:defaultValue="true"

View File

@ -1,4 +1,4 @@
/** /*
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -16,32 +16,47 @@
*/ */
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBar; import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import android.support.v7.widget.Toolbar;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
import android.text.style.TypefaceSpan; import android.text.style.TypefaceSpan;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.components.AnimatingToggle; import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.DynamicIntroTheme; import org.thoughtcrime.securesms.util.DynamicIntroTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.util.TextSecurePreferences;
/** /**
* Activity that prompts for a user's passphrase. * Activity that prompts for a user's passphrase.
@ -50,18 +65,29 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
*/ */
public class PassphrasePromptActivity extends PassphraseActivity { public class PassphrasePromptActivity extends PassphraseActivity {
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme(); private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage(); private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ImageView fingerprintPrompt;
private EditText passphraseText; private EditText passphraseText;
private ImageButton showButton; private ImageButton showButton;
private ImageButton hideButton; private ImageButton hideButton;
private AnimatingToggle visibilityToggle; private AnimatingToggle visibilityToggle;
private FingerprintManagerCompat fingerprintManager;
private CancellationSignal fingerprintCancellationSignal;
private FingerprintListener fingerprintListener;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "onCreate()");
dynamicTheme.onCreate(this); dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this); dynamicLanguage.onCreate(this);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.prompt_passphrase_activity); setContentView(R.layout.prompt_passphrase_activity);
@ -73,6 +99,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
super.onResume(); super.onResume();
dynamicTheme.onResume(this); dynamicTheme.onResume(this);
dynamicLanguage.onResume(this); dynamicLanguage.onResume(this);
if (TextSecurePreferences.isScreenLockEnabled(this)) {
resumeScreenLock();
}
}
@Override
public void onPause() {
super.onPause();
if (TextSecurePreferences.isScreenLockEnabled(this)) {
pauseScreenLock();
}
} }
@Override @Override
@ -101,6 +140,17 @@ public class PassphrasePromptActivity extends PassphraseActivity {
return false; return false;
} }
public void onActivityResult(int requestCode, int resultcode, Intent data) {
if (requestCode != 1) return;
if (resultcode == RESULT_OK) {
handleAuthenticated();
} else {
Log.w(TAG, "Authentication failed");
finish();
}
}
private void handleLogSubmit() { private void handleLogSubmit() {
Intent intent = new Intent(this, LogSubmitActivity.class); Intent intent = new Intent(this, LogSubmitActivity.class);
startActivity(intent); startActivity(intent);
@ -120,6 +170,15 @@ public class PassphrasePromptActivity extends PassphraseActivity {
} }
} }
private void handleAuthenticated() {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
throw new AssertionError(e);
}
}
private void setPassphraseVisibility(boolean visibility) { private void setPassphraseVisibility(boolean visibility) {
int cursorPosition = passphraseText.getSelectionStart(); int cursorPosition = passphraseText.getSelectionStart();
if (visibility) { if (visibility) {
@ -133,15 +192,22 @@ public class PassphrasePromptActivity extends PassphraseActivity {
} }
private void initializeResources() { private void initializeResources() {
getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); View passphraseAuthContainer = findViewById(R.id.password_auth_container);
getSupportActionBar().setCustomView(R.layout.centered_app_title); ImageButton okButton = findViewById(R.id.ok_button);
Toolbar toolbar = findViewById(R.id.toolbar);
ImageButton okButton = (ImageButton) findViewById(R.id.ok_button); showButton = findViewById(R.id.passphrase_visibility);
hideButton = findViewById(R.id.passphrase_visibility_off);
visibilityToggle = findViewById(R.id.button_toggle);
passphraseText = findViewById(R.id.passphrase_edit);
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
fingerprintManager = FingerprintManagerCompat.from(this);
fingerprintCancellationSignal = new CancellationSignal();
fingerprintListener = new FingerprintListener();
setSupportActionBar(toolbar);
getSupportActionBar().setTitle("");
showButton = (ImageButton) findViewById(R.id.passphrase_visibility);
hideButton = (ImageButton) findViewById(R.id.passphrase_visibility_off);
visibilityToggle = (AnimatingToggle) findViewById(R.id.button_toggle);
passphraseText = (EditText) findViewById(R.id.passphrase_edit);
SpannableString hint = new SpannableString(" " + getString(R.string.PassphrasePromptActivity_enter_passphrase)); SpannableString hint = new SpannableString(" " + getString(R.string.PassphrasePromptActivity_enter_passphrase));
hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
@ -153,6 +219,48 @@ public class PassphrasePromptActivity extends PassphraseActivity {
passphraseText.setOnEditorActionListener(new PassphraseActionListener()); passphraseText.setOnEditorActionListener(new PassphraseActionListener());
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock), passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock),
EditorInfo.IME_ACTION_DONE); EditorInfo.IME_ACTION_DONE);
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
if (TextSecurePreferences.isScreenLockEnabled(this)) {
passphraseAuthContainer.setVisibility(View.GONE);
fingerprintPrompt.setVisibility(View.VISIBLE);
} else {
passphraseAuthContainer.setVisibility(View.VISIBLE);
fingerprintPrompt.setVisibility(View.GONE);
}
}
private void resumeScreenLock() {
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
assert keyguardManager != null;
if (Build.VERSION.SDK_INT >= 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 { private class PassphraseActionListener implements TextView.OnEditorActionListener {
@ -202,4 +310,57 @@ public class PassphrasePromptActivity extends PassphraseActivity {
this.passphraseText.setText(""); this.passphraseText.setText("");
System.gc(); 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);
}
}
} }

View File

@ -1,8 +1,10 @@
package org.thoughtcrime.securesms.preferences; package org.thoughtcrime.securesms.preferences;
import android.app.KeyguardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
@ -18,6 +20,7 @@ import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.BlockedContactsActivity; import org.thoughtcrime.securesms.BlockedContactsActivity;
import org.thoughtcrime.securesms.PassphraseChangeActivity; import org.thoughtcrime.securesms.PassphraseChangeActivity;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
@ -37,16 +40,16 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary"); disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF) this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
.setOnPreferenceClickListener(new ChangePassphraseClickListener()); this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
.setOnPreferenceClickListener(new PassphraseIntervalClickListener()); this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF) this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
.setOnPreferenceChangeListener(new ReadReceiptToggleListener()); this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(PREFERENCE_CATEGORY_BLOCKED) this.findPreference(PREFERENCE_CATEGORY_BLOCKED).setOnPreferenceClickListener(new BlockedContactsClickListener());
.setOnPreferenceClickListener(new BlockedContactsClickListener()); disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
disablePassphrase
.setOnPreferenceChangeListener(new DisablePassphraseClickListener()); initializeVisibility();
} }
@Override @Override
@ -59,17 +62,88 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
super.onResume(); super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__privacy); ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__privacy);
initializeTimeoutSummary(); if (!TextSecurePreferences.isPasswordDisabled(getContext())) initializePassphraseTimeoutSummary();
else initializeScreenLockTimeoutSummary();
disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity())); disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity()));
} }
private void initializeTimeoutSummary() { private void initializePassphraseTimeoutSummary() {
int timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(getActivity()); int timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(getActivity());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF) this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
.setSummary(getResources().getQuantityString(R.plurals.AppProtectionPreferenceFragment_minutes, timeoutMinutes, timeoutMinutes)); .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 { private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
@ -118,7 +192,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
(int)TimeUnit.SECONDS.toMinutes(seconds), 1); (int)TimeUnit.SECONDS.toMinutes(seconds), 1);
TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes); TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes);
initializeTimeoutSummary(); initializePassphraseTimeoutSummary();
} }
} }
@ -142,6 +216,8 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
Intent intent = new Intent(getActivity(), KeyCachingService.class); Intent intent = new Intent(getActivity(), KeyCachingService.class);
intent.setAction(KeyCachingService.DISABLE_ACTION); intent.setAction(KeyCachingService.DISABLE_ACTION);
getActivity().startService(intent); getActivity().startService(intent);
initializeVisibility();
}); });
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
builder.show(); builder.show();
@ -171,7 +247,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on); final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on);
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off); final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
if (TextSecurePreferences.isPasswordDisabled(context)) { if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
if (TextSecurePreferences.isScreenSecurityEnabled(context)) { if (TextSecurePreferences.isScreenSecurityEnabled(context)) {
return context.getString(privacySummaryResId, offRes, onRes); return context.getString(privacySummaryResId, offRes, onRes);
} else { } else {

View File

@ -83,7 +83,7 @@ public class KeyCachingService extends Service {
} }
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) { public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
if (masterSecret == null && TextSecurePreferences.isPasswordDisabled(context)) { if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
try { try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
Intent intent = new Intent(context, KeyCachingService.class); 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.pending = PendingIntent.getService(this, 0, new Intent(PASSPHRASE_EXPIRED_EVENT, null,
this, KeyCachingService.class), 0); this, KeyCachingService.class), 0);
if (TextSecurePreferences.isPasswordDisabled(this)) { if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
try { try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret); setMasterSecret(masterSecret);
@ -210,8 +210,11 @@ public class KeyCachingService extends Service {
} }
private void handleDisableService() { private void handleDisableService() {
if (TextSecurePreferences.isPasswordDisabled(this)) if (TextSecurePreferences.isPasswordDisabled(this) &&
!TextSecurePreferences.isScreenLockEnabled(this))
{
stopForeground(true); stopForeground(true);
}
} }
private void handleLocaleChanged() { private void handleLocaleChanged() {
@ -221,10 +224,19 @@ public class KeyCachingService extends Service {
private void startTimeoutIfAppropriate() { private void startTimeoutIfAppropriate() {
boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(this); boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(this);
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(this);
if ((activitiesRunning == 0) && (KeyCachingService.masterSecret != null) && timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(this)) { if ((activitiesRunning == 0) && (KeyCachingService.masterSecret != null) &&
long timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(this); (timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(this)) ||
long timeoutMillis = TimeUnit.MINUTES.toMillis(timeoutMinutes); (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); Log.w("KeyCachingService", "Starting timeout: " + timeoutMillis);
@ -280,7 +292,7 @@ public class KeyCachingService extends Service {
} }
private void foregroundService() { private void foregroundService() {
if (TextSecurePreferences.isPasswordDisabled(this)) { if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
stopForeground(true); stopForeground(true);
return; return;
} }

View File

@ -62,7 +62,7 @@ public class TextSecurePreferences {
public static final String ALL_MMS_PREF = "pref_all_mms"; public static final String ALL_MMS_PREF = "pref_all_mms";
public static final String ALL_SMS_PREF = "pref_all_sms"; public static final String ALL_SMS_PREF = "pref_all_sms";
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval"; 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"; 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_SENDS_PREF = "pref_enter_sends";
private static final String ENTER_PRESENT_PREF = "pref_enter_key"; 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"; private static final String BACKUP_TIME = "pref_backup_next_time";
public static final String BACKUP_NOW = "pref_backup_create"; 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) { public static void setBackupPassphrase(@NonNull Context context, @Nullable String passphrase) {
setStringPreference(context, BACKUP_PASSPHRASE, passphrase); setStringPreference(context, BACKUP_PASSPHRASE, passphrase);
} }