Update registration flow

pull/1/head
Moxie Marlinspike 7 years ago
parent e56e55363d
commit 90ff0e58b0

@ -289,12 +289,11 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".RegistrationActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".RegistrationProgressActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceActivity"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@ -400,10 +399,8 @@
</activity>
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.RegistrationService"/>
<service android:enabled="true" android:name=".service.MessageRetrievalService"/>
<service android:name=".service.QuickResponseService"

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:duration="150"
android:fromXDelta="0%"
android:toXDelta="-100%" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

@ -1,137 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:background="@drawable/background_pattern_repeat">
<LinearLayout android:padding="16dp"
android:paddingBottom="0dp"
android:layout_marginTop="20dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
android:layout_height="fill_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fillViewport="true"
android:background="@color/white"
tools:context=".RegistrationActivity">
<Spinner android:id="@+id/country_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip" />
<RelativeLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout android:orientation="horizontal"
android:layoutDirection="ltr"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="vertical"
android:background="@color/signal_primary"
android:padding="16dp">
<TextView android:layout_width="wrap_content"
<TextView android:id="@+id/verify_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:text="@string/registration_activity__verify_your_number"
android:textSize="20sp"
style="@style/Registration.Constant"
android:text="+" />
android:textStyle="bold"
android:textColor="@color/white"
android:layout_gravity="center"
android:gravity="center"/>
<EditText android:id="@+id/country_code"
android:layout_width="55dip"
<TextView android:id="@+id/verify_subheader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center"
android:inputType="phone"
android:digits="0123456789"
android:maxLength="3" />
<EditText android:id="@+id/number"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="phone"
android:hint="@string/registration_activity__phone_number"
android:gravity="center"
android:singleLine="true"/>
android:paddingBottom="25dp"
android:textColor="@color/white"
android:text="@string/registration_activity__please_enter_your_mobile_number_to_receive_a_verification_code_carrier_rates_may_apply"
android:gravity="center"/>
</LinearLayout>
<TextView style="@style/Registration.Description"
android:layout_width="fill_parent"
android:layout_marginBottom="8dip"
android:layout_marginTop="16dip"
android:gravity="start"
android:text="@string/registration_activity__verify_your_number_to_connect_with_signal"/>
<TextView style="@style/Registration.Description"
android:id="@+id/registration_information"
android:layout_width="fill_parent"
android:gravity="start"
android:visibility="gone"
android:layout_marginBottom="16dp"
android:text="@string/registration_activity__registration_will_transmit_some_contact_information_to_the_server_temporariliy"/>
<LinearLayout android:id="@+id/information_link_container"
android:clickable="true"
android:orientation="horizontal"
<android.support.design.widget.FloatingActionButton
app:fabSize="normal"
app:backgroundTint="@color/white"
app:elevation="1dp"
android:id="@+id/fab"
android:transitionName="icon"
android:src="@drawable/ic_action_name"
android:tint="@color/grey_700"
android:rotation="15"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/header"
android:layout_centerHorizontal="true"
android:layout_marginBottom="-32dp"/>
<LinearLayout android:id="@+id/registration_container"
android:padding="16dp"
android:paddingBottom="0dp"
android:layout_marginTop="30dp"
android:layout_below="@id/header"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical"
tools:visibility="invisible">
<Spinner android:id="@+id/country_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip" />
<LinearLayout android:orientation="horizontal"
android:layoutDirection="ltr"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
style="@style/Registration.Constant"
android:text="+" />
<EditText android:id="@+id/country_code"
android:layout_width="55dip"
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center"
android:inputType="phone"
android:digits="0123456789"
android:maxLength="3" />
<EditText android:id="@+id/number"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="phone"
android:hint="@string/registration_activity__phone_number"
android:gravity="center"
android:singleLine="true"/>
</LinearLayout>
<com.dd.CircularProgressButton
android:id="@+id/registerButton"
app:cpb_textIdle="Register"
app:cpb_selectorIdle="@drawable/progress_button_state"
app:cpb_colorIndicator="@color/white"
app:cpb_colorProgress="@color/textsecure_primary"
app:cpb_cornerRadius="50dp"
android:background="@color/signal_primary"
android:textColor="@color/white"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"/>
<TextView android:id="@+id/skip_button"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:src="@drawable/ic_info_white_18dp"
android:tint="#73B7F0"/>
android:layout_marginTop="20dp"
android:textColor="@color/gray50"
android:text="@android:string/cancel"/>
<TextView style="@style/Registration.Description"
android:id="@+id/information_label"
android:clickable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:layout_gravity="center_vertical"
android:textColor="#73B7F0"
android:text="@string/RegistrationActivity_more_information"/>
</LinearLayout>
android:id="@+id/registration_information"
android:layout_width="fill_parent"
android:gravity="start"
android:visibility="gone"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:text="@string/registration_activity__registration_will_transmit_some_contact_information_to_the_server_temporariliy"/>
<LinearLayout android:id="@+id/information_link_container"
android:clickable="true"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dip">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:src="@drawable/ic_info_white_18dp"
android:tint="#73B7F0"/>
<TextView style="@style/Registration.Description"
android:id="@+id/information_label"
android:clickable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:layout_gravity="center_vertical"
android:textColor="#73B7F0"
android:text="@string/RegistrationActivity_more_information"/>
</LinearLayout>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dip"
android:layout_marginBottom="16dip"
android:layout_gravity="right"
android:orientation="horizontal">
<TextView android:id="@+id/skipButton"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:text="@android:string/cancel"
android:textColor="@color/white"
android:background="@drawable/pill_button"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_marginRight="5dip"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/registerButton"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:text="@string/registration_activity__register"
android:textColor="@color/white"
android:background="@drawable/pill_button"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
</LinearLayout>
<ImageView android:id="@+id/twilio_shoutout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_twilio_shoutout_white"
android:layout_gravity="right"
android:tint="@color/grey_800"
android:contentDescription="@string/registration_activity__powered_by_twilio"/>
<RelativeLayout android:id="@+id/verification_container"
android:visibility="invisible"
android:layout_below="@id/header"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:visibility="visible">
<org.thoughtcrime.securesms.components.registration.VerificationCodeView
android:id="@+id/code"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:vcv_inputWidth="30dp"
app:vcv_spacing="10dp"
app:vcv_textColor="@color/signal_primary"
app:vcv_inputColor="@color/grey_600"/>
<org.thoughtcrime.securesms.components.registration.CallMeCountDownView
android:id="@+id/call_me_count_down"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/code"
android:layout_marginTop="30dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"/>
<org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard
android:id="@+id/keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.RelativeLayout">
<ImageView android:id="@+id/phone_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_phone_grey600_32dp"/>
<TextView android:id="@+id/call_me_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/phone_icon"
android:layout_toEndOf="@id/phone_icon"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:gravity="center_vertical"
android:textSize="16sp"
android:text="Call me instead"/>
<TextView android:id="@+id/available_in_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/countdown"
android:layout_toStartOf="@+id/countdown"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:textSize="16sp"
android:text="Available in: "/>
<TextView android:id="@+id/countdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textSize="16sp"
tools:text="1:04"/>
</merge>

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<LinearLayout android:id="@+id/container_zero"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:id="@+id/code_zero"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
tools:text="1"/>
<View android:id="@+id/space_zero"
android:layout_width="20dp"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
<LinearLayout android:id="@+id/container_one"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginLeft="5dp">
<TextView android:id="@+id/code_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
tools:text="2"/>
<View android:id="@+id/space_one"
android:layout_width="20dp"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
<LinearLayout android:id="@+id/container_two"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginLeft="5dp">
<TextView android:id="@+id/code_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
tools:text="2"/>
<View android:id="@+id/space_two"
android:layout_width="20dp"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
<LinearLayout android:id="@+id/separator_container"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginLeft="5dp">
<TextView android:id="@+id/separator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
android:text="-"/>
<View
android:layout_width="20dp"
android:layout_height="1dp"/>
</LinearLayout>
<LinearLayout android:id="@+id/container_three"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginLeft="5dp">
<TextView android:id="@+id/code_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
tools:text="2"/>
<View android:id="@+id/space_three"
android:layout_width="20dp"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
<LinearLayout android:id="@+id/container_four"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginLeft="5dp">
<TextView android:id="@+id/code_four"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
tools:text="2"/>
<View android:id="@+id/space_four"
android:layout_width="20dp"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
<LinearLayout android:id="@+id/container_five"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginLeft="5dp">
<TextView android:id="@+id/code_five"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textStyle="bold"
tools:text="2"/>
<View android:id="@+id/space_five"
android:layout_width="20dp"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.inputmethodservice.KeyboardView
android:id="@+id/keyboard_view"
android:keyBackground="@color/grey_300"
android:background="@color/grey_300"
android:keyTextColor="@color/black"
android:shadowColor="@color/transparent"
android:keyTextSize="30sp"
android:elevation="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ProgressBar android:id="@+id/progress"
android:layout_gravity="center"
android:layout_width="96dp"
android:layout_height="96dp"
android:indeterminate="true"/>
<org.thoughtcrime.securesms.components.SquareImageView
android:id="@+id/success"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/green_400"
android:gravity="center_vertical"
android:src="@drawable/ic_check_white_48dp"/>
<org.thoughtcrime.securesms.components.SquareImageView
android:id="@+id/failure"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/green_400"
android:gravity="center_vertical"
android:src="@drawable/ic_close_white_48dp"/>
</merge>

