mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Migrate to Android fingerprints and auth for Signal screen lock
This commit is contained in:
parent
3970a30e14
commit
d28dc670ea
@ -19,6 +19,7 @@
|
||||
<uses-feature android:name="android.hardware.portrait" 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="android.permission.READ_PROFILE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
|
||||
@ -262,10 +263,8 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PassphrasePromptActivity"
|
||||
android:label="@string/AndroidManifest__enter_passphrase"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightIntroTheme"
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".NewConversationActivity"
|
||||
|
BIN
res/drawable-hdpi/ic_fingerprint_white_48dp.png
Normal file
BIN
res/drawable-hdpi/ic_fingerprint_white_48dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
res/drawable-mdpi/ic_fingerprint_white_48dp.png
Normal file
BIN
res/drawable-mdpi/ic_fingerprint_white_48dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-xhdpi/ic_fingerprint_white_48dp.png
Normal file
BIN
res/drawable-xhdpi/ic_fingerprint_white_48dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-xxhdpi/ic_fingerprint_white_48dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_fingerprint_white_48dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
BIN
res/drawable-xxxhdpi/ic_fingerprint_white_48dp.png
Normal file
BIN
res/drawable-xxxhdpi/ic_fingerprint_white_48dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
7
res/drawable/rounded_rectangle_dark.xml
Normal file
7
res/drawable/rounded_rectangle_dark.xml
Normal 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>
|
7
res/drawable/rounded_rectangle_white.xml
Normal file
7
res/drawable/rounded_rectangle_white.xml
Normal 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>
|
@ -1,28 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scroll_parent"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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:fillViewport="true">
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout android:id="@+id/prompt_layout"
|
||||
<View android:id="@+id/shim"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="1dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<ImageView android:id="@+id/watermark"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?login_top_background"
|
||||
android:layout_above="@id/shim">
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_marginTop="20dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:src="?lockscreen_watermark"
|
||||
android:contentDescription="@string/PassphrasePromptActivity_watermark_content_description"
|
||||
android:layout_marginTop="30dp"/>
|
||||
android:src="@drawable/icon_transparent"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<RelativeLayout
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
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"
|
||||
android:layout_width="match_parent"
|
||||
@ -83,6 +131,5 @@
|
||||
android:paddingBottom="5dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
@ -134,6 +134,9 @@
|
||||
|
||||
<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">
|
||||
<attr name="itemLayout" format="reference" />
|
||||
<attr name="choices" format="reference" />
|
||||
|
@ -39,7 +39,7 @@
|
||||
<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_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>
|
||||
|
||||
<!-- AppProtectionPreferenceFragment -->
|
||||
@ -1360,6 +1360,11 @@
|
||||
<string name="RegistrationActivity_wrong_number">Wrong number?</string>
|
||||
<string name="BackupUtil_never">Never</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 -->
|
||||
|
@ -58,23 +58,28 @@
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightIntroTheme" parent="@style/Theme.AppCompat.Light">
|
||||
<!--<item name="colorPrimary">@android:color/transparent</item>-->
|
||||
<item name="actionBarStyle">@style/TextSecure.IntroActionBar</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="colorAccent">@color/signal_primary</item>
|
||||
|
||||
<item name="android:textColorHint">#cc000000</item>
|
||||
<item name="centered_app_title_color">#55000000</item>
|
||||
<item name="ic_arrow_forward">@drawable/ic_arrow_forward_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_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 name="TextSecure.DarkIntroTheme" parent="@style/Theme.AppCompat">
|
||||
<item name="actionBarStyle">@style/TextSecure.IntroActionBar</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="colorAccent">@color/signal_primary_dark</item>
|
||||
|
||||
<item name="android:textColorHint">@color/white</item>
|
||||
@ -84,6 +89,9 @@
|
||||
<item name="android:windowBackground">@color/black</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="login_top_background">@color/black</item>
|
||||
<item name="login_floating_background">@drawable/rounded_rectangle_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="PopupAnimation" parent="@android:style/Animation">
|
||||
@ -352,4 +360,5 @@
|
||||
<style name="RationaleDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -2,6 +2,17 @@
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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
|
||||
android:key="pref_enable_passphrase_temporary"
|
||||
android:defaultValue="true"
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -16,32 +16,47 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
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.InputType;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
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.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicIntroTheme;
|
||||
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.
|
||||
@ -50,18 +65,29 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
*/
|
||||
public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
|
||||
|
||||
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private ImageView fingerprintPrompt;
|
||||
|
||||
private EditText passphraseText;
|
||||
private ImageButton showButton;
|
||||
private ImageButton hideButton;
|
||||
private AnimatingToggle visibilityToggle;
|
||||
|
||||
private FingerprintManagerCompat fingerprintManager;
|
||||
private CancellationSignal fingerprintCancellationSignal;
|
||||
private FingerprintListener fingerprintListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.w(TAG, "onCreate()");
|
||||
dynamicTheme.onCreate(this);
|
||||
dynamicLanguage.onCreate(this);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.prompt_passphrase_activity);
|
||||
@ -73,6 +99,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
dynamicLanguage.onResume(this);
|
||||
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||
resumeScreenLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||
pauseScreenLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,6 +140,17 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
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() {
|
||||
Intent intent = new Intent(this, LogSubmitActivity.class);
|
||||
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) {
|
||||
int cursorPosition = passphraseText.getSelectionStart();
|
||||
if (visibility) {
|
||||
@ -133,15 +192,22 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||
getSupportActionBar().setCustomView(R.layout.centered_app_title);
|
||||
View passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
||||
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));
|
||||
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);
|
||||
@ -153,6 +219,48 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
passphraseText.setOnEditorActionListener(new PassphraseActionListener());
|
||||
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock),
|
||||
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 {
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,9 +210,12 @@ public class KeyCachingService extends Service {
|
||||
}
|
||||
|
||||
private void handleDisableService() {
|
||||
if (TextSecurePreferences.isPasswordDisabled(this))
|
||||
if (TextSecurePreferences.isPasswordDisabled(this) &&
|
||||
!TextSecurePreferences.isScreenLockEnabled(this))
|
||||
{
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocaleChanged() {
|
||||
dynamicLanguage.updateServiceLocale(this);
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user