diff --git a/build.gradle b/build.gradle index 189dce39f1..de44615fb1 100644 --- a/build.gradle +++ b/build.gradle @@ -74,7 +74,7 @@ dependencies { compile('org.whispersystems:libpastelog:1.1.1') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } - compile 'org.whispersystems:signal-service-android:2.7.1' + compile 'org.whispersystems:signal-service-android:2.7.2' compile 'org.whispersystems:webrtc-android:M64' compile "me.leolin:ShortcutBadger:1.1.16" @@ -93,9 +93,7 @@ dependencies { compile 'com.google.zxing:android-integration:3.1.0' compile 'com.squareup.dagger:dagger:1.2.2' annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.2' - compile ("com.doomonafireball.betterpickers:library:1.5.3") { - exclude group: 'com.android.support', module: 'support-v4' - } + compile 'com.code-troopers.betterpickers:library:3.1.0' provided 'com.squareup.dagger:dagger-compiler:1.2.2' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'com.google.zxing:core:3.2.1' @@ -149,6 +147,7 @@ dependencyVerification { 'com.android.support:preference-v7:d9f32ddc92f8d6a0bd86a18f4fcaff805fa021245e395b6e7dd382907443ee53', 'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54', 'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c', + 'com.code-troopers.betterpickers:library:ab5cb3f00bd9c575461300b38f31f5874d522524b10bbaf75f3f05a4b6dbf5fd', 'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794', 'com.android.support:appcompat-v7:b2825e8b47f665d3362d8481c8d147d1af9230d16f23a2b94f6ccbc53c68cec1', 'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263', @@ -163,7 +162,7 @@ dependencyVerification { 'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718', 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:libpastelog:9bdbc598c7d2c20aec8a2a480ecfd83f9d85f9f7fb5c3128810c6aae03969e33', - 'org.whispersystems:signal-service-android:e639df8c3ea76f27b3eb8784a0b0fb365fbf833fe7f196a1abc13d961ac642ca', + 'org.whispersystems:signal-service-android:a7dfcb2f88ec69e8a1d31215cc7b67f0db50a96cd9d3832bfe75f56e67188537', 'org.whispersystems:webrtc-android:ed297e8b795dad9658cf306c2aa0f7d296c65f0997a2ac4353fd0157910acc12', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', @@ -176,7 +175,6 @@ dependencyVerification { 'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177', 'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4', 'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883', - 'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'com.davemorrissey.labs:subsampling-scale-image-view:550c5baa07e0bb4ff0a18b705e96d34436d22619248bd8c08c08c730b1f55cfe', @@ -203,7 +201,7 @@ dependencyVerification { 'com.github.bumptech.glide:gifdecoder:59ccf3bb0cec11dab4b857382cbe0b171111b6fc62bf141adce4e1180889af15', 'com.android.support:support-annotations:af05330d997eb92a066534dbe0a3ea24347d26d7001221092113ae02a8f233da', 'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1', - 'org.whispersystems:signal-service-java:49dbd3aeb84ed9640355791d78e0825d4ae88e6120d24cb626ec416360c6975d', + 'org.whispersystems:signal-service-java:f5ca4595eb09e25b9c9fd39c83bdcf1978a61d8a4b6f770bb548f3dd40ecc493', 'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b', 'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', diff --git a/res/drawable-hdpi/ic_lock_white_48dp.png b/res/drawable-hdpi/ic_lock_white_48dp.png new file mode 100644 index 0000000000..0e52c7c75e Binary files /dev/null and b/res/drawable-hdpi/ic_lock_white_48dp.png differ diff --git a/res/drawable-mdpi/ic_lock_white_48dp.png b/res/drawable-mdpi/ic_lock_white_48dp.png new file mode 100644 index 0000000000..ad8d91a99f Binary files /dev/null and b/res/drawable-mdpi/ic_lock_white_48dp.png differ diff --git a/res/drawable-xhdpi/ic_lock_white_48dp.png b/res/drawable-xhdpi/ic_lock_white_48dp.png new file mode 100644 index 0000000000..a55147be1c Binary files /dev/null and b/res/drawable-xhdpi/ic_lock_white_48dp.png differ diff --git a/res/drawable-xxhdpi/ic_lock_white_48dp.png b/res/drawable-xxhdpi/ic_lock_white_48dp.png new file mode 100644 index 0000000000..2fa030271f Binary files /dev/null and b/res/drawable-xxhdpi/ic_lock_white_48dp.png differ diff --git a/res/drawable-xxxhdpi/ic_lock_white_48dp.png b/res/drawable-xxxhdpi/ic_lock_white_48dp.png new file mode 100644 index 0000000000..d94c2d7f94 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_lock_white_48dp.png differ diff --git a/res/layout/registration_activity.xml b/res/layout/registration_activity.xml index dfdac7b622..7c60432138 100644 --- a/res/layout/registration_activity.xml +++ b/res/layout/registration_activity.xml @@ -63,7 +63,7 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" - tools:visibility="visible"> + tools:visibility="invisible"> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/registration_lock_dialog_view.xml b/res/layout/registration_lock_dialog_view.xml new file mode 100644 index 0000000000..eb93f6642d --- /dev/null +++ b/res/layout/registration_lock_dialog_view.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/registration_lock_reminder_view.xml b/res/layout/registration_lock_reminder_view.xml new file mode 100644 index 0000000000..c36b560972 --- /dev/null +++ b/res/layout/registration_lock_reminder_view.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/registration_unlock_dialog_view.xml b/res/layout/registration_unlock_dialog_view.xml new file mode 100644 index 0000000000..f261f63d17 --- /dev/null +++ b/res/layout/registration_unlock_dialog_view.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/verification_pin_keyboard_view.xml b/res/layout/verification_pin_keyboard_view.xml index 2cb45fa365..c02ef34475 100644 --- a/res/layout/verification_pin_keyboard_view.xml +++ b/res/layout/verification_pin_keyboard_view.xml @@ -1,5 +1,6 @@ - + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 15bbcb2210..65260f45a0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -39,7 +39,7 @@ off Off SMS %1$s, MMS %2$s - Screen lock %1$s, Screen security %2$s + Screen lock %1$s, Registration lock %2$s Theme %1$s, Language %2$s @@ -1365,8 +1365,37 @@ Lock Signal access with Android screen lock or fingerprint Screen lock inactivity timeout None - - + The Registration Lock PIN is not the same as the SMS verification code you just received. Please enter the PIN you previously configured in the application. + Registration Lock PIN + Forgot PIN? + The PIN can consist of four or more digits. If you forget your PIN, you could be locked out of your account for up to seven days. + Enter PIN + Confirm PIN + Enter your Registration Lock PIN + Enter PIN + Enable a Registration Lock PIN that will be required to register this phone number with Signal again. + Registration Lock PIN + Registration Lock + You must enter your Registration Lock PIN + Incorrect Registration Lock PIN + Too many attempts + You\'ve made too many incorrect Registration Lock PIN attempts. Please try again in a day. + Error connecting to service + Oh no! + Registration of this phone number will be possible without your Registration Lock PIN after 7 days have passed since this phone number was last active on Signal. You have %d days remaining. + Registration lock PIN + This phone number has Registration Lock enabled. Please enter the Registration Lock PIN. + Registration Lock is enabled for your phone number. + I forgot my PIN. + Forgotten PIN? + Registration Lock helps protect your phone number from unauthorized registration attempts. This feature can be disabled at any time in your Signal privacy settings + Registration Lock + Enable + The Registration Lock PIN must be at least 4 digits. + The two PINs you entered do not match. + Error connecting to the service + Disable Registration Lock PIN? + Disable diff --git a/res/xml/preferences_app_protection.xml b/res/xml/preferences_app_protection.xml index 36f541de89..466b4cac41 100644 --- a/res/xml/preferences_app_protection.xml +++ b/res/xml/preferences_app_protection.xml @@ -67,4 +67,15 @@ android:title="@string/preferences_app_protection__blocked_contacts" /> + + + + + + + diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 5459a5639a..2dbfba3039 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.components.RatingManager; import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; +import org.thoughtcrime.securesms.lock.RegistrationLockDialog; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.permissions.Permissions; @@ -80,6 +81,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit initializeSearchListener(); RatingManager.showRatingDialogIfNecessary(this); + RegistrationLockDialog.showReminderIfNecessary(this); } @Override diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index 287d1323db..12ac550aa9 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -84,11 +84,14 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; +import org.whispersystems.signalservice.internal.push.LockedException; import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.concurrent.TimeUnit; /** * The register account activity. Prompts ths user for their registration information @@ -127,6 +130,12 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif private TextView restoreBackupProgress; private CircularProgressButton restoreButton; + private View pinContainer; + private EditText pin; + private CircularProgressButton pinButton; + private TextView pinForgotButton; + private View pinClarificationContainer; + private CallMeCountDownView callMeCountDownView; private VerificationPinKeyboard keyboard; private VerificationCodeView verificationCodeView; @@ -196,6 +205,12 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif this.restoreBackupProgress = findViewById(R.id.backup_progress_text); this.restoreButton = findViewById(R.id.restore_button); + this.pinContainer = findViewById(R.id.pin_container); + this.pin = findViewById(R.id.pin); + this.pinButton = findViewById(R.id.pinButton); + this.pinForgotButton = findViewById(R.id.forgot_button); + this.pinClarificationContainer = findViewById(R.id.pin_clarification_container); + this.registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, null); this.countryCode.addTextChangedListener(new CountryCodeChangedListener()); @@ -502,72 +517,36 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif callMeCountDownView.setVisibility(View.INVISIBLE); keyboard.displayProgress(); - new AsyncTask() { + new AsyncTask>() { @Override - protected Boolean doInBackground(Void... voids) { + protected Pair doInBackground(Void... voids) { try { - int registrationId = KeyHelper.generateRegistrationId(false); - TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId); - SessionUtil.archiveAllSessions(RegistrationActivity.this); - - String signalingKey = Util.getSecret(52); - - accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent()); - - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this); - List records = PreKeyUtil.generatePreKeys(RegistrationActivity.this); - SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(RegistrationActivity.this, identityKey, true); - - accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records); - - if (registrationState.gcmToken.isPresent()) { - accountManager.setGcmId(registrationState.gcmToken); - } - - TextSecurePreferences.setGcmRegistrationId(RegistrationActivity.this, registrationState.gcmToken.orNull()); - TextSecurePreferences.setGcmDisabled(RegistrationActivity.this, !registrationState.gcmToken.isPresent()); - TextSecurePreferences.setWebsocketRegistered(RegistrationActivity.this, true); - - DatabaseFactory.getIdentityDatabase(RegistrationActivity.this) - .saveIdentity(Address.fromSerialized(registrationState.e164number), - identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, - true, System.currentTimeMillis(), true); - - TextSecurePreferences.setVerifying(RegistrationActivity.this, false); - TextSecurePreferences.setPushRegistered(RegistrationActivity.this, true); - TextSecurePreferences.setLocalNumber(RegistrationActivity.this, registrationState.e164number); - TextSecurePreferences.setPushServerPassword(RegistrationActivity.this, registrationState.password); - TextSecurePreferences.setSignalingKey(RegistrationActivity.this, signalingKey); - TextSecurePreferences.setSignedPreKeyRegistered(RegistrationActivity.this, true); - TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true); - TextSecurePreferences.setUnauthorizedReceived(RegistrationActivity.this, false); - - return true; + verifyAccount(code, null); + return new Pair<>(1, -1L); + } catch (LockedException e) { + Log.w(TAG, e); + return new Pair<>(2, e.getTimeRemaining()); } catch (IOException e) { Log.w(TAG, e); - return false; + return new Pair<>(3, -1L); } } @Override - protected void onPostExecute(Boolean result) { - if (result) { + protected void onPostExecute(Pair result) { + if (result.first == 1) { keyboard.displaySuccess().addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean result) { - ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(RegistrationActivity.this, false)); - - DirectoryRefreshListener.schedule(RegistrationActivity.this); - RotateSignedPreKeyListener.schedule(RegistrationActivity.this); - - Intent nextIntent = getIntent().getParcelableExtra("next_intent"); - - if (nextIntent == null) { - nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class); - } - - startActivity(nextIntent); - finish(); + handleSuccessfulRegistration(); + } + }); + } else if (result.first == 2) { + keyboard.displayLocked().addListener(new AssertedSuccessListener() { + @Override + public void onSuccess(Boolean r) { + registrationState = new RegistrationState(RegistrationState.State.PIN, registrationState); + displayPinView(code, result.second); } }); } else { @@ -585,6 +564,67 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + @SuppressLint("StaticFieldLeak") + private void handleVerifyWithPinClicked(@NonNull String code, @Nullable String pin) { + if (TextUtils.isEmpty(pin) || TextUtils.isEmpty(pin.replace(" ", ""))) { + Toast.makeText(this, R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show(); + return; + } + + pinButton.setIndeterminateProgressMode(true); + pinButton.setProgress(50); + + new AsyncTask() { + @Override + protected Integer doInBackground(Void... voids) { + try { + verifyAccount(code, pin); + return 1; + } catch (LockedException e) { + Log.w(TAG, e); + return 2; + } catch (RateLimitException e) { + Log.w(TAG, e); + return 3; + } catch (IOException e) { + Log.w(TAG, e); + return 4; + } + } + + @Override + protected void onPostExecute(Integer result) { + pinButton.setIndeterminateProgressMode(false); + pinButton.setProgress(0); + + if (result == 1) { + TextSecurePreferences.setRegistrationLockPin(RegistrationActivity.this, pin); + TextSecurePreferences.setRegistrationtLockEnabled(RegistrationActivity.this, true); + handleSuccessfulRegistration(); + } else if (result == 2) { + RegistrationActivity.this.pin.setText(""); + Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_incorrect_registration_lock_pin, Toast.LENGTH_LONG).show(); + } else if (result == 3) { + new AlertDialog.Builder(RegistrationActivity.this) + .setTitle(R.string.RegistrationActivity_too_many_attempts) + .setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day) + .setPositiveButton(android.R.string.ok, null) + .show(); + } else if (result == 4) { + Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show(); + } + } + }.execute(); + } + + private void handleForgottenPin(long timeRemaining) { + new AlertDialog.Builder(RegistrationActivity.this) + .setTitle(R.string.RegistrationActivity_oh_no) + .setMessage(getString(R.string.RegistrationActivity_registration_of_this_phone_number_will_be_possible_without_your_registration_lock_pin_after_seven_days_have_passed, (TimeUnit.MILLISECONDS.toDays(timeRemaining) + 1))) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + @SuppressLint("StaticFieldLeak") private void handlePhoneCallRequest() { if (registrationState.state == RegistrationState.State.VERIFYING) { @@ -605,6 +645,60 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif } } + private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException { + int registrationId = KeyHelper.generateRegistrationId(false); + TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId); + SessionUtil.archiveAllSessions(RegistrationActivity.this); + + String signalingKey = Util.getSecret(52); + + accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin); + + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this); + List records = PreKeyUtil.generatePreKeys(RegistrationActivity.this); + SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(RegistrationActivity.this, identityKey, true); + + accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records); + + if (registrationState.gcmToken.isPresent()) { + accountManager.setGcmId(registrationState.gcmToken); + } + + TextSecurePreferences.setGcmRegistrationId(RegistrationActivity.this, registrationState.gcmToken.orNull()); + TextSecurePreferences.setGcmDisabled(RegistrationActivity.this, !registrationState.gcmToken.isPresent()); + TextSecurePreferences.setWebsocketRegistered(RegistrationActivity.this, true); + + DatabaseFactory.getIdentityDatabase(RegistrationActivity.this) + .saveIdentity(Address.fromSerialized(registrationState.e164number), + identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, + true, System.currentTimeMillis(), true); + + TextSecurePreferences.setVerifying(RegistrationActivity.this, false); + TextSecurePreferences.setPushRegistered(RegistrationActivity.this, true); + TextSecurePreferences.setLocalNumber(RegistrationActivity.this, registrationState.e164number); + TextSecurePreferences.setPushServerPassword(RegistrationActivity.this, registrationState.password); + TextSecurePreferences.setSignalingKey(RegistrationActivity.this, signalingKey); + TextSecurePreferences.setSignedPreKeyRegistered(RegistrationActivity.this, true); + TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true); + TextSecurePreferences.setUnauthorizedReceived(RegistrationActivity.this, false); + } + + private void handleSuccessfulRegistration() { + ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(RegistrationActivity.this, false)); + + DirectoryRefreshListener.schedule(RegistrationActivity.this); + RotateSignedPreKeyListener.schedule(RegistrationActivity.this); + + Intent nextIntent = getIntent().getParcelableExtra("next_intent"); + + if (nextIntent == null) { + nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class); + } + + startActivity(nextIntent); + finish(); + } + private void displayRestoreView(@NonNull BackupUtil.BackupInfo backup) { title.animate().translationX(title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() { @Override @@ -782,6 +876,64 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif this.callMeCountDownView.startCountDown(callCountdown); } + private void displayPinView(String code, long lockedUntil) { + title.animate().translationX(-1 * title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() { + @Override + public void onAnimationEnd(Animator animation) { + title.setText(R.string.RegistrationActivity_registration_lock_pin); + title.clearAnimation(); + title.setTranslationX(title.getWidth()); + title.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start(); + } + }).start(); + + subtitle.animate().translationX(-1 * subtitle.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() { + @Override + public void onAnimationEnd(Animator animation) { + subtitle.setText(R.string.RegistrationActivity_this_phone_number_has_registration_lock_enabled_please_enter_the_registration_lock_pin); + subtitle.clearAnimation(); + subtitle.setTranslationX(subtitle.getWidth()); + subtitle.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start(); + } + }).start(); + + verificationContainer.animate().translationX(-1 * verificationContainer.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() { + @Override + public void onAnimationEnd(Animator animation) { + verificationContainer.clearAnimation(); + verificationContainer.setVisibility(View.INVISIBLE); + verificationContainer.setTranslationX(0); + + pinContainer.setTranslationX(pinContainer.getWidth()); + pinContainer.setVisibility(View.VISIBLE); + pinContainer.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start(); + } + }).start(); + + fab.animate().rotationBy(-360f).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() { + @Override + public void onAnimationEnd(Animator animation) { + fab.clearAnimation(); + fab.setImageResource(R.drawable.ic_lock_white_24dp); + fab.animate().rotationBy(-360f).setDuration(SCENE_TRANSITION_DURATION).setListener(null).start(); + } + }).start(); + + pinButton.setOnClickListener(v -> handleVerifyWithPinClicked(code, pin.getText().toString())); + pinForgotButton.setOnClickListener(v -> handleForgottenPin(lockedUntil)); + pin.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + @Override + public void afterTextChanged(Editable s) { + if (s != null && code.equals(s.toString())) pinClarificationContainer.setVisibility(View.VISIBLE); + else if (pinClarificationContainer.getVisibility() == View.VISIBLE) pinClarificationContainer.setVisibility(View.GONE); + } + }); + } + private void handleCancel() { TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true); Intent nextIntent = getIntent().getParcelableExtra("next_intent"); @@ -916,7 +1068,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif private static class RegistrationState { private enum State { - INITIAL, VERIFYING, CHECKING + INITIAL, VERIFYING, CHECKING, PIN } private final State state; diff --git a/src/org/thoughtcrime/securesms/components/registration/VerificationPinKeyboard.java b/src/org/thoughtcrime/securesms/components/registration/VerificationPinKeyboard.java index b22296e6fe..3b37034288 100644 --- a/src/org/thoughtcrime/securesms/components/registration/VerificationPinKeyboard.java +++ b/src/org/thoughtcrime/securesms/components/registration/VerificationPinKeyboard.java @@ -30,6 +30,7 @@ public class VerificationPinKeyboard extends FrameLayout { private ProgressBar progressBar; private ImageView successView; private ImageView failureView; + private ImageView lockedView; private OnKeyPressListener listener; @@ -60,7 +61,8 @@ public class VerificationPinKeyboard extends FrameLayout { this.keyboardView = findViewById(R.id.keyboard_view); this.progressBar = findViewById(R.id.progress); this.successView = findViewById(R.id.success); - this.failureView = findViewById(R.id.failure); ; + this.failureView = findViewById(R.id.failure); + this.lockedView = findViewById(R.id.locked); keyboardView.setPreviewEnabled(false); keyboardView.setKeyboard(new Keyboard(getContext(), R.xml.pin_keyboard)); @@ -97,6 +99,7 @@ public class VerificationPinKeyboard extends FrameLayout { this.progressBar.setVisibility(View.GONE); this.successView.setVisibility(View.GONE); this.failureView.setVisibility(View.GONE); + this.lockedView.setVisibility(View.GONE); } public void displayProgress() { @@ -104,6 +107,7 @@ public class VerificationPinKeyboard extends FrameLayout { this.progressBar.setVisibility(View.VISIBLE); this.successView.setVisibility(View.GONE); this.failureView.setVisibility(View.GONE); + this.lockedView.setVisibility(View.GONE); } public ListenableFuture displaySuccess() { @@ -112,6 +116,7 @@ public class VerificationPinKeyboard extends FrameLayout { this.keyboardView.setVisibility(View.INVISIBLE); this.progressBar.setVisibility(View.GONE); this.failureView.setVisibility(View.GONE); + this.lockedView.setVisibility(View.GONE); this.successView.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN); @@ -143,6 +148,7 @@ public class VerificationPinKeyboard extends FrameLayout { this.keyboardView.setVisibility(View.INVISIBLE); this.progressBar.setVisibility(View.GONE); this.failureView.setVisibility(View.GONE); + this.lockedView.setVisibility(View.GONE); this.failureView.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN); this.failureView.setVisibility(View.VISIBLE); @@ -168,6 +174,38 @@ public class VerificationPinKeyboard extends FrameLayout { return result; } + public ListenableFuture displayLocked() { + SettableFuture result = new SettableFuture<>(); + + this.keyboardView.setVisibility(View.INVISIBLE); + this.progressBar.setVisibility(View.GONE); + this.failureView.setVisibility(View.GONE); + this.lockedView.setVisibility(View.GONE); + + this.lockedView.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN); + + ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, + ScaleAnimation.RELATIVE_TO_SELF, 0.5f, + ScaleAnimation.RELATIVE_TO_SELF, 0.5f); + scaleAnimation.setInterpolator(new OvershootInterpolator()); + scaleAnimation.setDuration(800); + scaleAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + + @Override + public void onAnimationEnd(Animation animation) { + result.set(true); + } + + @Override + public void onAnimationRepeat(Animation animation) {} + }); + + ViewUtil.animateIn(this.lockedView, scaleAnimation); + return result; + } + public interface OnKeyPressListener { void onKeyPress(int keyCode); } diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index cc4d71d4f6..208e372490 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -33,6 +33,8 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; +import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; +import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment; import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.service.MessageRetrievalService; @@ -75,7 +77,8 @@ import dagger.Provides; RetrieveProfileAvatarJob.class, MultiDeviceProfileKeyUpdateJob.class, SendReadReceiptJob.class, - MultiDeviceReadReceiptUpdateJob.class}) + MultiDeviceReadReceiptUpdateJob.class, + AppProtectionPreferenceFragment.class}) public class SignalCommunicationModule { private static final String TAG = SignalCommunicationModule.class.getSimpleName(); diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index 0ef701b10c..e1689c1b4a 100644 --- a/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -37,11 +37,12 @@ public class RefreshAttributesJob extends ContextJob implements InjectableType { @Override public void onRun() throws IOException { - String signalingKey = TextSecurePreferences.getSignalingKey(context); - int registrationId = TextSecurePreferences.getLocalRegistrationId(context); - boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context); + String signalingKey = TextSecurePreferences.getSignalingKey(context); + int registrationId = TextSecurePreferences.getLocalRegistrationId(context); + boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context); + String pin = TextSecurePreferences.getRegistrationLockPin(context); - signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages); + signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin); } @Override diff --git a/src/org/thoughtcrime/securesms/lock/RegistrationLockDialog.java b/src/org/thoughtcrime/securesms/lock/RegistrationLockDialog.java new file mode 100644 index 0000000000..16d4eef1a0 --- /dev/null +++ b/src/org/thoughtcrime/securesms/lock/RegistrationLockDialog.java @@ -0,0 +1,231 @@ +package org.thoughtcrime.securesms.lock; + + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; +import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalServiceAccountManager; + +import java.io.IOException; + +public class RegistrationLockDialog { + + private static final String TAG = RegistrationLockDialog.class.getSimpleName(); + + public static void showReminderIfNecessary(@NonNull Context context) { + if (!RegistrationLockReminders.needsReminder(context)) return; + + AlertDialog dialog = new AlertDialog.Builder(context, R.style.RationaleDialog) + .setView(R.layout.registration_lock_reminder_view) + .setCancelable(true) + .setOnCancelListener(d -> RegistrationLockReminders.scheduleReminder(context, false)) + .create(); + + WindowManager windowManager = ServiceUtil.getWindowManager(context); + Display display = windowManager.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.getWindow().setLayout((int)(metrics.widthPixels * .75), ViewGroup.LayoutParams.WRAP_CONTENT); + dialog.show(); + + EditText pinEditText = dialog.findViewById(R.id.pin); + TextView reminder = dialog.findViewById(R.id.reminder); + + assert pinEditText != null; + assert reminder != null; + + SpannableString reminderText = new SpannableString(context.getString(R.string.RegistrationLockDialog_registration_lock_is_enabled_for_your_phone_number)); + SpannableString forgotText = new SpannableString(context.getString(R.string.RegistrationLockDialog_i_forgot_my_pin)); + + ClickableSpan clickableSpan = new ClickableSpan() { + @Override + public void onClick(View widget) { + dialog.dismiss(); + new AlertDialog.Builder(context).setTitle(R.string.RegistrationLockDialog_forgotten_pin) + .setMessage(R.string.RegistrationLockDialog_registration_lock_helps_protect_your_phone_number_from_unauthorized_registration_attempts) + .setPositiveButton(android.R.string.ok, null) + .create() + .show(); + } + }; + + forgotText.setSpan(clickableSpan, 0, forgotText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + reminder.setText(new SpannableStringBuilder(reminderText).append(" ").append(forgotText)); + reminder.setMovementMethod(LinkMovementMethod.getInstance()); + + pinEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if (s != null && s.toString().replace(" ", "").equals(TextSecurePreferences.getRegistrationLockPin(context))) { + dialog.dismiss(); + RegistrationLockReminders.scheduleReminder(context, true); + } + } + }); + + } + + @SuppressLint("StaticFieldLeak") + public static void showRegistrationLockPrompt(@NonNull Context context, @NonNull SwitchPreferenceCompat preference, @NonNull SignalServiceAccountManager accountManager) { + AlertDialog dialog = new AlertDialog.Builder(context) + .setTitle(R.string.RegistrationLockDialog_registration_lock) + .setView(R.layout.registration_lock_dialog_view) + .setPositiveButton(R.string.RegistrationLockDialog_enable, null) + .setNegativeButton(android.R.string.cancel, null) + .create(); + + dialog.setOnShowListener(created -> { + Button button = ((AlertDialog) created).getButton(AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(v -> { + EditText pin = dialog.findViewById(R.id.pin); + EditText repeat = dialog.findViewById(R.id.repeat); + ProgressBar progressBar = dialog.findViewById(R.id.progress); + + assert pin != null; + assert repeat != null; + assert progressBar != null; + + String pinValue = pin.getText().toString().replace(" ", ""); + String repeatValue = repeat.getText().toString().replace(" ", ""); + + if (pinValue.length() < 4) { + Toast.makeText(context, R.string.RegistrationLockDialog_the_registration_lock_pin_must_be_at_least_four_digits, Toast.LENGTH_LONG).show(); + return; + } + + if (!pinValue.equals(repeatValue)) { + Toast.makeText(context, R.string.RegistrationLockDialog_the_two_pins_you_entered_do_not_match, Toast.LENGTH_LONG).show(); + return; + } + + new AsyncTask() { + @Override + protected void onPreExecute() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setIndeterminate(true); + button.setEnabled(false); + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + accountManager.setPin(Optional.of(pinValue)); + TextSecurePreferences.setRegistrationLockPin(context, pinValue); + TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis()); + TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL); + return true; + } catch (IOException e) { + Log.w(TAG, e); + return false; + } + } + + @Override + protected void onPostExecute(@NonNull Boolean result) { + button.setEnabled(true); + progressBar.setVisibility(View.GONE); + + if (result) { + preference.setChecked(true); + created.dismiss(); + } else { + Toast.makeText(context, R.string.RegistrationLockDialog_error_connecting_to_the_service, Toast.LENGTH_LONG).show(); + } + } + }.execute(); + }); + }); + + dialog.show(); + } + + @SuppressLint("StaticFieldLeak") + public static void showRegistrationUnlockPrompt(@NonNull Context context, @NonNull SwitchPreferenceCompat preference, @NonNull SignalServiceAccountManager accountManager) { + AlertDialog dialog = new AlertDialog.Builder(context) + .setTitle(R.string.RegistrationLockDialog_disable_registration_lock_pin) + .setView(R.layout.registration_unlock_dialog_view) + .setPositiveButton(R.string.RegistrationLockDialog_disable, null) + .setNegativeButton(android.R.string.cancel, null) + .create(); + + dialog.setOnShowListener(created -> { + Button button = ((AlertDialog) created).getButton(AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(v -> { + ProgressBar progressBar = dialog.findViewById(R.id.progress); + assert progressBar != null; + + new AsyncTask() { + @Override + protected void onPreExecute() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setIndeterminate(true); + button.setEnabled(false); + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + accountManager.setPin(Optional.absent()); + return true; + } catch (IOException e) { + Log.w(TAG, e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + progressBar.setVisibility(View.GONE); + button.setEnabled(true); + + if (result) { + preference.setChecked(false); + created.dismiss(); + } else { + Toast.makeText(context, R.string.RegistrationLockDialog_error_connecting_to_the_service, Toast.LENGTH_LONG).show(); + } + } + }.execute(); + }); + }); + + dialog.show(); + } + +} diff --git a/src/org/thoughtcrime/securesms/lock/RegistrationLockReminders.java b/src/org/thoughtcrime/securesms/lock/RegistrationLockReminders.java new file mode 100644 index 0000000000..d5e265a29a --- /dev/null +++ b/src/org/thoughtcrime/securesms/lock/RegistrationLockReminders.java @@ -0,0 +1,54 @@ +package org.thoughtcrime.securesms.lock; + + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class RegistrationLockReminders { + + public static final long INITIAL_INTERVAL = TimeUnit.HOURS.toMillis(6); + + private static Map INTERVAL_PROGRESSION = new HashMap() {{ + put(TimeUnit.HOURS.toMillis(6), TimeUnit.HOURS.toMillis(12)); + put(TimeUnit.HOURS.toMillis(12), TimeUnit.DAYS.toMillis(1)); + put(TimeUnit.DAYS.toMillis(1), TimeUnit.DAYS.toMillis(3)); + put(TimeUnit.DAYS.toMillis(3), TimeUnit.DAYS.toMillis(7)); + put(TimeUnit.DAYS.toMillis(7), TimeUnit.DAYS.toMillis(7)); + }}; + + + private static Map INTERVAL_REGRESSION = new HashMap() {{ + put(TimeUnit.HOURS.toMillis(12), TimeUnit.HOURS.toMillis(6)); + put(TimeUnit.DAYS.toMillis(1), TimeUnit.HOURS.toMillis(12)); + put(TimeUnit.DAYS.toMillis(3), TimeUnit.DAYS.toMillis(1)); + put(TimeUnit.DAYS.toMillis(7), TimeUnit.DAYS.toMillis(3)); + }}; + + + public static boolean needsReminder(@NonNull Context context) { + if (!TextSecurePreferences.isRegistrationtLockEnabled(context)) return false; + + long lastReminderTime = TextSecurePreferences.getRegistrationLockLastReminderTime(context); + long nextIntervalTime = TextSecurePreferences.getRegistrationLockNextReminderInterval(context); + + return System.currentTimeMillis() > lastReminderTime + nextIntervalTime; + } + + public static void scheduleReminder(@NonNull Context context, boolean success) { + long lastReminderInterval = TextSecurePreferences.getRegistrationLockNextReminderInterval(context); + long nextReminderInterval; + + if (success) nextReminderInterval = INTERVAL_PROGRESSION.get(lastReminderInterval); + else nextReminderInterval = INTERVAL_REGRESSION.get(lastReminderInterval); + + TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis()); + TextSecurePreferences.setRegistrationLockNextReminderInterval(context, nextReminderInterval); + } + +} diff --git a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java index 92c321d4f3..45ec404a2f 100644 --- a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.preferences; import android.app.KeyguardManager; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -12,8 +13,8 @@ import android.support.v7.preference.CheckBoxPreference; import android.support.v7.preference.Preference; import android.widget.Toast; -import com.doomonafireball.betterpickers.hmspicker.HmsPickerBuilder; -import com.doomonafireball.betterpickers.hmspicker.HmsPickerDialogFragment; +import com.codetroopers.betterpickers.hmspicker.HmsPickerBuilder; +import com.codetroopers.betterpickers.hmspicker.HmsPickerDialogFragment; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; @@ -22,24 +23,39 @@ 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.dependencies.InjectableType; import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob; +import org.thoughtcrime.securesms.lock.RegistrationLockDialog; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.api.SignalServiceAccountManager; import java.util.concurrent.TimeUnit; -public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment { +import javax.inject.Inject; + +public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType { private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked"; private CheckBoxPreference disablePassphrase; + @Inject + SignalServiceAccountManager accountManager; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + ApplicationContext.getInstance(activity).injectDependencies(this); + } + @Override public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary"); + this.findPreference(TextSecurePreferences.REGISTRATION_LOCK_PREF).setOnPreferenceClickListener(new AccountLockClickListener()); this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener()); this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener()); @@ -116,7 +132,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment } } - private class ScreenLockTimeoutListener implements Preference.OnPreferenceClickListener, HmsPickerDialogFragment.HmsPickerDialogHandler { + private class ScreenLockTimeoutListener implements Preference.OnPreferenceClickListener, HmsPickerDialogFragment.HmsPickerDialogHandlerV2 { @Override public boolean onPreferenceClick(Preference preference) { @@ -132,18 +148,31 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment return true; } - + @Override - public void onDialogHmsSet(int reference, int hours, int minutes, int seconds) { + public void onDialogHmsSet(int reference, boolean isNegative, int hours, int minutes, int seconds) { long timeoutSeconds = Math.max(TimeUnit.HOURS.toSeconds(hours) + - TimeUnit.MINUTES.toSeconds(minutes) + - TimeUnit.SECONDS.toSeconds(seconds), 60); + TimeUnit.MINUTES.toSeconds(minutes) + + TimeUnit.SECONDS.toSeconds(seconds), 60); TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds); initializeScreenLockTimeoutSummary(); } } + private class AccountLockClickListener implements Preference.OnPreferenceClickListener { + @Override + public boolean onPreferenceClick(Preference preference) { + if (((SwitchPreferenceCompat)preference).isChecked()) { + RegistrationLockDialog.showRegistrationUnlockPrompt(getContext(), (SwitchPreferenceCompat)preference, accountManager); + } else { + RegistrationLockDialog.showRegistrationLockPrompt(getContext(), (SwitchPreferenceCompat)preference, accountManager); + } + + return true; + } + } + private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { @@ -153,6 +182,40 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment } } + private class ReadReceiptToggleListener implements Preference.OnPreferenceChangeListener { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean enabled = (boolean)newValue; + ApplicationContext.getInstance(getContext()) + .getJobManager() + .add(new MultiDeviceReadReceiptUpdateJob(getContext(), enabled)); + + return true; + } + } + + public static CharSequence getSummary(Context context) { + final int privacySummaryResId = R.string.ApplicationPreferencesActivity_privacy_summary; + final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on); + final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off); + + if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) { + if (TextSecurePreferences.isRegistrationtLockEnabled(context)) { + return context.getString(privacySummaryResId, offRes, onRes); + } else { + return context.getString(privacySummaryResId, offRes, offRes); + } + } else { + if (TextSecurePreferences.isRegistrationtLockEnabled(context)) { + return context.getString(privacySummaryResId, onRes, onRes); + } else { + return context.getString(privacySummaryResId, onRes, offRes); + } + } + } + + // Derecated + private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { @@ -160,15 +223,15 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment startActivity(new Intent(getActivity(), PassphraseChangeActivity.class)); } else { Toast.makeText(getActivity(), - R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet, - Toast.LENGTH_LONG).show(); + R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet, + Toast.LENGTH_LONG).show(); } return true; } } - private class PassphraseIntervalClickListener implements Preference.OnPreferenceClickListener, HmsPickerDialogFragment.HmsPickerDialogHandler { + private class PassphraseIntervalClickListener implements Preference.OnPreferenceClickListener, HmsPickerDialogFragment.HmsPickerDialogHandlerV2 { @Override public boolean onPreferenceClick(Preference preference) { @@ -186,12 +249,13 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment } @Override - public void onDialogHmsSet(int reference, int hours, int minutes, int seconds) { + public void onDialogHmsSet(int reference, boolean isNegative, int hours, int minutes, int seconds) { int timeoutMinutes = Math.max((int)TimeUnit.HOURS.toMinutes(hours) + - minutes + - (int)TimeUnit.SECONDS.toMinutes(seconds), 1); + minutes + + (int)TimeUnit.SECONDS.toMinutes(seconds), 1); TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes); + initializePassphraseTimeoutSummary(); } } @@ -230,35 +294,4 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment } } - private class ReadReceiptToggleListener implements Preference.OnPreferenceChangeListener { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - boolean enabled = (boolean)newValue; - ApplicationContext.getInstance(getContext()) - .getJobManager() - .add(new MultiDeviceReadReceiptUpdateJob(getContext(), enabled)); - - return true; - } - } - - public static CharSequence getSummary(Context context) { - final int privacySummaryResId = R.string.ApplicationPreferencesActivity_privacy_summary; - final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on); - final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off); - - if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) { - if (TextSecurePreferences.isScreenSecurityEnabled(context)) { - return context.getString(privacySummaryResId, offRes, onRes); - } else { - return context.getString(privacySummaryResId, offRes, offRes); - } - } else { - if (TextSecurePreferences.isScreenSecurityEnabled(context)) { - return context.getString(privacySummaryResId, onRes, onRes); - } else { - return context.getString(privacySummaryResId, onRes, offRes); - } - } - } } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 21c06c6846..a0068abb01 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -12,11 +12,11 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.util.Log; -import android.util.Pair; import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirementProvider; +import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; import org.whispersystems.libsignal.util.Medium; @@ -145,6 +145,11 @@ public class TextSecurePreferences { public static final String SCREEN_LOCK = "pref_android_screen_lock"; public static final String SCREEN_LOCK_TIMEOUT = "pref_android_screen_lock_timeout"; + public static final String REGISTRATION_LOCK_PREF = "pref_registration_lock"; + private static final String REGISTRATION_LOCK_PIN_PREF = "pref_registration_lock_pin"; + private static final String REGISTRATION_LOCK_LAST_REMINDER_TIME = "pref_registration_lock_last_reminder_time"; + private static final String REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL = "pref_registration_lock_next_reminder_interval"; + public static boolean isScreenLockEnabled(@NonNull Context context) { return getBooleanPreference(context, SCREEN_LOCK, false); } @@ -161,6 +166,38 @@ public class TextSecurePreferences { setLongPreference(context, SCREEN_LOCK_TIMEOUT, value); } + public static boolean isRegistrationtLockEnabled(@NonNull Context context) { + return getBooleanPreference(context, REGISTRATION_LOCK_PREF, false); + } + + public static void setRegistrationtLockEnabled(@NonNull Context context, boolean value) { + setBooleanPreference(context, REGISTRATION_LOCK_PREF, value); + } + + public static @Nullable String getRegistrationLockPin(@NonNull Context context) { + return getStringPreference(context, REGISTRATION_LOCK_PIN_PREF, null); + } + + public static void setRegistrationLockPin(@NonNull Context context, String pin) { + setStringPreference(context, REGISTRATION_LOCK_PIN_PREF, pin); + } + + public static long getRegistrationLockLastReminderTime(@NonNull Context context) { + return getLongPreference(context, REGISTRATION_LOCK_LAST_REMINDER_TIME, 0); + } + + public static void setRegistrationLockLastReminderTime(@NonNull Context context, long time) { + setLongPreference(context, REGISTRATION_LOCK_LAST_REMINDER_TIME, time); + } + + public static long getRegistrationLockNextReminderInterval(@NonNull Context context) { + return getLongPreference(context, REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL, RegistrationLockReminders.INITIAL_INTERVAL); + } + + public static void setRegistrationLockNextReminderInterval(@NonNull Context context, long value) { + setLongPreference(context, REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL, value); + } + public static void setBackupPassphrase(@NonNull Context context, @Nullable String passphrase) { setStringPreference(context, BACKUP_PASSPHRASE, passphrase); }