@ -227,4 +227,14 @@
</attr>
</declare-styleable>
<declare-styleable name="VerificationCodeView">
<attr name="vcv_spacing" format="dimension"/>
<attr name="vcv_inputWidth" format="dimension"/>
<attr name="vcv_inputHeight" format="dimension"/>
<attr name="vcv_inputColor" format="color"/>
<attr name="vcv_textSize" format="dimension"/>
<attr name="vcv_textColor" format="color"/>
</declare-styleable>
</resources>

@ -1488,6 +1488,8 @@
<string name="experience_upgrade_preference_fragment__optionally_see_and_share_when_messages_have_been_read">Optionally see and share when messages have been read</string>
<string name="experience_upgrade_preference_fragment__enable_read_receipts">Enable read receipts</string>
<string name="recipient_preference_activity__shared_media">Shared media</string>
<string name="registration_activity__verify_your_number">Verify Your Number</string>
<string name="registration_activity__please_enter_your_mobile_number_to_receive_a_verification_code_carrier_rates_may_apply">Please enter your mobile number to receive a verification code. Carrier rates may apply.</string>
<!-- EOF -->

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="33.3%p"
android:keyHeight="10%p">
<Row>
<Key android:codes="1" android:keyLabel="1" android:keyEdgeFlags="left" />
<Key android:codes="2" android:keyLabel="2" />
<Key android:codes="3" android:keyLabel="3" />
</Row>
<Row>
<Key android:codes="4" android:keyLabel="4" android:keyEdgeFlags="left" />
<Key android:codes="5" android:keyLabel="5" />
<Key android:codes="6" android:keyLabel="6" />
</Row>
<Row>
<Key android:codes="7" android:keyLabel="7" android:keyEdgeFlags="left" />
<Key android:codes="8" android:keyLabel="8" />
<Key android:codes="9" android:keyLabel="9" />
</Row>
<Row>
<Key android:codes="0" android:keyLabel="0" android:horizontalGap="33.3%p"/>
<Key android:codes="-1" android:keyIcon="@drawable/ic_backspace_grey600_24dp" android:isRepeatable="true"/>
</Row>
</Keyboard>

