Update registration flow
@ -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"
|
||||
|
9
res/anim/slide_to_left.xml
Normal file
@ -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>
|
BIN
res/drawable-hdpi/ic_action_name.png
Normal file
After Width: | Height: | Size: 297 B |
BIN
res/drawable-hdpi/ic_text_sms.png
Normal file
After Width: | Height: | Size: 311 B |
BIN
res/drawable-mdpi/ic_action_name.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
res/drawable-mdpi/ic_text_sms.png
Normal file
After Width: | Height: | Size: 224 B |
BIN
res/drawable-xhdpi/ic_action_name.png
Normal file
After Width: | Height: | Size: 335 B |
BIN
res/drawable-xhdpi/ic_text_sms.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
res/drawable-xxhdpi/ic_action_name.png
Normal file
After Width: | Height: | Size: 563 B |
BIN
res/drawable-xxhdpi/ic_text_sms.png
Normal file
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">
|
||||
|
||||
<TextView android:layout_width="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: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"/>
|
||||
<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"/>
|
||||
|
||||
<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/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">
|
||||
|
||||
<LinearLayout android:id="@+id/information_link_container"
|
||||
android:clickable="true"
|
||||
android:orientation="horizontal"
|
||||
<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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dip"
|
||||
android:layout_marginBottom="16dip"
|
||||
android:layout_gravity="right"
|
||||
android:orientation="horizontal">
|
||||
<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">
|
||||
|
||||
<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"/>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
45
res/layout/registration_call_me_view.xml
Normal file
@ -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>
|
154
res/layout/verification_code_view.xml
Normal file
@ -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>
|
43
res/layout/verification_pin_keyboard_view.xml
Normal file
@ -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 -->
|
||||
|
27
res/xml/pin_keyboard.xml
Normal file
@ -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 int SCENE_TRANSITION_DURATION = 250;
|
||||
public static final String CHALLENGE_EVENT = "org.thoughtcrime.securesms.CHALLENGE_EVENT";
|
||||
public static final String CHALLENGE_EXTRA = "CAAChallenge";
|
||||
|
||||
private static final int PICK_COUNTRY = 1;
|
||||
private static final String TAG = RegistrationActivity.class.getSimpleName();
|
||||
|
||||
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 CircularProgressButton createButton;
|
||||
private TextView informationView;
|
||||
private TextView informationToggleText;
|
||||
private TextView title;
|
||||
private TextView subtitle;
|
||||
private View registrationContainer;
|
||||
private View verificationContainer;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
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 MasterSecret masterSecret;
|
||||
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);
|
||||
TextView skipButton = findViewById(R.id.skip_button);
|
||||
View informationToggle = findViewById(R.id.information_link_container);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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((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;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
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(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(self,
|
||||
getString(R.string.RegistrationActivity_invalid_number),
|
||||
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid),
|
||||
e164number));
|
||||
return;
|
||||
}
|
||||
|
||||
PlayServicesStatus gcmStatus = checkPlayServices(self);
|
||||
|
||||
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));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
if (TextUtils.isEmpty(number.getText())) {
|
||||
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
promptForRegistrationStart(context, e164number, false);
|
||||
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;
|
||||
}
|
||||
});
|
||||
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
private PlayServicesStatus checkPlayServices(Context context) {
|
||||
int gcmStatus = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
registrationState = new RegistrationState(RegistrationState.State.VERIFYING, e164number, result.first, result.second);
|
||||
displayVerificationView(e164number, 64);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void handleChallengeReceived(@Nullable String challenge) {
|
||||
if (challenge != null && challenge.length() == 6 && registrationState.state == RegistrationState.State.VERIFYING) {
|
||||
verificationCodeView.clear();
|
||||
|
||||
try {
|
||||
gcmStatus = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, t);
|
||||
return PlayServicesStatus.MISSING;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "Play Services: " + gcmStatus);
|
||||
@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));
|
||||
|
||||
switch (gcmStatus) {
|
||||
case ConnectionResult.SUCCESS:
|
||||
return PlayServicesStatus.SUCCESS;
|
||||
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
|
||||
try {
|
||||
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo("com.google.android.gms", 0);
|
||||
DirectoryRefreshListener.schedule(RegistrationActivity.this);
|
||||
RotateSignedPreKeyListener.schedule(RegistrationActivity.this);
|
||||
|
||||
if (applicationInfo != null && !applicationInfo.enabled) {
|
||||
return PlayServicesStatus.MISSING;
|
||||
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
||||
|
||||
if (nextIntent == null) {
|
||||
nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class);
|
||||
}
|
||||
|
||||
startActivity(nextIntent);
|
||||
finish();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
});
|
||||
} 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
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
accountManager.requestVoiceVerificationCode();
|
||||
} catch (IOException 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;
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
61
src/org/thoughtcrime/securesms/util/PlayServicesUtil.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|