From cb1401f556705c7687002ca353d35ee2f1ab4b13 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Wed, 13 Jan 2021 19:35:44 -0400 Subject: [PATCH] Prompt to confirm number before SMS or call. --- .../securesms/components/LabeledEditText.java | 15 +++++ .../fragments/BaseRegistrationFragment.java | 52 +++++++++++++++ .../fragments/EnterCodeFragment.java | 16 ++++- .../fragments/EnterPhoneNumberFragment.java | 18 +++++- app/src/main/res/values/strings.xml | 4 ++ .../core/util/TranslationDetection.java | 64 +++++++++++++++++++ 6 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 core-util/src/main/java/org/signal/core/util/TranslationDetection.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LabeledEditText.java b/app/src/main/java/org/thoughtcrime/securesms/components/LabeledEditText.java index 0c56a44fa..cf2ecc7df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LabeledEditText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/LabeledEditText.java @@ -8,6 +8,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; @@ -91,4 +92,18 @@ public class LabeledEditText extends FrameLayout implements View.OnFocusChangeLi super.setEnabled(enabled); input.setEnabled(enabled); } + + public void focusAndMoveCursorToEndAndOpenKeyboard() { + input.requestFocus(); + + int numberLength = getText().length(); + input.setSelection(numberLength, numberLength); + + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT); + + if (!imm.isAcceptingText()) { + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationFragment.java index 04544fdc6..b2cc6dc58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationFragment.java @@ -4,27 +4,38 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.text.SpannableStringBuilder; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.SavedStateViewModelFactory; import androidx.lifecycle.ViewModelProviders; import com.dd.CircularProgressButton; +import org.signal.core.util.TranslationDetection; +import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; +import org.thoughtcrime.securesms.util.SpanUtil; + +import java.util.Locale; import static org.thoughtcrime.securesms.registration.RegistrationNavigationActivity.RE_REGISTRATION_EXTRA; abstract class BaseRegistrationFragment extends LoggingFragment { + private static final String TAG = Log.tag(BaseRegistrationFragment.class); + private RegistrationViewModel model; @Override @@ -104,4 +115,45 @@ abstract class BaseRegistrationFragment extends LoggingFragment { } }); } + + /** + * Presents a prompt for the user to confirm their number as long as it can be shown in one of their device languages. + */ + protected final void showConfirmNumberDialogIfTranslated(@NonNull Context context, + @StringRes int firstMessageLine, + @NonNull String e164number, + @NonNull Runnable onConfirmed, + @NonNull Runnable onEditNumber) + { + TranslationDetection translationDetection = new TranslationDetection(context); + + if (translationDetection.textExistsInUsersLanguage(firstMessageLine) && + translationDetection.textExistsInUsersLanguage(R.string.RegistrationActivity_is_your_phone_number_above_correct) && + translationDetection.textExistsInUsersLanguage(R.string.RegistrationActivity_edit_number)) + { + CharSequence message = new SpannableStringBuilder().append(context.getString(firstMessageLine)) + .append("\n\n") + .append(SpanUtil.bold(PhoneNumberFormatter.prettyPrint(e164number))) + .append("\n\n") + .append(context.getString(R.string.RegistrationActivity_is_your_phone_number_above_correct)); + + Log.i(TAG, "Showing confirm number dialog (" + context.getString(firstMessageLine) + ")"); + new AlertDialog.Builder(context) + .setMessage(message) + .setPositiveButton(android.R.string.ok, + (a, b) -> { + Log.i(TAG, "Number confirmed"); + onConfirmed.run(); + }) + .setNegativeButton(R.string.RegistrationActivity_edit_number, + (a, b) -> { + Log.i(TAG, "User requested edit number from confirm dialog"); + onEditNumber.run(); + }) + .show(); + } else { + Log.i(TAG, "Confirm number dialog not translated in " + Locale.getDefault() + " skipping"); + onConfirmed.run(); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java index 20f32853c..f4ee4c427 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java @@ -79,10 +79,11 @@ public final class EnterCodeFragment extends BaseRegistrationFragment signalStrengthListener = new SignalStrengthPhoneStateListener(this, this); connectKeyboard(verificationCodeView, keyboard); + hideKeyboard(requireContext(), view); setOnCodeFullyEnteredListener(verificationCodeView); - wrongNumber.setOnClickListener(v -> Navigation.findNavController(view).navigate(EnterCodeFragmentDirections.actionWrongNumber())); + wrongNumber.setOnClickListener(v -> onWrongNumber()); callMeCountDown.setOnClickListener(v -> handlePhoneCallRequest()); @@ -106,6 +107,11 @@ public final class EnterCodeFragment extends BaseRegistrationFragment model.onStartEnterCode(); } + private void onWrongNumber() { + Navigation.findNavController(requireView()) + .navigate(EnterCodeFragmentDirections.actionWrongNumber()); + } + private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCodeView) { verificationCodeView.setOnCompleteListener(code -> { RegistrationViewModel model = getModel(); @@ -261,6 +267,14 @@ public final class EnterCodeFragment extends BaseRegistrationFragment } private void handlePhoneCallRequest() { + showConfirmNumberDialogIfTranslated(requireContext(), + R.string.RegistrationActivity_you_will_receive_a_call_to_verify_this_number, + getModel().getNumber().getE164Number(), + this::handlePhoneCallRequestAfterConfirm, + this::onWrongNumber); + } + + private void handlePhoneCallRequestAfterConfirm() { RegistrationViewModel model = getModel(); String captcha = model.getCaptchaToken(); model.clearCaptchaResponse(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java index 0935f0001..679575419 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java @@ -147,9 +147,9 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment { PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(context); if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS) { - handleRequestVerification(context, e164number, true); + confirmNumberPrompt(context, e164number, () -> handleRequestVerification(context, e164number, true)); } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.MISSING) { - handlePromptForNoPlayServices(context, e164number); + confirmNumberPrompt(context, e164number, () -> handlePromptForNoPlayServices(context, e164number)); } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE) { GoogleApiAvailability.getInstance().getErrorDialog(requireActivity(), ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, 0).show(); } else { @@ -427,4 +427,18 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment { .setNegativeButton(android.R.string.cancel, null) .show(); } + + protected final void confirmNumberPrompt(@NonNull Context context, + @NonNull String e164number, + @NonNull Runnable onConfirmed) + { + showConfirmNumberDialogIfTranslated(context, + R.string.RegistrationActivity_a_verification_code_will_be_sent_to, + e164number, + () -> { + hideKeyboard(context, number.getInput()); + onConfirmed.run(); + }, + () -> number.focusAndMoveCursorToEndAndOpenKeyboard()); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 123ba86d0..bddb46419 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1489,6 +1489,10 @@ The number you specified (%s) is invalid. + A verification code will be sent to: + You\'ll receive a call to verify this number. + Is your phone number above correct? + Edit number Missing Google Play Services This device is missing Google Play Services. You can still use Signal, but this configuration may result in reduced reliability or performance.\n\nIf you are not an advanced user, are not running an aftermarket Android ROM, or believe that you are seeing this in error, please contact support@signal.org for help troubleshooting. I understand diff --git a/core-util/src/main/java/org/signal/core/util/TranslationDetection.java b/core-util/src/main/java/org/signal/core/util/TranslationDetection.java new file mode 100644 index 000000000..709e6a73f --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/TranslationDetection.java @@ -0,0 +1,64 @@ +package org.signal.core.util; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import java.util.Locale; + +/** + * Allows you to detect if a string resource is readable by the user according to their language settings. + */ +public final class TranslationDetection { + private final Resources resourcesLocal; + private final Resources resourcesEn; + private final Configuration configurationLocal; + + /** + * @param context Do not pass Application context, as this may not represent the users selected in-app locale. + */ + public TranslationDetection(@NonNull Context context) { + this.resourcesLocal = context.getResources(); + this.configurationLocal = resourcesLocal.getConfiguration(); + + Configuration configurationEn = new Configuration(configurationLocal); + configurationEn.setLocale(Locale.ENGLISH); + + this.resourcesEn = context.createConfigurationContext(configurationEn).getResources(); + } + + /** + * Returns true if any of these are true: + * - The current locale is English. + * - In a multi-locale capable device, the device supports any English locale in any position. + * - The text for the current locale does not Equal the English. + */ + public boolean textExistsInUsersLanguage(@StringRes int resId) { + if (configSupportsEnglish()) { + return true; + } + + String stringEn = resourcesEn.getString(resId); + String stringLocal = resourcesLocal.getString(resId); + + return !stringEn.equals(stringLocal); + } + + protected boolean configSupportsEnglish() { + if (configurationLocal.locale.getLanguage().equals("en")) { + return true; + } + + if (Build.VERSION.SDK_INT >= 24) { + Locale firstMatch = configurationLocal.getLocales().getFirstMatch(new String[]{"en"}); + + return firstMatch != null && firstMatch.getLanguage().equals("en"); + } + + return false; + } +} \ No newline at end of file