@ -1,39 +1,79 @@
package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.PorterDuff;
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
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.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.dd.CircularProgressButton;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.PlayServicesUtil;
import org.thoughtcrime.securesms.util.PlayServicesUtil.PlayServicesStatus;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.state.PreKeyRecord;
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.util.PhoneNumberFormatter;
import java.io.IOException;
import java.util.List;
/**
* The register account activity. Prompts ths user for their registration information
* and begins the account registration process.
@ -41,81 +81,106 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
* @author Moxie Marlinspike
*
*/
public class RegistrationActivity extends BaseActionBarActivity {
public class RegistrationActivity extends BaseActionBarActivity implements VerificationCodeView.OnCodeEnteredListener {
private static final int PICK_COUNTRY = 1;
private static final String TAG = RegistrationActivity.class.getSimpleName();
private static final int PICK_COUNTRY = 1;
private static final int SCENE_TRANSITION_DURATION = 250;
public static final String CHALLENGE_EVENT = "org.thoughtcrime.securesms.CHALLENGE_EVENT";
public static final String CHALLENGE_EXTRA = "CAAChallenge";
private enum PlayServicesStatus {
SUCCESS,
MISSING,
NEEDS_UPDATE,
TRANSIENT_ERROR
}
private AsYouTypeFormatter countryFormatter;
private ArrayAdapter<String> countrySpinnerAdapter;
private Spinner countrySpinner;
private TextView countryCode;
private TextView number;
private TextView createButton;
private TextView skipButton;
private TextView informationView;
private View informationToggle;
private TextView informationToggleText;
private static final String TAG = RegistrationActivity.class.getSimpleName();
private MasterSecret masterSecret;
private AsYouTypeFormatter countryFormatter;
private ArrayAdapter<String> countrySpinnerAdapter;
private Spinner countrySpinner;
private TextView countryCode;
private TextView number;
private CircularProgressButton createButton;
private TextView informationView;
private TextView informationToggleText;
private TextView title;
private TextView subtitle;
private View registrationContainer;
private View verificationContainer;
private FloatingActionButton fab;
private CallMeCountDownView callMeCountDownView;
private VerificationPinKeyboard keyboard;
private VerificationCodeView verificationCodeView;
private RegistrationState registrationState;
private ChallengeReceiver challengeReceiver;
private SignalServiceAccountManager accountManager;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.registration_activity);
getSupportActionBar().setTitle(getString(R.string.RegistrationActivity_connect_with_signal));
initializeResources();
initializeSpinner();
initializeNumber();
initializeChallengeListener();
}
@Override
public void onDestroy() {
super.onDestroy();
shutdownChallengeListener();
markAsVerifying(false);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PICK_COUNTRY && resultCode == RESULT_OK && data != null) {
this.countryCode.setText(data.getIntExtra("country_code", 1)+"");
this.countryCode.setText(String.valueOf(data.getIntExtra("country_code", 1)));
setCountryDisplay(data.getStringExtra("country_name"));
setCountryFormatter(data.getIntExtra("country_code", 1));
}
}
private void initializeResources() {
this.masterSecret = getIntent().getParcelableExtra("master_secret");
this.countrySpinner = (Spinner) findViewById(R.id.country_spinner);
this.countryCode = (TextView) findViewById(R.id.country_code);
this.number = (TextView) findViewById(R.id.number);
this.createButton = (TextView) findViewById(R.id.registerButton);
this.skipButton = (TextView) findViewById(R.id.skipButton);
this.informationView = (TextView) findViewById(R.id.registration_information);
this.informationToggle = findViewById(R.id.information_link_container);
this.informationToggleText = (TextView) findViewById(R.id.information_label);
this.createButton.getBackground().setColorFilter(ContextCompat.getColor(this, R.color.signal_primary),
PorterDuff.Mode.MULTIPLY);
this.skipButton.getBackground().setColorFilter(ContextCompat.getColor(this, R.color.grey_400),
PorterDuff.Mode.MULTIPLY);
TextView skipButton = findViewById(R.id.skip_button);
View informationToggle = findViewById(R.id.information_link_container);
this.countrySpinner = findViewById(R.id.country_spinner);
this.countryCode = findViewById(R.id.country_code);
this.number = findViewById(R.id.number);
this.createButton = findViewById(R.id.registerButton);
this.informationView = findViewById(R.id.registration_information);
this.informationToggleText = findViewById(R.id.information_label);
this.title = findViewById(R.id.verify_header);
this.subtitle = findViewById(R.id.verify_subheader);
this.registrationContainer = findViewById(R.id.registration_container);
this.verificationContainer = findViewById(R.id.verification_container);
this.fab = findViewById(R.id.fab);
this.verificationCodeView = findViewById(R.id.code);
this.keyboard = findViewById(R.id.keyboard);
this.callMeCountDownView = findViewById(R.id.call_me_count_down);
this.registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, null);
this.countryCode.addTextChangedListener(new CountryCodeChangedListener());
this.number.addTextChangedListener(new NumberChangedListener());
this.createButton.setOnClickListener(new CreateButtonListener());
this.skipButton.setOnClickListener(new CancelButtonListener());
this.informationToggle.setOnClickListener(new InformationToggleListener());
this.createButton.setOnClickListener(v -> handleRegister());
this.callMeCountDownView.setOnClickListener(v -> handlePhoneCallRequest());
skipButton.setOnClickListener(v -> handleCancel());
informationToggle.setOnClickListener(new InformationToggleListener());
if (getIntent().getBooleanExtra("cancel_button", false)) {
this.skipButton.setVisibility(View.VISIBLE);
skipButton.setVisibility(View.VISIBLE);
} else {
this.skipButton.setVisibility(View.INVISIBLE);
skipButton.setVisibility(View.INVISIBLE);
}
this.keyboard.setOnKeyPressListener(key -> {
if (key >= 0) verificationCodeView.append(key);
else verificationCodeView.delete();
});
this.verificationCodeView.setOnCompleteListener(this);
}
@SuppressLint("ClickableViewAccessibility")
private void initializeSpinner() {
this.countrySpinnerAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
this.countrySpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@ -123,26 +188,20 @@ public class RegistrationActivity extends BaseActionBarActivity {
setCountryDisplay(getString(R.string.RegistrationActivity_select_your_country));
this.countrySpinner.setAdapter(this.countrySpinnerAdapter);
this.countrySpinner.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
startActivityForResult(intent, PICK_COUNTRY);
}
return true;
this.countrySpinner.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
startActivityForResult(intent, PICK_COUNTRY);
}
return true;
});
this.countrySpinner.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && event.getAction() == KeyEvent.ACTION_UP) {
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
startActivityForResult(intent, PICK_COUNTRY);
return true;
}
return false;
this.countrySpinner.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && event.getAction() == KeyEvent.ACTION_UP) {
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
startActivityForResult(intent, PICK_COUNTRY);
return true;
}
return false;
});
}
@ -156,7 +215,7 @@ public class RegistrationActivity extends BaseActionBarActivity {
Optional<String> simCountryIso = Util.getSimCountryIso(this);
if (simCountryIso.isPresent() && !TextUtils.isEmpty(simCountryIso.get())) {
this.countryCode.setText(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get())+"");
this.countryCode.setText(String.valueOf(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get())));
}
}
}
@ -179,119 +238,370 @@ public class RegistrationActivity extends BaseActionBarActivity {
number.getText().toString());
}
private class CreateButtonListener implements View.OnClickListener {
@Override
public void onClick(View v) {
final RegistrationActivity self = RegistrationActivity.this;
private void handleRegister() {
if (TextUtils.isEmpty(countryCode.getText())) {
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_country_code), Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(countryCode.getText())) {
Toast.makeText(self,
getString(R.string.RegistrationActivity_you_must_specify_your_country_code),
Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(number.getText())) {
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number), Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(number.getText())) {
Toast.makeText(self,
getString(R.string.RegistrationActivity_you_must_specify_your_phone_number),
Toast.LENGTH_LONG).show();
return;
final String e164number = getConfiguredE164Number();
if (!PhoneNumberFormatter.isValidNumber(e164number)) {
Dialogs.showAlertDialog(this, getString(R.string.RegistrationActivity_invalid_number),
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid),
e164number));
return;
}
PlayServicesStatus gcmStatus = PlayServicesUtil.getPlayServicesStatus(this);
if (gcmStatus == PlayServicesStatus.SUCCESS) {
handleRequestVerification(e164number, true);
} else if (gcmStatus == PlayServicesStatus.MISSING) {
handlePromptForNoPlayServices(e164number);
} else if (gcmStatus == PlayServicesStatus.NEEDS_UPDATE) {
GoogleApiAvailability.getInstance().getErrorDialog(this, ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, 0).show();
} else {
Dialogs.showAlertDialog(this, getString(R.string.RegistrationActivity_play_services_error),
getString(R.string.RegistrationActivity_google_play_services_is_updating_or_unavailable));
}
}
@SuppressLint("StaticFieldLeak")
private void handleRequestVerification(@NonNull String e164number, boolean gcmSupported) {
createButton.setIndeterminateProgressMode(true);
createButton.setProgress(50);
new AsyncTask<Void, Void, Pair<String, Optional<String>>> () {
@Override
protected @Nullable Pair<String, Optional<String>> doInBackground(Void... voids) {
try {
markAsVerifying(true);
String password = Util.getSecret(18);
Optional<String> gcmToken;
if (gcmSupported) {
gcmToken = Optional.of(GoogleCloudMessaging.getInstance(RegistrationActivity.this).register(GcmRefreshJob.REGISTRATION_ID));
} else {
gcmToken = Optional.absent();
}
accountManager = AccountManagerFactory.createManager(RegistrationActivity.this, e164number, password);
accountManager.requestSmsVerificationCode();
return new Pair<>(password, gcmToken);
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
final String e164number = getConfiguredE164Number();
protected void onPostExecute(@Nullable Pair<String, Optional<String>> result) {
if (result == null) {
Toast.makeText(RegistrationActivity.this, "Unable to connect to service. Please check network connection and try again.", Toast.LENGTH_LONG).show();
return;
}
if (!PhoneNumberFormatter.isValidNumber(e164number)) {
Dialogs.showAlertDialog(self,
getString(R.string.RegistrationActivity_invalid_number),
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid),
e164number));
return;
registrationState = new RegistrationState(RegistrationState.State.VERIFYING, e164number, result.first, result.second);
displayVerificationView(e164number, 64);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
PlayServicesStatus gcmStatus = checkPlayServices(self);
private void handleChallengeReceived(@Nullable String challenge) {
if (challenge != null && challenge.length() == 6 && registrationState.state == RegistrationState.State.VERIFYING) {
verificationCodeView.clear();
if (gcmStatus == PlayServicesStatus.SUCCESS) {
promptForRegistrationStart(self, e164number, true);
} else if (gcmStatus == PlayServicesStatus.MISSING) {
promptForNoPlayServices(self, e164number);
} else if (gcmStatus == PlayServicesStatus.NEEDS_UPDATE) {
GoogleApiAvailability.getInstance().getErrorDialog(self, ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, 0).show();
} else {
Dialogs.showAlertDialog(self, getString(R.string.RegistrationActivity_play_services_error),
getString(R.string.RegistrationActivity_google_play_services_is_updating_or_unavailable));
try {
for (int i=0;i<challenge.length();i++) {
final int index = i;
verificationCodeView.postDelayed(() -> verificationCodeView.append(Integer.parseInt(Character.toString(challenge.charAt(index)))), i * 200);
}
} catch (NumberFormatException e) {
Log.w(TAG, e);
verificationCodeView.clear();
}
}
}
private void promptForRegistrationStart(final Context context, final String e164number, final boolean gcmSupported) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(PhoneNumberFormatter.getInternationalFormatFromE164(e164number));
dialog.setMessage(R.string.RegistrationActivity_we_will_now_verify_that_the_following_number_is_associated_with_your_device_s);
dialog.setPositiveButton(getString(R.string.RegistrationActivity_continue),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(context, RegistrationProgressActivity.class);
intent.putExtra(RegistrationProgressActivity.NUMBER_EXTRA, e164number);
intent.putExtra(RegistrationProgressActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationProgressActivity.GCM_SUPPORTED_EXTRA, gcmSupported);
startActivity(intent);
finish();
}
});
dialog.setNegativeButton(getString(R.string.RegistrationActivity_edit), null);
dialog.show();
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCodeComplete(@NonNull String code) {
this.registrationState = new RegistrationState(RegistrationState.State.CHECKING, this.registrationState);
callMeCountDownView.setVisibility(View.INVISIBLE);
keyboard.displayProgress();
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... voids) {
try {
int registrationId = KeyHelper.generateRegistrationId(false);
TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId);
SessionUtil.archiveAllSessions(RegistrationActivity.this);
String signalingKey = Util.getSecret(52);
private void promptForNoPlayServices(final Context context, final String e164number) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(R.string.RegistrationActivity_missing_google_play_services);
dialog.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services);
dialog.setPositiveButton(R.string.RegistrationActivity_i_understand, new DialogInterface.OnClickListener() {
accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent());
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this);
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(RegistrationActivity.this);
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(RegistrationActivity.this, identityKey, true);
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records);
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);
return true;
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
keyboard.displaySuccess().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(RegistrationActivity.this));
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();
}
});
} else {
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
registrationState = new RegistrationState(RegistrationState.State.VERIFYING, registrationState);
callMeCountDownView.setVisibility(View.VISIBLE);
verificationCodeView.clear();
keyboard.displayKeyboard();
}
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@SuppressLint("StaticFieldLeak")
private void handlePhoneCallRequest() {
if (registrationState.state == RegistrationState.State.VERIFYING) {
callMeCountDownView.startCountDown(300);
new AsyncTask<Void, Void, Void>() {
@Override
public void onClick(DialogInterface dialog, int which) {
promptForRegistrationStart(context, e164number, false);
protected Void doInBackground(Void... voids) {
try {
accountManager.requestVoiceVerificationCode();
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
});
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.show();
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private PlayServicesStatus checkPlayServices(Context context) {
int gcmStatus = 0;
private void displayInitialView(@NonNull String e164number) {
title.animate().translationX(title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
title.setText(R.string.registration_activity__verify_your_number);
title.clearAnimation();
title.setTranslationX(-1 * title.getWidth());
title.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
try {
gcmStatus = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
} catch (Throwable t) {
Log.w(TAG, t);
return PlayServicesStatus.MISSING;
subtitle.animate().translationX(subtitle.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
subtitle.setText(R.string.registration_activity__please_enter_your_mobile_number_to_receive_a_verification_code_carrier_rates_may_apply);
subtitle.clearAnimation();
subtitle.setTranslationX(-1 * subtitle.getWidth());
subtitle.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
Log.w(TAG, "Play Services: " + gcmStatus);
verificationContainer.animate().translationX(verificationContainer.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
verificationContainer.clearAnimation();
verificationContainer.setVisibility(View.INVISIBLE);
verificationContainer.setTranslationX(0);
registrationContainer.setTranslationX(-1 * registrationContainer.getWidth());
registrationContainer.setVisibility(View.VISIBLE);
createButton.setProgress(0);
createButton.setIndeterminateProgressMode(false);
registrationContainer.animate().translationX(0).setDuration(SCENE_TRANSITION_DURATION).setListener(null).setInterpolator(new OvershootInterpolator()).start();
}
}).start();
switch (gcmStatus) {
case ConnectionResult.SUCCESS:
return PlayServicesStatus.SUCCESS;
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
try {
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo("com.google.android.gms", 0);
fab.animate().rotationBy(360f).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
fab.clearAnimation();
fab.setImageResource(R.drawable.ic_action_name);
fab.animate().rotationBy(375f).setDuration(SCENE_TRANSITION_DURATION).setListener(null).start();
}
}).start();
}
if (applicationInfo != null && !applicationInfo.enabled) {
return PlayServicesStatus.MISSING;
}
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, e);
private void displayVerificationView(@NonNull String e164number, int callCountdown) {
ServiceUtil.getInputMethodManager(this)
.hideSoftInputFromWindow(countryCode.getWindowToken(), 0);
ServiceUtil.getInputMethodManager(this)
.hideSoftInputFromWindow(number.getWindowToken(), 0);
title.animate().translationX(-1 * title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
title.setText(String.format("Verify %s", e164number));
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) {
SpannableString subtitleDescription = new SpannableString(String.format("Please enter the verification code sent to %s.", e164number));
SpannableString wrongNumber = new SpannableString("Wrong number?");
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
displayInitialView(e164number);
registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, null);
}
return PlayServicesStatus.NEEDS_UPDATE;
case ConnectionResult.SERVICE_DISABLED:
case ConnectionResult.SERVICE_MISSING:
case ConnectionResult.SERVICE_INVALID:
case ConnectionResult.API_UNAVAILABLE:
case ConnectionResult.SERVICE_MISSING_PERMISSION:
return PlayServicesStatus.MISSING;
default:
return PlayServicesStatus.TRANSIENT_ERROR;
@Override
public void updateDrawState(TextPaint paint) {
paint.setColor(Color.WHITE);
paint.setUnderlineText(true);
}
};
wrongNumber.setSpan(clickableSpan, 0, wrongNumber.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
subtitle.setText(new SpannableStringBuilder(subtitleDescription).append(" ").append(wrongNumber));
subtitle.setMovementMethod(LinkMovementMethod.getInstance());
subtitle.clearAnimation();
subtitle.setTranslationX(subtitle.getWidth());
subtitle.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
registrationContainer.animate().translationX(-1 * registrationContainer.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
registrationContainer.clearAnimation();
registrationContainer.setVisibility(View.INVISIBLE);
registrationContainer.setTranslationX(0);
verificationContainer.setTranslationX(verificationContainer.getWidth());
verificationContainer.setVisibility(View.VISIBLE);
verificationContainer.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_textsms_24dp);
fab.animate().rotationBy(-375f).setDuration(SCENE_TRANSITION_DURATION).setListener(null).start();
}
}).start();
this.callMeCountDownView.startCountDown(callCountdown);
}
private void handleCancel() {
TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true);
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent == null) {
nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class);
}
startActivity(nextIntent);
finish();
}
private void handlePromptForNoPlayServices(@NonNull String e164number) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.RegistrationActivity_missing_google_play_services);
dialog.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services);
dialog.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> handleRequestVerification(e164number, false));
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.show();
}
private void initializeChallengeListener() {
challengeReceiver = new ChallengeReceiver();
IntentFilter filter = new IntentFilter(CHALLENGE_EVENT);
registerReceiver(challengeReceiver, filter);
}
private void shutdownChallengeListener() {
if (challengeReceiver != null) {
unregisterReceiver(challengeReceiver);
challengeReceiver = null;
}
}
private void markAsVerifying(boolean verifying) {
TextSecurePreferences.setVerifying(this, verifying);
if (verifying) {
TextSecurePreferences.setPushRegistered(this, false);
}
}
private class ChallengeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.w(TAG, "Got a challenge broadcast...");
handleChallengeReceived(intent.getStringExtra(CHALLENGE_EXTRA));
}
}
@ -358,21 +668,6 @@ public class RegistrationActivity extends BaseActionBarActivity {
}
}
private class CancelButtonListener implements View.OnClickListener {
@Override
public void onClick(View v) {
TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true);
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent == null) {
nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class);
}
startActivity(nextIntent);
finish();
}
}
private class InformationToggleListener implements View.OnClickListener {
@Override
public void onClick(View v) {
@ -385,4 +680,29 @@ public class RegistrationActivity extends BaseActionBarActivity {
}
}
}
private static class RegistrationState {
private enum State {
INITIAL, VERIFYING, CHECKING
}
private final State state;
private final String e164number;
private final String password;
private final Optional<String> gcmToken;
RegistrationState(State state, String e164number, String password, Optional<String> gcmToken) {
this.state = state;
this.e164number = e164number;
this.password = password;
this.gcmToken = gcmToken;
}
RegistrationState(State state, RegistrationState previous) {
this.state = state;
this.e164number = previous.e164number;
this.password = previous.password;
this.gcmToken = previous.gcmToken;
}
}
}

