From d28dc670ea58867e97336434d31c7003e5cf9c54 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 4 Mar 2018 11:35:37 -0800 Subject: [PATCH] Migrate to Android fingerprints and auth for Signal screen lock --- AndroidManifest.xml | 3 +- .../ic_fingerprint_white_48dp.png | Bin 0 -> 1808 bytes .../ic_fingerprint_white_48dp.png | Bin 0 -> 1192 bytes .../ic_fingerprint_white_48dp.png | Bin 0 -> 2483 bytes .../ic_fingerprint_white_48dp.png | Bin 0 -> 3765 bytes .../ic_fingerprint_white_48dp.png | Bin 0 -> 5064 bytes res/drawable/rounded_rectangle_dark.xml | 7 + res/drawable/rounded_rectangle_white.xml | 7 + res/layout/prompt_passphrase_activity.xml | 85 ++++++-- res/values/attrs.xml | 3 + res/values/strings.xml | 7 +- res/values/themes.xml | 21 +- res/xml/preferences_app_protection.xml | 11 ++ .../securesms/PassphrasePromptActivity.java | 181 +++++++++++++++++- .../AppProtectionPreferenceFragment.java | 104 ++++++++-- .../securesms/service/KeyCachingService.java | 26 ++- .../securesms/util/TextSecurePreferences.java | 21 +- 17 files changed, 416 insertions(+), 60 deletions(-) create mode 100644 res/drawable-hdpi/ic_fingerprint_white_48dp.png create mode 100644 res/drawable-mdpi/ic_fingerprint_white_48dp.png create mode 100644 res/drawable-xhdpi/ic_fingerprint_white_48dp.png create mode 100644 res/drawable-xxhdpi/ic_fingerprint_white_48dp.png create mode 100644 res/drawable-xxxhdpi/ic_fingerprint_white_48dp.png create mode 100644 res/drawable/rounded_rectangle_dark.xml create mode 100644 res/drawable/rounded_rectangle_white.xml 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"/> FF1zf0`cE^N+}zf^w|A5Nv1k4|^U8N- z&YW{*&S;V*X_Eg<(b@pxEwIrpN1XyS%I#L-HIEyljWe<-nGR^?Ml)>F5ZP{qev*v` zbcJ8|T&8kRu`am=O1Q~-vDDh)72^zYrE}#=NNAy*%iV00+1^znmep>Q$Q4j`B>?z` zIliyu*N!SM$jc4`;BD9B2B-{BXN6ld%g83qG}vnOfStJkdKq!)M;=`uS|iu`Vv&=AJrxe zw_hxWY_LGFNv4=-ky4dn*`#kguUk}ta@{fk>R=x@sz)64gGF)L8Y6W|YcJP&%zFS> z93P+FD!>8fH54e%JK!rl;_X%g_|h}!CyR@27K9N8-5j-C?*!PSdBcE;L2YzcEi3`l zD0X&(uY70A0d;;7)!(Kbyx0(+q#7_b)U@;#U~AfwZ=tiRb=N7~iWNEvm=e`a1jib- zLx02)iEyj_3|J^X#c38<=o57S>U?azLM_Doz7;I$=h3p4VRplSk~&HT+xdUMB(VXv z_q=0BFJGD}HsqGp0e&1u$ZSH#s5IN1^pFYw)Z1^n?e?n&K)EpzQR1=zoOHG9Lx(P^ zz;C0e+gJ}^k7xCfA01*}Q+x_wwN|06m0jQidD#LbtOq-z&Hc8I03W+uQu@c_ojw7S zMGoQ$RbWE4Km$O%-jU_(0u-mcQSr^20oV~es(%K)(k45gcftH9k=Oug+}$8Abgx>l zRQTBHd zOp5yS^sZD{CeC-B2bJ0f``Qog%oHeLFGNY_Ot3AwBg557Cr9;-Y-uMb4mFd(l1zav z0>|YAKT-j1m3kZ}%rVHNx+?NJ^`KJs(C>C|G#pY7a6~dQpdzq7W!l}rA6=jll)6wn zuD8#?j9|h zU|hDA~T>jKw;1ifeXUHz@Bh`=Xk=9(6=3^4Tp9y*q`Da zU(5_>H5eH5Do_wxm{pwe94??i6R^WJO9S!DX4isPdS_n7gF7QTZT>;S^?SydC zl0J_=tBeD4GXq)y1_!+oLh||pur9RDPsuUYfMJoLKcZvM7JvyE0EI#K1^oc%6g)J1 zn39mrNiplcNu-&%o#WuT%z&n)tfCb33x8@*9n5whD2u{OHFzXVYO)e+lgJEcG8v%PfWuWU7 znxz6TE%Wr_98ewZ)gZ7nv=0UE1-&mg9epF5Wv%owO(L6o=p*pme`#ks4bBTzbPDtg z{nvr_qYOJ~oI_$^o9|_tUlzPE11N70~T_%{QWp01+ zXMw}ogdJG}PRB{R9yUuaOv^1$YhQpx(F6HIP-kTPL~xt+>ZA&jat0J0f53<+ZtVih zk)IMqhnQ}qcWhN^t+_^No@=0l<=~Wl(U0W&fE~e+W?eELa*o}gLXS}Q4J!e>sgFeC z1I6WX<=}{}(at^S1c2=xcbP<^1BKGZ3GkFr5Df-`n{1%OR0A%&+(xqP(z-4PZQW&7pW_Lli zX7fx{8n>0IF-Mb2VAvK__XbF5Fo3MxW+u2HOUuBtWU(zA@Q-mlP(Hf19N zHlR18U6m8aXc{>~JmF_xmj}X_OK2VmkaEYc=OSJ0B0AN@ttIL-SYd?*bxPv;kNO9W z8z22WW5~eB7JR__LL@rS762gPr)nZyspISeb#gVX#=cJ`Qn-8E&4?Y$0=oqe; z@n1vyUVZp&uwpF^GUaXDu1pRpT=zwEb7^$aZ zq{EOh8>ImZS{;_~ysWgxBfSRAqXEjJNy{U>H6BpnATst?8J7c$exrwR3`%(ust5w8LrB!cqOObGMo-c-GiD) z{~8}m27Cz{BlQ}76{#9@MWz?oU{N^I(-;>|J1bH@!Te;vDy)yx9PEr#9sU@pM{z0i z>?Pz^3to!U7JNDxupFDh8N=SNsrXBzCSow;Pa8fS661x)zLY=Vmu##|4Uq!|*e8%%?)OuB2A#c+Oda*dtZ`Oz1;Q{%Ot%db<%l%^*i5`Dg=Mn{Q>|5me&D=m;V6dHwx!pp(gPF0000 literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_fingerprint_white_48dp.png b/res/drawable-xhdpi/ic_fingerprint_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8793f6a6a76e9c5142785ca60c78841c9a16d6e4 GIT binary patch literal 2483 zcmV;k2~75hP)AK&T7_tSzos5sIuptE_eMRH@p&W?i*dgZNP0>NZPpYPE=MWwyYI zG8ES}4sTNC=RGf(dze@1Nc0b*|UBh$QDE=8t^Nf4}>_ zujG4Q*YD}N?k@SElTJG6q?1mh3^Uzq|7@+-m8nt>>eYDL8~(#Gce~noQo7{pmybaT zj4|6<2c0a3tu@-iPJ~PoeZ@Qsf6It-GS%X z^ca0ix6}b4`xGl^YXKj!StgC1H(gOHo&R!c)XU@*A8k7UDYplS|6Pj=$vKXEz_--| z-ZMwnwh(ZJ=cQ3^k%4)O=4Y4}yqXsc%(sA#*d>h(hBtHX0H1b?xt3a`#A?edaE~we zm{Xg1>TJ(Qquj^xDd5jlOQ`YXc;q(FOskYTF-?AHfzO^WXkMv8!ZFkHCE!YR5`JM= z!c=R?3%>Lo1GE$jMO=Bv>fN)<=O>*`?%O=jdO;4;T0 z%#q59=MSqz$WgCa?i+4$jVpc547dA^XYJI8kaAy)EXbJ}0FAEBMZic$08M6Q1=Z_R z2zkZVjde;h32ch__6T{|sH{7$b{tS^LTd$_=?@Z$v)1a5W%9NM3~50L_=LJ3_J2)5 zmghA9DxA|A0bOmB@b|)NpR|`Wd(70W6$vbi{hRa`-Z3L1bQHB*z#IvSg{$-i^%9Pm zqkF3p*kCJ2^IIPhUcX<${FVwx`6FP9!r;o+N%N8q=A4Buw@*T)aBnE(-vCEjE}$G# z8Wh-POK9?duHvpb)jTD3D09dmWp*er&oq6a_xJaLghMV2uAOc-*xB+WbctVhRoK4Y zB;j3GMO*nVS+4zf#)V8Qb|3oF=s0opruvbgi>$6X(deCVn(GNeQR>C1;gc-gP@LWCxECl>fnA9(HNW!Z@^l1J!+-njJhMT#* zDB%z~6bxSrd#0F8zjt=iZ2&Kovmj) zRr;2Mb;0waBuvPMfL^KrvjY1p35%q%6592iwLimImz$&@OTwcPz8qXB0sLz|1Y8TK z)IA8DRe;T5{qGcitwM~qOv^gu+>&8)U2ISRNMM zhB*p&AefudU}ka<&{b7rJ)Y_a;I82Ap<%bB&fE42*%bPOy8%^!eKz3fTm_5-R0j*C z81Rmsfy@3z!V%v!=Kr7RWr`mWFBJ5400{OVJ_^w1Jg598PuC2(k|OA;(OQRCg8cq z^iBlq3hrD9_*FCg@%d^2cLZ{s)a-cw2>8#y zx*7I49z) z#n~lT5cZ0^krm5RDkJ`L3t&l5=ceZ>7Xg((5LMPh-0L>Lg2=3F4n}+fFgCJWRz~J( zdu{>_0KwkGGZ6tdNAmSpz_wt-t$@iHwhyWUw?>|RB{u=(fO9i!Pes!Fb%5o;c^dGC zV8o}Ro!tbB6-z~4$?Dt$yal)@!}d>ruV&b$L|m??CeRpcq2YfCP3d1g86X2n! z%T^kcb^ci7oGk#oX zUATMNKHtp!81P6C?wTU4&S@$EE5mo}*AhyNY;OVA1Ky0v+AJ06)`V_ouwA4*=Ue); zr-0rLgXaV5CnS6(ST3&ts>4H5XZfWxsyyN%srLHpa=hm~=>wtFPXWIT+V~M_(*;ET zFsFD*NVzrcbAu~fZkp?S&0|)$xV^p`9jOWMjreJfZB7rL@t5{_FRJuuuMb}z1JniE zPbrT}_-_NU7UVq+Xr2-6C7`eUfbBt&DzsL@?;=HWch~xNN5$EqpdAFHmqfynz~0k~ z5~|~cfg)qg^01X&u)#WOeBWKBDQYht2zy*^2@g5^L>epd`NevE1@!SdK!Yp7;|oir zaoAmYbsz!3R`?M>txpA4Qf^TvjRU^v?2ad(i>Vp_N3#w`FSK1IO*XmJ;Ep69{JLsa z*0n-+IxK{j^y^3hy0{&1F#d6|Z#pPsZpRYP#U1K3n0dnMJr&z3jR!l3fS{ay?;>ER xJ8g8#CjC2}fcF1&3g{HjDWFq8r-0mk{|5pyK~$$JaistN002ovPDHLkV1n#}vg`l= literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_fingerprint_white_48dp.png b/res/drawable-xxhdpi/ic_fingerprint_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b7e20cddcf1abbd90b3f80565ec244ff7ac65bc4 GIT binary patch literal 3765 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 + + + + + + \ 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); }