@ -1,653 +0,0 @@
package org.thoughtcrime.securesms;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.service.RegistrationService;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.KeyHelper;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.IOException;
import static org.thoughtcrime.securesms.service.RegistrationService.RegistrationState;
public class RegistrationProgressActivity extends BaseActionBarActivity {
private static final String TAG = RegistrationProgressActivity.class.getSimpleName();
public static final String NUMBER_EXTRA = "e164number";
public static final String MASTER_SECRET_EXTRA = "master_secret";
public static final String GCM_SUPPORTED_EXTRA = "gcm_supported";
private static final int FOCUSED_COLOR = Color.parseColor("#ff333333");
private static final int UNFOCUSED_COLOR = Color.parseColor("#ff808080");
private ServiceConnection serviceConnection = new RegistrationServiceConnection();
private Handler registrationStateHandler = new RegistrationStateHandler();
private RegistrationReceiver registrationReceiver = new RegistrationReceiver();
private RegistrationService registrationService;
private LinearLayout registrationLayout;
private LinearLayout verificationFailureLayout;
private LinearLayout connectivityFailureLayout;
private RelativeLayout timeoutProgressLayout;
private ProgressBar registrationProgress;
private ProgressBar connectingProgress;
private ProgressBar verificationProgress;
private ProgressBar generatingKeysProgress;
private ProgressBar gcmRegistrationProgress;
private ImageView connectingCheck;
private ImageView verificationCheck;
private ImageView generatingKeysCheck;
private ImageView gcmRegistrationCheck;
private TextView connectingText;
private TextView verificationText;
private TextView registrationTimerText;
private TextView generatingKeysText;
private TextView gcmRegistrationText;
private Button verificationFailureButton;
private Button connectivityFailureButton;
private Button callButton;
private Button verifyButton;
private EditText codeEditText;
private MasterSecret masterSecret;
private boolean gcmSupported;
private volatile boolean visible;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
getSupportActionBar().setTitle(getString(R.string.RegistrationProgressActivity_verifying_number));
setContentView(R.layout.registration_progress_activity);
initializeResources();
initializeLinks();
initializeServiceBinding();
}
@Override
public void onDestroy() {
super.onDestroy();
shutdownServiceBinding();
}
@Override
public void onResume() {
super.onResume();
handleActivityVisible();
}
@Override
public void onPause() {
super.onPause();
handleActivityNotVisible();
}
@Override
public void onBackPressed() {
}
private void initializeServiceBinding() {
Intent intent = new Intent(this, RegistrationService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void initializeResources() {
this.masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
this.gcmSupported = getIntent().getBooleanExtra(GCM_SUPPORTED_EXTRA, true);
this.registrationLayout = (LinearLayout)findViewById(R.id.registering_layout);
this.verificationFailureLayout = (LinearLayout)findViewById(R.id.verification_failure_layout);
this.connectivityFailureLayout = (LinearLayout)findViewById(R.id.connectivity_failure_layout);
this.registrationProgress = (ProgressBar) findViewById(R.id.registration_progress);
this.connectingProgress = (ProgressBar) findViewById(R.id.connecting_progress);
this.verificationProgress = (ProgressBar) findViewById(R.id.verification_progress);
this.generatingKeysProgress = (ProgressBar) findViewById(R.id.generating_keys_progress);
this.gcmRegistrationProgress = (ProgressBar) findViewById(R.id.gcm_registering_progress);
this.connectingCheck = (ImageView) findViewById(R.id.connecting_complete);
this.verificationCheck = (ImageView) findViewById(R.id.verification_complete);
this.generatingKeysCheck = (ImageView) findViewById(R.id.generating_keys_complete);
this.gcmRegistrationCheck = (ImageView) findViewById(R.id.gcm_registering_complete);
this.connectingText = (TextView) findViewById(R.id.connecting_text);
this.verificationText = (TextView) findViewById(R.id.verification_text);
this.registrationTimerText = (TextView) findViewById(R.id.registration_timer);
this.generatingKeysText = (TextView) findViewById(R.id.generating_keys_text);
this.gcmRegistrationText = (TextView) findViewById(R.id.gcm_registering_text);
this.verificationFailureButton = (Button) findViewById(R.id.verification_failure_edit_button);
this.connectivityFailureButton = (Button) findViewById(R.id.connectivity_failure_edit_button);
this.callButton = (Button) findViewById(R.id.call_button);
this.verifyButton = (Button) findViewById(R.id.verify_button);
this.codeEditText = (EditText) findViewById(R.id.telephone_code);
this.timeoutProgressLayout = (RelativeLayout) findViewById(R.id.timer_progress_layout);
Button editButton = (Button) findViewById(R.id.edit_button);
editButton.setOnClickListener(new EditButtonListener());
this.verificationFailureButton.setOnClickListener(new EditButtonListener());
this.connectivityFailureButton.setOnClickListener(new EditButtonListener());
}
private void initializeLinks() {
TextView failureText = (TextView) findViewById(R.id.sms_failed_text);
String pretext = getString(R.string.registration_progress_activity__signal_timed_out_while_waiting_for_a_verification_sms_message);
String link = getString(R.string.RegistrationProblemsActivity_possible_problems);
SpannableString spannableString = new SpannableString(pretext + " " + link);
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
new AlertDialog.Builder(RegistrationProgressActivity.this)
.setTitle(R.string.RegistrationProblemsActivity_possible_problems)
.setView(R.layout.registration_problems)
.setNeutralButton(android.R.string.ok, null)
.show();
}
}, pretext.length() + 1, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
failureText.setText(spannableString);
failureText.setMovementMethod(LinkMovementMethod.getInstance());
}
private void handleActivityVisible() {
IntentFilter filter = new IntentFilter(RegistrationService.REGISTRATION_EVENT);
filter.setPriority(1000);
registerReceiver(registrationReceiver, filter);
visible = true;
}
private void handleActivityNotVisible() {
unregisterReceiver(registrationReceiver);
visible = false;
}
private void handleStateIdle() {
if (hasNumberDirective()) {
Intent intent = new Intent(this, RegistrationService.class);
intent.setAction(RegistrationService.REGISTER_NUMBER_ACTION);
intent.putExtra(RegistrationService.NUMBER_EXTRA, getNumberDirective());
intent.putExtra(RegistrationService.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationService.GCM_SUPPORTED_EXTRA, gcmSupported);
startService(intent);
} else {
Intent intent = new Intent(this, RegistrationActivity.class);
intent.putExtra("master_secret", masterSecret);
startActivity(intent);
finish();
}
}
private void handleStateConnecting() {
this.registrationLayout.setVisibility(View.VISIBLE);
this.verificationFailureLayout.setVisibility(View.GONE);
this.connectivityFailureLayout.setVisibility(View.GONE);
this.connectingProgress.setVisibility(View.VISIBLE);
this.connectingCheck.setVisibility(View.INVISIBLE);
this.verificationProgress.setVisibility(View.INVISIBLE);
this.verificationCheck.setVisibility(View.INVISIBLE);
this.generatingKeysProgress.setVisibility(View.INVISIBLE);
this.generatingKeysCheck.setVisibility(View.INVISIBLE);
this.gcmRegistrationProgress.setVisibility(View.INVISIBLE);
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
this.connectingText.setTextColor(FOCUSED_COLOR);
this.verificationText.setTextColor(UNFOCUSED_COLOR);
this.generatingKeysText.setTextColor(UNFOCUSED_COLOR);
this.gcmRegistrationText.setTextColor(UNFOCUSED_COLOR);
this.timeoutProgressLayout.setVisibility(View.VISIBLE);
}
private void handleStateVerifying() {
this.registrationLayout.setVisibility(View.VISIBLE);
this.verificationFailureLayout.setVisibility(View.GONE);
this.connectivityFailureLayout.setVisibility(View.GONE);
this.connectingProgress.setVisibility(View.INVISIBLE);
this.connectingCheck.setVisibility(View.VISIBLE);
this.verificationProgress.setVisibility(View.VISIBLE);
this.verificationCheck.setVisibility(View.INVISIBLE);
this.generatingKeysProgress.setVisibility(View.INVISIBLE);
this.generatingKeysCheck.setVisibility(View.INVISIBLE);
this.gcmRegistrationProgress.setVisibility(View.INVISIBLE);
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
this.connectingText.setTextColor(UNFOCUSED_COLOR);
this.verificationText.setTextColor(FOCUSED_COLOR);
this.generatingKeysText.setTextColor(UNFOCUSED_COLOR);
this.gcmRegistrationText.setTextColor(UNFOCUSED_COLOR);
this.registrationProgress.setVisibility(View.VISIBLE);
this.timeoutProgressLayout.setVisibility(View.VISIBLE);
}
private void handleStateGeneratingKeys() {
this.registrationLayout.setVisibility(View.VISIBLE);
this.verificationFailureLayout.setVisibility(View.GONE);
this.connectivityFailureLayout.setVisibility(View.GONE);
this.connectingProgress.setVisibility(View.INVISIBLE);
this.connectingCheck.setVisibility(View.VISIBLE);
this.verificationProgress.setVisibility(View.INVISIBLE);
this.verificationCheck.setVisibility(View.VISIBLE);
this.generatingKeysProgress.setVisibility(View.VISIBLE);
this.generatingKeysCheck.setVisibility(View.INVISIBLE);
this.gcmRegistrationProgress.setVisibility(View.INVISIBLE);
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
this.connectingText.setTextColor(UNFOCUSED_COLOR);
this.verificationText.setTextColor(UNFOCUSED_COLOR);
this.generatingKeysText.setTextColor(FOCUSED_COLOR);
this.gcmRegistrationText.setTextColor(UNFOCUSED_COLOR);
this.registrationProgress.setVisibility(View.INVISIBLE);
this.timeoutProgressLayout.setVisibility(View.INVISIBLE);
}
private void handleStateGcmRegistering() {
this.registrationLayout.setVisibility(View.VISIBLE);
this.verificationFailureLayout.setVisibility(View.GONE);
this.connectivityFailureLayout.setVisibility(View.GONE);
this.connectingProgress.setVisibility(View.INVISIBLE);
this.connectingCheck.setVisibility(View.VISIBLE);
this.verificationProgress.setVisibility(View.INVISIBLE);
this.verificationCheck.setVisibility(View.VISIBLE);
this.generatingKeysProgress.setVisibility(View.INVISIBLE);
this.generatingKeysCheck.setVisibility(View.VISIBLE);
this.gcmRegistrationProgress.setVisibility(View.VISIBLE);
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
this.connectingText.setTextColor(UNFOCUSED_COLOR);
this.verificationText.setTextColor(UNFOCUSED_COLOR);
this.generatingKeysText.setTextColor(UNFOCUSED_COLOR);
this.gcmRegistrationText.setTextColor(FOCUSED_COLOR);
this.registrationProgress.setVisibility(View.INVISIBLE);
this.timeoutProgressLayout.setVisibility(View.INVISIBLE);
}
private void handleGcmTimeout(RegistrationState state) {
handleConnectivityError(state);
}
private void handleVerificationRequestedVoice(RegistrationState state) {
handleVerificationTimeout(state);
verifyButton.setOnClickListener(new VerifyClickListener(state.number, state.password, gcmSupported));
verifyButton.setEnabled(true);
codeEditText.setEnabled(true);
}
private void handleVerificationTimeout(RegistrationState state) {
this.callButton.setOnClickListener(new CallClickListener(state.number));
this.verifyButton.setEnabled(false);
this.codeEditText.setEnabled(false);
this.registrationLayout.setVisibility(View.GONE);
this.connectivityFailureLayout.setVisibility(View.GONE);
this.verificationFailureLayout.setVisibility(View.VISIBLE);
this.verificationFailureButton.setText(String.format(getString(R.string.RegistrationProgressActivity_edit_s),
PhoneNumberFormatter.formatNumberInternational(state.number)));
}
private void handleConnectivityError(RegistrationState state) {
this.registrationLayout.setVisibility(View.GONE);
this.verificationFailureLayout.setVisibility(View.GONE);
this.connectivityFailureLayout.setVisibility(View.VISIBLE);
this.connectivityFailureButton.setText(String.format(getString(R.string.RegistrationProgressActivity_edit_s),
PhoneNumberFormatter.formatNumberInternational(state.number)));
}
private void handleMultiRegistrationError(RegistrationState state) {
handleVerificationTimeout(state);
Dialogs.showAlertDialog(this, getString(R.string.RegistrationProgressActivity_registration_conflict),
getString(R.string.RegistrationProgressActivity_this_number_is_already_registered_on_a_different));
}
private void handleVerificationComplete() {
if (visible) {
Toast.makeText(this,
R.string.RegistrationProgressActivity_registration_complete,
Toast.LENGTH_LONG).show();
}
shutdownService();
Intent intent = new Intent(this, CreateProfileActivity.class);
intent.putExtra(CreateProfileActivity.NEXT_INTENT, new Intent(this, ConversationListActivity.class));
startActivity(intent);
finish();
}
private void handleTimerUpdate() {
if (registrationService == null)
return;
int totalSecondsRemaining = registrationService.getSecondsRemaining();
int minutesRemaining = totalSecondsRemaining / 60;
int secondsRemaining = totalSecondsRemaining - (minutesRemaining * 60);
double percentageComplete = (double)((60 * 2) - totalSecondsRemaining) / (double)(60 * 2);
int progress = (int)Math.round(((double)registrationProgress.getMax()) * percentageComplete);
this.registrationProgress.setProgress(progress);
this.registrationTimerText.setText(String.format("%02d:%02d", minutesRemaining, secondsRemaining));
registrationStateHandler.sendEmptyMessageDelayed(RegistrationState.STATE_TIMER, 1000);
}
private boolean hasNumberDirective() {
return getIntent().getStringExtra(NUMBER_EXTRA) != null;
}
private String getNumberDirective() {
return getIntent().getStringExtra(NUMBER_EXTRA);
}
private void shutdownServiceBinding() {
if (serviceConnection != null) {
unbindService(serviceConnection);
serviceConnection = null;
}
}
private void shutdownService() {
if (registrationService != null) {
registrationService.shutdown();
registrationService = null;
}
shutdownServiceBinding();
Intent serviceIntent = new Intent(RegistrationProgressActivity.this, RegistrationService.class);
stopService(serviceIntent);
}
private class RegistrationServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
registrationService = ((RegistrationService.RegistrationServiceBinder)service).getService();
registrationService.setRegistrationStateHandler(registrationStateHandler);
RegistrationState state = registrationService.getRegistrationState();
registrationStateHandler.obtainMessage(state.state, state).sendToTarget();
handleTimerUpdate();
}
@Override
public void onServiceDisconnected(ComponentName name) {
registrationService.setRegistrationStateHandler(null);
}
}
private class RegistrationStateHandler extends Handler {
@Override
public void handleMessage(Message message) {
RegistrationState state = (RegistrationState)message.obj;
switch (message.what) {
case RegistrationState.STATE_IDLE: handleStateIdle(); break;
case RegistrationState.STATE_CONNECTING: handleStateConnecting(); break;
case RegistrationState.STATE_VERIFYING: handleStateVerifying(); break;
case RegistrationState.STATE_TIMER: handleTimerUpdate(); break;
case RegistrationState.STATE_GENERATING_KEYS: handleStateGeneratingKeys(); break;
case RegistrationState.STATE_GCM_REGISTERING: handleStateGcmRegistering(); break;
case RegistrationState.STATE_TIMEOUT: handleVerificationTimeout(state); break;
case RegistrationState.STATE_COMPLETE: handleVerificationComplete(); break;
case RegistrationState.STATE_GCM_TIMEOUT: handleGcmTimeout(state); break;
case RegistrationState.STATE_NETWORK_ERROR: handleConnectivityError(state); break;
case RegistrationState.STATE_MULTI_REGISTERED: handleMultiRegistrationError(state); break;
case RegistrationState.STATE_VOICE_REQUESTED: handleVerificationRequestedVoice(state); break;
}
}
}
private class EditButtonListener implements View.OnClickListener {
@Override
public void onClick(View v) {
shutdownService();
Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class);
activityIntent.putExtra(RegistrationProgressActivity.MASTER_SECRET_EXTRA, masterSecret);
activityIntent.putExtra(RegistrationProgressActivity.GCM_SUPPORTED_EXTRA, gcmSupported);
startActivity(activityIntent);
finish();
}
}
private static class RegistrationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
abortBroadcast();
}
}
private class VerifyClickListener implements View.OnClickListener {
private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1;
private static final int RATE_LIMIT_ERROR = 2;
private static final int VERIFICATION_ERROR = 3;
private static final int MULTI_REGISTRATION_ERROR = 4;
private final String e164number;
private final String password;
private final String signalingKey;
private final boolean gcmSupported;
private final Context context;
private ProgressDialog progressDialog;
public VerifyClickListener(String e164number, String password, boolean gcmSupported) {
this.e164number = e164number;
this.password = password;
this.signalingKey = Util.getSecret(52);
this.gcmSupported = gcmSupported;
this.context = RegistrationProgressActivity.this;
}
@Override
public void onClick(View v) {
final String code = codeEditText.getText().toString();
if (TextUtils.isEmpty(code)) {
Toast.makeText(context,
getString(R.string.RegistrationProgressActivity_you_must_enter_the_code_you_received_first),
Toast.LENGTH_LONG).show();
return;
}
new AsyncTask<Void, Void, Integer>() {
@Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(context,
getString(R.string.RegistrationProgressActivity_connecting),
getString(R.string.RegistrationProgressActivity_connecting_for_verification),
true, false);
}
@Override
protected void onPostExecute(Integer result) {
if (progressDialog != null) progressDialog.dismiss();
switch (result) {
case SUCCESS:
Intent intent = new Intent(context, RegistrationService.class);
intent.setAction(RegistrationService.VOICE_REGISTER_ACTION);
intent.putExtra(RegistrationService.NUMBER_EXTRA, e164number);
intent.putExtra(RegistrationService.PASSWORD_EXTRA, password);
intent.putExtra(RegistrationService.SIGNALING_KEY_EXTRA, signalingKey);
intent.putExtra(RegistrationService.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationService.GCM_SUPPORTED_EXTRA, gcmSupported);
startService(intent);
break;
case NETWORK_ERROR:
Dialogs.showAlertDialog(context, getString(R.string.RegistrationProgressActivity_network_error),
getString(R.string.RegistrationProgressActivity_unable_to_connect));
break;
case VERIFICATION_ERROR:
Dialogs.showAlertDialog(context, getString(R.string.RegistrationProgressActivity_verification_failed),
getString(R.string.RegistrationProgressActivity_the_verification_code_you_submitted_is_incorrect));
break;
case RATE_LIMIT_ERROR:
Dialogs.showAlertDialog(context, getString(R.string.RegistrationProgressActivity_too_many_attempts),
getString(R.string.RegistrationProgressActivity_youve_submitted_an_incorrect_verification_code_too_many_times));
break;
case MULTI_REGISTRATION_ERROR:
Dialogs.showAlertDialog(context, getString(R.string.RegistrationProgressActivity_registration_conflict),
getString(R.string.RegistrationProgressActivity_this_number_is_already_registered_on_a_different));
break;
}
}
@Override
protected Integer doInBackground(Void... params) {
try {
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context, e164number, password);
int registrationId = KeyHelper.generateRegistrationId(false);
TextSecurePreferences.setLocalRegistrationId(context, registrationId);
SessionUtil.archiveAllSessions(context);
accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !gcmSupported);
return SUCCESS;
} catch (ExpectationFailedException e) {
Log.w(TAG, e);
return MULTI_REGISTRATION_ERROR;
} catch (RateLimitException e) {
Log.w(TAG, e);
return RATE_LIMIT_ERROR;
} catch (AuthorizationFailedException e) {
Log.w(TAG, e);
return VERIFICATION_ERROR;
} catch (IOException e) {
Log.w(TAG, e);
return NETWORK_ERROR;
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private class CallClickListener implements View.OnClickListener {
private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1;
private static final int RATE_LIMIT_EXCEEDED = 2;
private static final int CREATE_ERROR = 3;
private final String e164number;
private final String password;
private final Context context;
public CallClickListener(String e164number) {
this.e164number = e164number;
this.password = Util.getSecret(18);
this.context = RegistrationProgressActivity.this;
}
@Override
public void onClick(View v) {
new AsyncTask<Void, Void, Integer>() {
private ProgressDialog progressDialog;
@Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(context,
getString(R.string.RegistrationProgressActivity_requesting_call),
getString(R.string.RegistrationProgressActivity_requesting_incoming_call),
true, false);
}
@Override
protected void onPostExecute(Integer result) {
if (progressDialog != null) progressDialog.dismiss();
switch (result) {
case SUCCESS:
Intent intent = new Intent(context, RegistrationService.class);
intent.setAction(RegistrationService.VOICE_REQUESTED_ACTION);
intent.putExtra(RegistrationService.NUMBER_EXTRA, e164number);
intent.putExtra(RegistrationService.PASSWORD_EXTRA, password);
intent.putExtra(RegistrationService.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(RegistrationService.GCM_SUPPORTED_EXTRA, gcmSupported);
startService(intent);
callButton.setEnabled(false);
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
callButton.setEnabled(true);
}
}, 15000);
break;
case NETWORK_ERROR:
Dialogs.showAlertDialog(context,
getString(R.string.RegistrationProgressActivity_network_error),
getString(R.string.RegistrationProgressActivity_unable_to_connect));
break;
case CREATE_ERROR:
Dialogs.showAlertDialog(context,
getString(R.string.RegistrationProgressActivity_server_error),
getString(R.string.RegistrationProgressActivity_the_server_encountered_an_error));
break;
case RATE_LIMIT_EXCEEDED:
Dialogs.showAlertDialog(context,
getString(R.string.RegistrationProgressActivity_too_many_requests),
getString(R.string.RegistrationProgressActivity_youve_already_requested_a_voice_call));
break;
}
}
@Override
protected Integer doInBackground(Void... params) {
try {
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context, e164number, password);
accountManager.requestVoiceVerificationCode();
return SUCCESS;
} catch (RateLimitException e) {
Log.w(TAG, e);
return RATE_LIMIT_EXCEEDED;
} catch (IOException e) {
Log.w(TAG, e);
return NETWORK_ERROR;
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}

@ -0,0 +1,17 @@
package org.thoughtcrime.securesms.animation;
import android.animation.Animator;
public abstract class AnimationCompleteListener implements Animator.AnimatorListener {
@Override
public final void onAnimationStart(Animator animation) {}
@Override
public abstract void onAnimationEnd(Animator animation);
@Override
public final void onAnimationCancel(Animator animation) {}
@Override
public final void onAnimationRepeat(Animator animation) {}
}

@ -76,7 +76,7 @@ class Tweener {
interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
} else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) {
updateListener = (AnimatorUpdateListener) value;
} else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
} else if ("onCodeComplete".equals(key) || "onCompleteListener".equals(key)) {
listener = (AnimatorListener) value;
} else if ("delay".equals(key)) {
delay = ((Number) value).longValue();

@ -0,0 +1,107 @@
package org.thoughtcrime.securesms.components.registration;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
public class CallMeCountDownView extends RelativeLayout {
private ImageView phone;
private TextView callMeText;
private TextView availableInText;
private TextView countDownText;
private int countDown;
private OnClickListener listener;
public CallMeCountDownView(Context context) {
super(context);
initialize();
}
public CallMeCountDownView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public CallMeCountDownView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CallMeCountDownView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.registration_call_me_view, this);
this.phone = findViewById(R.id.phone_icon);
this.callMeText = findViewById(R.id.call_me_text);
this.availableInText = findViewById(R.id.available_in_text);
this.countDownText = findViewById(R.id.countdown);
}
public void setOnClickListener(@Nullable OnClickListener listener) {
this.listener = listener;
}
public void startCountDown(int countDown) {
setVisibility(View.VISIBLE);
this.phone.setColorFilter(null);
this.phone.setOnClickListener(null);
this.callMeText.setTextColor(getResources().getColor(R.color.grey_700));
this.callMeText.setOnClickListener(null);
this.availableInText.setVisibility(View.VISIBLE);
this.countDownText.setVisibility(View.VISIBLE);
this.countDown = countDown;
updateCountDown();
}
public void setCallEnabled() {
setVisibility(View.VISIBLE);
this.phone.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN));
this.callMeText.setTextColor(getResources().getColor(R.color.signal_primary));
this.availableInText.setVisibility(View.GONE);
this.countDownText.setVisibility(View.GONE);
this.phone.setOnClickListener(v -> handlePhoneCallRequest());
this.callMeText.setOnClickListener(v -> handlePhoneCallRequest());
}
private void updateCountDown() {
if (countDown > 0) {
countDown--;
int minutesRemaining = countDown / 60;
int secondsRemaining = countDown - (minutesRemaining * 60);
countDownText.setText(String.format("%02d:%02d", minutesRemaining, secondsRemaining));
countDownText.postDelayed(this::updateCountDown, 1000);
} else if (countDown == 0) {
setCallEnabled();
}
}
private void handlePhoneCallRequest() {
if (listener != null) listener.onClick(this);
}
}

@ -0,0 +1,162 @@
package org.thoughtcrime.securesms.components.registration;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.OvershootInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.ArrayList;
import java.util.List;
public class VerificationCodeView extends FrameLayout {
private final List<View> spaces = new ArrayList<>(6);
private final List<TextView> codes = new ArrayList<>(6);
private final List<View> containers = new ArrayList<>(7);
private OnCodeEnteredListener listener;
private int index = 0;
public VerificationCodeView(Context context) {
super(context);
initialize(context, null);
}
public VerificationCodeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public VerificationCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
inflate(context, R.layout.verification_code_view, this);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView);
try {
TextView separator = findViewById(R.id.separator);
this.spaces.add(findViewById(R.id.space_zero));
this.spaces.add(findViewById(R.id.space_one));
this.spaces.add(findViewById(R.id.space_two));
this.spaces.add(findViewById(R.id.space_three));
this.spaces.add(findViewById(R.id.space_four));
this.spaces.add(findViewById(R.id.space_five));
this.codes.add(findViewById(R.id.code_zero));
this.codes.add(findViewById(R.id.code_one));
this.codes.add(findViewById(R.id.code_two));
this.codes.add(findViewById(R.id.code_three));
this.codes.add(findViewById(R.id.code_four));
this.codes.add(findViewById(R.id.code_five));
this.containers.add(findViewById(R.id.container_zero));
this.containers.add(findViewById(R.id.container_one));
this.containers.add(findViewById(R.id.container_two));
this.containers.add(findViewById(R.id.separator_container));
this.containers.add(findViewById(R.id.container_three));
this.containers.add(findViewById(R.id.container_four));
this.containers.add(findViewById(R.id.container_five));
Stream.of(spaces).forEach(view -> view.setBackgroundColor(typedArray.getColor(R.styleable.VerificationCodeView_vcv_inputColor, Color.BLACK)));
Stream.of(spaces).forEach(view -> view.setLayoutParams(new LinearLayout.LayoutParams(typedArray.getDimensionPixelSize(R.styleable.VerificationCodeView_vcv_inputWidth, ViewUtil.dpToPx(context, 20)),
typedArray.getDimensionPixelSize(R.styleable.VerificationCodeView_vcv_inputHeight, ViewUtil.dpToPx(context, 1)))));
Stream.of(codes).forEach(textView -> textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, typedArray.getDimension(R.styleable.VerificationCodeView_vcv_textSize, 30)));
Stream.of(codes).forEach(textView -> textView.setTextColor(typedArray.getColor(R.styleable.VerificationCodeView_vcv_textColor, Color.GRAY)));
Stream.of(containers).forEach(view -> {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)view.getLayoutParams();
params.setMargins(typedArray.getDimensionPixelSize(R.styleable.VerificationCodeView_vcv_spacing, ViewUtil.dpToPx(context, 5)),
params.topMargin, params.rightMargin, params.bottomMargin);
view.setLayoutParams(params);
});
separator.setTextSize(TypedValue.COMPLEX_UNIT_SP, typedArray.getDimension(R.styleable.VerificationCodeView_vcv_textSize, 30));
} finally {
if (typedArray != null) typedArray.recycle();
}
}
@MainThread
public void setOnCompleteListener(OnCodeEnteredListener listener) {
this.listener = listener;
}
@MainThread
public void append(int value) {
if (index >= codes.size()) return;
TextView codeView = codes.get(index++);
Animation translateIn = new TranslateAnimation(0, 0, codeView.getHeight(), 0);
translateIn.setInterpolator(new OvershootInterpolator());
translateIn.setDuration(500);
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setDuration(200);
AnimationSet animationSet = new AnimationSet(false);
animationSet.addAnimation(fadeIn);
animationSet.addAnimation(translateIn);
animationSet.reset();
animationSet.setStartTime(0);
codeView.setText(String.valueOf(value));
codeView.clearAnimation();
codeView.startAnimation(animationSet);
if (index == codes.size() && listener != null) {
listener.onCodeComplete(Stream.of(codes).map(TextView::getText).collect(Collectors.joining()));
}
}
@MainThread
public void delete() {
if (index <= 0) return;
codes.get(--index).setText("");
}
@MainThread
public void clear() {
if (index != 0) {
Stream.of(codes).forEach(code -> code.setText(""));
index = 0;
}
}
public interface OnCodeEnteredListener {
void onCodeComplete(@NonNull String code);
}
}

@ -0,0 +1,174 @@
package org.thoughtcrime.securesms.components.registration;
import android.content.Context;
import android.graphics.PorterDuff;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.OvershootInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
public class VerificationPinKeyboard extends FrameLayout {
private KeyboardView keyboardView;
private ProgressBar progressBar;
private ImageView successView;
private ImageView failureView;
private OnKeyPressListener listener;
public VerificationPinKeyboard(@NonNull Context context) {
super(context);
initialize();
}
public VerificationPinKeyboard(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
public VerificationPinKeyboard(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public VerificationPinKeyboard(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.verification_pin_keyboard_view, this);
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); ;
keyboardView.setPreviewEnabled(false);
keyboardView.setKeyboard(new Keyboard(getContext(), R.xml.pin_keyboard));
keyboardView.setOnKeyboardActionListener(new KeyboardView.OnKeyboardActionListener() {
@Override
public void onPress(int primaryCode) {
if (listener != null) listener.onKeyPress(primaryCode);
}
@Override
public void onRelease(int primaryCode) {}
@Override
public void onKey(int primaryCode, int[] keyCodes) {}
@Override
public void onText(CharSequence text) {}
@Override
public void swipeLeft() {}
@Override
public void swipeRight() {}
@Override
public void swipeDown() {}
@Override
public void swipeUp() {}
});
displayKeyboard();
}
public void setOnKeyPressListener(@Nullable OnKeyPressListener listener) {
this.listener = listener;
}
public void displayKeyboard() {
this.keyboardView.setVisibility(View.VISIBLE);
this.progressBar.setVisibility(View.GONE);
this.successView.setVisibility(View.GONE);
this.failureView.setVisibility(View.GONE);
}
public void displayProgress() {
this.keyboardView.setVisibility(View.INVISIBLE);
this.progressBar.setVisibility(View.VISIBLE);
this.successView.setVisibility(View.GONE);
this.failureView.setVisibility(View.GONE);
}
public ListenableFuture<Boolean> displaySuccess() {
SettableFuture<Boolean> result = new SettableFuture<>();
this.keyboardView.setVisibility(View.INVISIBLE);
this.progressBar.setVisibility(View.GONE);
this.failureView.setVisibility(View.GONE);
this.successView.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.successView, scaleAnimation);
return result;
}
public ListenableFuture<Boolean> displayFailure() {
SettableFuture<Boolean> result = new SettableFuture<>();
this.keyboardView.setVisibility(View.INVISIBLE);
this.progressBar.setVisibility(View.GONE);
this.failureView.setVisibility(View.GONE);
this.failureView.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
this.failureView.setVisibility(View.VISIBLE);
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) {
result.set(true);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
this.failureView.startAnimation(shake);
return result;
}
public interface OnKeyPressListener {
void onKeyPress(int keyCode);
}
}

@ -0,0 +1,61 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class PlayServicesUtil {
private static final String TAG = PlayServicesUtil.class.getSimpleName();
public enum PlayServicesStatus {
SUCCESS,
MISSING,
NEEDS_UPDATE,
TRANSIENT_ERROR
}
public static PlayServicesStatus getPlayServicesStatus(Context context) {
int gcmStatus = 0;
try {
gcmStatus = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
} catch (Throwable t) {
Log.w(TAG, t);
return PlayServicesStatus.MISSING;
}
Log.w(TAG, "Play Services: " + gcmStatus);
switch (gcmStatus) {
case ConnectionResult.SUCCESS:
return PlayServicesStatus.SUCCESS;
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
try {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo("com.google.android.gms", 0);
if (applicationInfo != null && !applicationInfo.enabled) {
return PlayServicesStatus.MISSING;
}
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, e);
}
return PlayServicesStatus.NEEDS_UPDATE;
case ConnectionResult.SERVICE_DISABLED:
case ConnectionResult.SERVICE_MISSING:
case ConnectionResult.SERVICE_INVALID:
case ConnectionResult.API_UNAVAILABLE:
case ConnectionResult.SERVICE_MISSING_PERMISSION:
return PlayServicesStatus.MISSING;
default:
return PlayServicesStatus.TRANSIENT_ERROR;
}
}
}
Loading…
Cancel
Save