Initial GCM registration
@ -30,6 +30,13 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.permissionC2D_MESSAGE"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE" />
|
||||
|
||||
<application android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
@ -138,7 +145,7 @@
|
||||
android:label="@string/AndroidManifest__complete_key_exchange"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ApplicationPreferencesActivity"
|
||||
<activity android:name=".ApplicationPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".VerifyImportedIdentityActivity"
|
||||
@ -146,9 +153,18 @@
|
||||
android:label="@string/AndroidManifest__verify_imported_identity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".RegistrationActivity"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".RegistrationProgressActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:name=".service.KeyCachingService"/>
|
||||
<service android:enabled="true" android:name=".service.SendReceiveService"/>
|
||||
<service android:enabled="true" android:name=".service.RegistrationService"/>
|
||||
<service android:enabled="true" android:name=".gcm.GcmIntentService"/>
|
||||
|
||||
<!-- <receiver android:name=".service.BootListener" -->
|
||||
<!-- android:enabled="true" -->
|
||||
@ -158,6 +174,14 @@
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
|
||||
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
||||
<category android:name="org.thoughtcrime.securesms" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.SmsListener"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
|
BIN
assets/whisper.store
Normal file
BIN
libs/gcm.jar
Normal file
BIN
libs/libphonenumber-5.3.jar
Normal file
BIN
libs/thoughtcrimegson-2.1.jar
Normal file
BIN
res/drawable-hdpi/alert.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-hdpi/check.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-hdpi/ic_menu_search_holo_light.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-mdpi/alert.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/check.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-mdpi/ic_menu_search_holo_light.png
Normal file
After Width: | Height: | Size: 858 B |
BIN
res/drawable-xhdpi/alert.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
res/drawable-xhdpi/check.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-xhdpi/ic_menu_search_holo_light.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable/background_pane.9.png
Normal file
After Width: | Height: | Size: 570 B |
28
res/layout/country_list_item.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dip"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView android:id="@+id/country_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView android:id="@+id/country_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18.0sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#7b7b7b"
|
||||
android:gravity="right|center"
|
||||
android:layout_marginLeft="4dip"
|
||||
android:layout_marginRight="5dip"
|
||||
android:layout_gravity="right|center"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
10
res/layout/country_selection.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<fragment android:id="@+id/fragment_content"
|
||||
android:name="org.thoughtcrime.securesms.CountrySelectionFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</FrameLayout>
|
29
res/layout/country_selection_fragment.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#ffffff">
|
||||
|
||||
<EditText android:id="@+id/country_search"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@drawable/ic_menu_search_holo_light"
|
||||
android:hint="Search" />
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:divider="#ffdddddd"
|
||||
android:dividerHeight="1.0px"
|
||||
android:choiceMode="singleChoice" />
|
||||
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="Loading countries..." />
|
||||
|
||||
|
||||
</LinearLayout>
|
83
res/layout/registration_activity.xml
Normal file
@ -0,0 +1,83 @@
|
||||
<?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">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center" >
|
||||
|
||||
<LinearLayout android:paddingRight="16dip"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingTop="10dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView style="@style/Registration.Description"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_marginBottom="16dip"
|
||||
android:layout_marginTop="16dip"
|
||||
android:text="Please confirm your country code and phone number."/>
|
||||
|
||||
<TextView style="@style/Registration.Label"
|
||||
android:layout_width="fill_parent"
|
||||
android:textAllCaps="true"
|
||||
android:text="YOUR COUNTRY" />
|
||||
|
||||
<Spinner android:id="@+id/country_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dip" />
|
||||
|
||||
<TextView style="@style/Registration.Label"
|
||||
android:layout_width="fill_parent"
|
||||
android:textAllCaps="true"
|
||||
android:text="YOUR COUNTRY CODE AND PHONE NUMBER" />
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
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" />
|
||||
|
||||
<EditText android:id="@+id/number"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="phone"
|
||||
android:hint="PHONE NUMBER"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button style="@android:style/Widget.Button"
|
||||
android:id="@+id/registerButton"
|
||||
android:text="Register"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_marginTop="20dip"
|
||||
android:layout_marginBottom="20dip"/>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
517
res/layout/registration_progress_activity.xml
Normal file
@ -0,0 +1,517 @@
|
||||
<?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:background="@drawable/background_pattern_repeat"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/verification_failure_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" >
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/background_pane" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/alert"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:src="@drawable/alert" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/alert"
|
||||
android:layout_toRightOf="@id/alert"
|
||||
android:paddingLeft="4.0dip"
|
||||
android:paddingRight="8.0dip"
|
||||
android:text="SMS verification failed."
|
||||
android:textColor="#333333"
|
||||
android:textSize="16.0sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/alert"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:text="TextSecure timed out while waiting for an SMS message to verify your phone number." />
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/verification_failure_edit_button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="10.0dip"
|
||||
android:gravity="center"
|
||||
android:text="Edit number"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:text="Some possible problems include:"/>
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="•" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SMS Interceptors."
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="Some third party text messaging clients, such as Handcent or GoSMS, behave poorly and intercept all incoming SMS messages. Check to see if you received a text message that starts with 'Your TextSecure verification code:', in which case you'll need to configure your third party text messaging app to let text messages through." />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="•" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Incorrect number."
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="Please check to make sure you entered your number correctly, and that it is formatted for correctly your region." />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="•" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Google Voice."
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="TextSecure will not work with Google Voice numbers." />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/connectivity_failure_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" >
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/background_pane" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/connectivity_alert"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:src="@drawable/alert" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/connectivity_alert"
|
||||
android:layout_toRightOf="@id/connectivity_alert"
|
||||
android:paddingLeft="4.0dip"
|
||||
android:paddingRight="8.0dip"
|
||||
android:text="Connectivity error."
|
||||
android:textColor="#333333"
|
||||
android:textSize="16.0sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/connectivity_alert"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:text="RedPhone was unable to connect to the switch." />
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/connectivity_failure_edit_button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="10.0dip"
|
||||
android:gravity="center"
|
||||
android:text="Edit number"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:text="Some possible problems include:" />
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="•" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No network connectivity."
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="Your device needs network connectivity in order to use this TextSecure feature. Check to ensure that it is connected to 3G or Wifi." />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="•" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Restrictive firewall."
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dip"
|
||||
android:text="If you are connected via wifi, it's possible that there is a firewall blocking access to the TextSecure server. Try another network or mobile data." />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/registering_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingRight="16dip" >
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:paddingLeft="5dip"
|
||||
android:text="TextSecure will now automatically verify your number with a confirmation SMS message." />
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/background_pane"
|
||||
android:gravity="center" >
|
||||
|
||||
<TableRow>
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center" >
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/connecting_complete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:src="@drawable/check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/connecting_progress"
|
||||
style="?android:attr/android:progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:visibility="invisible" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connecting_text"
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="4.0dip"
|
||||
android:paddingRight="8.0dip"
|
||||
android:text="Registering with server..."
|
||||
android:textSize="16.0sp" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/verification_complete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:src="@drawable/check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/verification_progress"
|
||||
style="?android:attr/android:progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:visibility="invisible" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verification_text"
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="4.0dip"
|
||||
android:paddingRight="8.0dip"
|
||||
android:text="Waiting for SMS verification..."
|
||||
android:textSize="16.0sp" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gcm_registering_complete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:src="@drawable/check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/gcm_registering_progress"
|
||||
style="?android:attr/android:progressBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:visibility="invisible" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gcm_registering_text"
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="4.0dip"
|
||||
android:paddingRight="8.0dip"
|
||||
android:text="Registering for push..."
|
||||
android:textSize="16.0sp" />
|
||||
</TableRow>
|
||||
|
||||
</TableLayout>
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Description"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="5dip"
|
||||
android:text="This could take a moment. Please be patient, we'll notify you when verification is complete." />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/timer_progress_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dip"
|
||||
android:layout_weight="1.0" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/registration_timer"
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:text="1:57"
|
||||
android:textSize="12.0sp" />
|
||||
|
||||
<TextView
|
||||
style="@style/Registration.Constant"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:text="Waiting for SMS verification..."
|
||||
android:textAllCaps="true"
|
||||
android:textSize="12.0sp"
|
||||
android:textStyle="normal" />
|
||||
</RelativeLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/registration_progress"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="14.0dip"
|
||||
android:layout_marginTop="2.0dip" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/edit_button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="6.0dip"
|
||||
android:layout_weight="1.0"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:text="Edit number"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</ScrollView>
|
@ -90,6 +90,11 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
|
||||
public static final String THREAD_TRIM_NOW = "pref_trim_now";
|
||||
|
||||
public static final String LOCAL_NUMBER_PREF = "pref_local_number";
|
||||
public static final String VERIFYING_STATE_PREF = "pref_verifying";
|
||||
public static final String REGISTERED_GCM_PREF = "pref_gcm_registered";
|
||||
public static final String GCM_PASSWORD_PREF = "pref_gcm_password";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
29
src/org/thoughtcrime/securesms/CountrySelectionActivity.java
Normal file
@ -0,0 +1,29 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
public class CountrySelectionActivity extends SherlockFragmentActivity
|
||||
implements CountrySelectionFragment.CountrySelectedListener
|
||||
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.setContentView(R.layout.country_selection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void countrySelected(String countryName, int countryCode) {
|
||||
Intent result = getIntent();
|
||||
result.putExtra("country_name", countryName);
|
||||
result.putExtra("country_code", countryCode);
|
||||
|
||||
this.setResult(RESULT_OK, result);
|
||||
this.finish();
|
||||
}
|
||||
}
|
100
src/org/thoughtcrime/securesms/CountrySelectionFragment.java
Normal file
@ -0,0 +1,100 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleAdapter;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.database.loaders.CountryListLoader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
public class CountrySelectionFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<ArrayList<Map<String, String>>> {
|
||||
|
||||
private EditText countryFilter;
|
||||
private CountrySelectedListener listener;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
return inflater.inflate(R.layout.country_selection_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle bundle) {
|
||||
super.onActivityCreated(bundle);
|
||||
this.countryFilter = (EditText)getView().findViewById(R.id.country_search);
|
||||
this.countryFilter.addTextChangedListener(new FilterWatcher());
|
||||
getLoaderManager().initLoader(0, null, this).forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
this.listener = (CountrySelectedListener)activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
Map<String, String> item = (Map<String, String>)this.getListAdapter().getItem(position);
|
||||
if (this.listener != null) {
|
||||
this.listener.countrySelected(item.get("country_name"),
|
||||
Integer.parseInt(item.get("country_code").substring(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<ArrayList<Map<String, String>>> onCreateLoader(int arg0, Bundle arg1) {
|
||||
return new CountryListLoader(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<ArrayList<Map<String, String>>> loader,
|
||||
ArrayList<Map<String, String>> results)
|
||||
{
|
||||
String[] from = {"country_name", "country_code"};
|
||||
int[] to = {R.id.country_name, R.id.country_code};
|
||||
this.setListAdapter(new SimpleAdapter(getActivity(), results, R.layout.country_list_item, from, to));
|
||||
|
||||
if (this.countryFilter != null && this.countryFilter.getText().length() != 0) {
|
||||
((SimpleAdapter)getListAdapter()).getFilter().filter(this.countryFilter.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<ArrayList<Map<String, String>>> arg0) {
|
||||
this.setListAdapter(null);
|
||||
}
|
||||
|
||||
public interface CountrySelectedListener {
|
||||
public void countrySelected(String countryName, int countryCode);
|
||||
}
|
||||
|
||||
private class FilterWatcher implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (getListAdapter() != null) {
|
||||
((SimpleAdapter)getListAdapter()).getFilter().filter(s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
}
|
||||
}
|
257
src/org/thoughtcrime/securesms/RegistrationActivity.java
Normal file
@ -0,0 +1,257 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
import com.google.android.gcm.GCMRegistrar;
|
||||
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import org.thoughtcrime.securesms.util.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
/**
|
||||
* The create account activity. Kicks off an account creation event, then waits
|
||||
* the server to respond with a challenge via SMS, receives the challenge, and
|
||||
* verifies it with the server.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class RegistrationActivity extends SherlockActivity {
|
||||
|
||||
private static final int PICK_COUNTRY = 1;
|
||||
|
||||
private AsYouTypeFormatter countryFormatter;
|
||||
private ArrayAdapter<String> countrySpinnerAdapter;
|
||||
private Spinner countrySpinner;
|
||||
private TextView countryCode;
|
||||
private TextView number;
|
||||
private Button createButton;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
setContentView(R.layout.registration_activity);
|
||||
|
||||
ActionBar actionBar = this.getSupportActionBar();
|
||||
actionBar.setTitle("Connect With TextSecure");
|
||||
|
||||
initializeResources();
|
||||
initializeNumber();
|
||||
}
|
||||
|
||||
@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)+"");
|
||||
setCountryDisplay(data.getStringExtra("country_name"));
|
||||
setCountryFormatter(data.getIntExtra("country_code", 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
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 = (Button)findViewById(R.id.registerButton);
|
||||
|
||||
this.countrySpinnerAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item);
|
||||
this.countrySpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
setCountryDisplay("Select Your Country");
|
||||
|
||||
this.countrySpinner.setAdapter(this.countrySpinnerAdapter);
|
||||
this.countrySpinner.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
|
||||
startActivityForResult(intent, PICK_COUNTRY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
this.countryCode.addTextChangedListener(new CountryCodeChangedListener());
|
||||
this.number.addTextChangedListener(new NumberChangedListener());
|
||||
this.createButton.setOnClickListener(new CreateButtonListener());
|
||||
}
|
||||
|
||||
|
||||
private void initializeNumber() {
|
||||
String localNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
|
||||
.getLine1Number();
|
||||
|
||||
if (!Util.isEmpty(localNumber) && !localNumber.startsWith("+")) {
|
||||
if (localNumber.length() == 10) localNumber = "+1" + localNumber;
|
||||
else localNumber = "+" + localNumber;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!Util.isEmpty(localNumber)) {
|
||||
PhoneNumberUtil numberUtil = PhoneNumberUtil.getInstance();
|
||||
Phonenumber.PhoneNumber localNumberObject = numberUtil.parse(localNumber, null);
|
||||
|
||||
if (localNumberObject != null) {
|
||||
this.countryCode.setText(localNumberObject.getCountryCode()+"");
|
||||
this.number.setText(localNumberObject.getNationalNumber()+"");
|
||||
}
|
||||
}
|
||||
} catch (NumberParseException npe) {
|
||||
Log.w("CreateAccountActivity", npe);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCountryDisplay(String value) {
|
||||
this.countrySpinnerAdapter.clear();
|
||||
this.countrySpinnerAdapter.add(value);
|
||||
}
|
||||
|
||||
private void setCountryFormatter(int countryCode) {
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
String regionCode = util.getRegionCodeForCountryCode(countryCode);
|
||||
|
||||
if (regionCode == null) {
|
||||
this.countryFormatter = null;
|
||||
} else {
|
||||
this.countryFormatter = util.getAsYouTypeFormatter(regionCode);
|
||||
}
|
||||
}
|
||||
|
||||
private String getConfiguredE164Number() {
|
||||
return PhoneNumberFormatter.formatE164(countryCode.getText().toString(),
|
||||
number.getText().toString());
|
||||
}
|
||||
|
||||
private class CreateButtonListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final RegistrationActivity self = RegistrationActivity.this;
|
||||
|
||||
if (Util.isEmpty(countryCode.getText())) {
|
||||
Toast.makeText(self, "You must specify your country code",
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Util.isEmpty(number.getText())) {
|
||||
Toast.makeText(self, "You must specify your phone number",
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
final String e164number = getConfiguredE164Number();
|
||||
|
||||
if (!PhoneNumberFormatter.isValidNumber(e164number)) {
|
||||
Util.showAlertDialog(self,
|
||||
"Invalid number",
|
||||
String.format("The number you specified (%s) is invalid.", e164number));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
GCMRegistrar.checkDevice(self);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
Util.showAlertDialog(self, "Unsupported", "Sorry, this device is not supported for data messaging. Devices running versions of Android older than 4.0 must have a registered Google Account. Devices running Android 4.0 or newer do not require a Google Account, but must have the Play Store app installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(self);
|
||||
dialog.setMessage(String.format("We will now verify that the following number is associated with this device:\n\n%s\n\nIs this number correct, or would you like to edit it before continuing?",
|
||||
PhoneNumberFormatter.getInternationalFormatFromE164(e164number)));
|
||||
dialog.setPositiveButton("Continue",
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(self, RegistrationProgressActivity.class);
|
||||
intent.putExtra("e164number", e164number);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
dialog.setNegativeButton("Edit", null);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
private class CountryCodeChangedListener implements TextWatcher {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (Util.isEmpty(s)) {
|
||||
setCountryDisplay("Select your country");
|
||||
countryFormatter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int countryCode = Integer.parseInt(s.toString());
|
||||
String regionCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(countryCode);
|
||||
setCountryFormatter(countryCode);
|
||||
setCountryDisplay(PhoneNumberFormatter.getRegionDisplayName(regionCode));
|
||||
|
||||
if (!Util.isEmpty(regionCode) && !regionCode.equals("ZZ")) {
|
||||
number.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
}
|
||||
|
||||
private class NumberChangedListener implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (countryFormatter == null)
|
||||
return;
|
||||
|
||||
if (Util.isEmpty(s))
|
||||
return;
|
||||
|
||||
countryFormatter.clear();
|
||||
|
||||
String number = s.toString().replaceAll("[^\\d.]", "");
|
||||
String formattedNumber = null;
|
||||
|
||||
for (int i=0;i<number.length();i++) {
|
||||
formattedNumber = countryFormatter.inputDigit(number.charAt(i));
|
||||
}
|
||||
|
||||
if (!s.toString().equals(formattedNumber)) {
|
||||
s.replace(0, s.length(), formattedNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
327
src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
Normal file
@ -0,0 +1,327 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
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.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
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 com.actionbarsherlock.app.SherlockActivity;
|
||||
import org.thoughtcrime.securesms.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.util.PhoneNumberFormatter;
|
||||
|
||||
import static org.thoughtcrime.securesms.service.RegistrationService.RegistrationState;
|
||||
|
||||
public class RegistrationProgressActivity extends SherlockActivity {
|
||||
|
||||
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 gcmRegistrationProgress;
|
||||
|
||||
private ImageView connectingCheck;
|
||||
private ImageView verificationCheck;
|
||||
private ImageView gcmRegistrationCheck;
|
||||
|
||||
private TextView connectingText;
|
||||
private TextView verificationText;
|
||||
private TextView registrationTimerText;
|
||||
private TextView gcmRegistrationText;
|
||||
|
||||
private Button editButton;
|
||||
private Button verificationFailureButton;
|
||||
private Button connectivityFailureButton;
|
||||
|
||||
private volatile boolean visible;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.getSupportActionBar().setTitle("Verifying number");
|
||||
setContentView(R.layout.registration_progress_activity);
|
||||
|
||||
initializeResources();
|
||||
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.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.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.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.gcmRegistrationText = (TextView) findViewById(R.id.gcm_registering_text);
|
||||
this.editButton = (Button) findViewById(R.id.edit_button);
|
||||
this.verificationFailureButton = (Button) findViewById(R.id.verification_failure_edit_button);
|
||||
this.connectivityFailureButton = (Button) findViewById(R.id.connectivity_failure_edit_button);
|
||||
this.timeoutProgressLayout = (RelativeLayout) findViewById(R.id.timer_progress_layout);
|
||||
|
||||
this.editButton.setOnClickListener(new EditButtonListener());
|
||||
this.verificationFailureButton.setOnClickListener(new EditButtonListener());
|
||||
this.connectivityFailureButton.setOnClickListener(new EditButtonListener());
|
||||
}
|
||||
|
||||
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("e164number", getNumberDirective());
|
||||
startService(intent);
|
||||
} else {
|
||||
startActivity(new Intent(this, RegistrationActivity.class));
|
||||
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.gcmRegistrationProgress.setVisibility(View.INVISIBLE);
|
||||
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
|
||||
this.connectingText.setTextColor(FOCUSED_COLOR);
|
||||
this.verificationText.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.gcmRegistrationProgress.setVisibility(View.INVISIBLE);
|
||||
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
|
||||
this.connectingText.setTextColor(UNFOCUSED_COLOR);
|
||||
this.verificationText.setTextColor(FOCUSED_COLOR);
|
||||
this.gcmRegistrationText.setTextColor(UNFOCUSED_COLOR);
|
||||
this.registrationProgress.setVisibility(View.VISIBLE);
|
||||
this.timeoutProgressLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
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.gcmRegistrationProgress.setVisibility(View.VISIBLE);
|
||||
this.gcmRegistrationCheck.setVisibility(View.INVISIBLE);
|
||||
this.connectingText.setTextColor(UNFOCUSED_COLOR);
|
||||
this.verificationText.setTextColor(UNFOCUSED_COLOR);
|
||||
this.gcmRegistrationText.setTextColor(FOCUSED_COLOR);
|
||||
this.registrationProgress.setVisibility(View.INVISIBLE);
|
||||
this.timeoutProgressLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
private void handleGcmTimeout(String number) {
|
||||
handleConnectivityError(number);
|
||||
}
|
||||
|
||||
private void handleVerificationTimeout(String number) {
|
||||
this.registrationLayout.setVisibility(View.GONE);
|
||||
this.connectivityFailureLayout.setVisibility(View.GONE);
|
||||
this.verificationFailureLayout.setVisibility(View.VISIBLE);
|
||||
this.verificationFailureButton.setText(String.format("Edit %s",
|
||||
PhoneNumberFormatter.formatNumberInternational(number)));
|
||||
}
|
||||
|
||||
private void handleConnectivityError(String number) {
|
||||
this.registrationLayout.setVisibility(View.GONE);
|
||||
this.verificationFailureLayout.setVisibility(View.GONE);
|
||||
this.connectivityFailureLayout.setVisibility(View.VISIBLE);
|
||||
this.connectivityFailureButton.setText(String.format("Edit %s",
|
||||
PhoneNumberFormatter.formatNumberInternational(number)));
|
||||
}
|
||||
|
||||
private void handleVerificationComplete() {
|
||||
if (visible) {
|
||||
Toast.makeText(this, "Registration complete", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
shutdownService();
|
||||
startActivity(new Intent(this, RoutingActivity.class));
|
||||
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("e164number") != null;
|
||||
}
|
||||
|
||||
private String getNumberDirective() {
|
||||
return getIntent().getStringExtra("e164number");
|
||||
}
|
||||
|
||||
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.number).sendToTarget();
|
||||
|
||||
handleTimerUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
registrationService.setRegistrationStateHandler(null);
|
||||
}
|
||||
}
|
||||
|
||||
private class RegistrationStateHandler extends Handler {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
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_GCM_REGISTERING: handleStateGcmRegistering(); break;
|
||||
case RegistrationState.STATE_TIMEOUT: handleVerificationTimeout((String)message.obj); break;
|
||||
case RegistrationState.STATE_COMPLETE: handleVerificationComplete(); break;
|
||||
case RegistrationState.STATE_GCM_TIMEOUT: handleGcmTimeout((String)message.obj); break;
|
||||
case RegistrationState.STATE_NETWORK_ERROR: handleConnectivityError((String)message.obj); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EditButtonListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
shutdownService();
|
||||
|
||||
Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class);
|
||||
startActivity(activityIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private class RegistrationReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
@ -94,18 +94,22 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
|
||||
}
|
||||
|
||||
private void handleDisplayConversationOrList() {
|
||||
ConversationParameters parameters = getConversationParameters();
|
||||
|
||||
Intent intent;
|
||||
|
||||
if (isShareAction() || parameters.recipients != null) {
|
||||
intent = getConversationIntent(parameters);
|
||||
} else {
|
||||
intent = getConversationListIntent();
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, RegistrationActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
// ConversationParameters parameters = getConversationParameters();
|
||||
//
|
||||
// Intent intent;
|
||||
//
|
||||
// if (isShareAction() || parameters.recipients != null) {
|
||||
// intent = getConversationIntent(parameters);
|
||||
// } else {
|
||||
// intent = getConversationListIntent();
|
||||
// }
|
||||
//
|
||||
// startActivity(intent);
|
||||
// finish();
|
||||
}
|
||||
|
||||
private Intent getConversationIntent(ConversationParameters parameters) {
|
||||
|
@ -0,0 +1,47 @@
|
||||
package org.thoughtcrime.securesms.database.loaders;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import org.thoughtcrime.securesms.util.PhoneNumberFormatter;
|
||||
|
||||
public class CountryListLoader extends AsyncTaskLoader<ArrayList<Map<String, String>>> {
|
||||
|
||||
public CountryListLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Map<String, String>> loadInBackground() {
|
||||
Set<String> regions = PhoneNumberUtil.getInstance().getSupportedRegions();
|
||||
ArrayList<Map<String, String>> results = new ArrayList<Map<String, String>>(regions.size());
|
||||
|
||||
for (String region : regions) {
|
||||
Map<String, String> data = new HashMap<String, String>(2);
|
||||
data.put("country_name", PhoneNumberFormatter.getRegionDisplayName(region));
|
||||
data.put("country_code", "+" +PhoneNumberUtil.getInstance().getCountryCodeForRegion(region));
|
||||
results.add(data);
|
||||
}
|
||||
|
||||
Collections.sort(results, new RegionComparator());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private class RegionComparator implements Comparator<Map<String, String>> {
|
||||
@Override
|
||||
public int compare(Map<String, String> lhs, Map<String, String> rhs) {
|
||||
return lhs.get("country_name").compareTo(rhs.get("country_name"));
|
||||
}
|
||||
}
|
||||
}
|
12
src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java
Normal file
@ -0,0 +1,12 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class GcmBroadcastReceiver extends com.google.android.gcm.GCMBroadcastReceiver {
|
||||
|
||||
@Override
|
||||
protected String getGCMIntentServiceClassName(Context context) {
|
||||
return "org.thoughtcrime.securesms.gcm.GcmIntentService";
|
||||
}
|
||||
|
||||
}
|
45
src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
Normal file
@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.gcm.GCMBaseIntentService;
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.service.RegistrationService;
|
||||
|
||||
public class GcmIntentService extends GCMBaseIntentService {
|
||||
|
||||
public static final String GCM_SENDER_ID = "312334754206";
|
||||
|
||||
@Override
|
||||
protected void onRegistered(Context context, String registrationId) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (!preferences.getBoolean(ApplicationPreferencesActivity.REGISTERED_GCM_PREF, false)) {
|
||||
Intent intent = new Intent(RegistrationService.GCM_REGISTRATION_EVENT);
|
||||
intent.putExtra(RegistrationService.GCM_REGISTRATION_ID, registrationId);
|
||||
sendBroadcast(intent);
|
||||
} else {
|
||||
//
|
||||
// // Talk to the server directly.
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMessage(Context context, Intent intent) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onError(Context context, String s) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onUnregistered(Context context, String s) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
public class GcmRegistrationTimeoutException extends Exception {
|
||||
public GcmRegistrationTimeoutException() {
|
||||
}
|
||||
|
||||
public GcmRegistrationTimeoutException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public GcmRegistrationTimeoutException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public GcmRegistrationTimeoutException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
20
src/org/thoughtcrime/securesms/gcm/GcmSender.java
Normal file
@ -0,0 +1,20 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
public class GcmSender {
|
||||
|
||||
private static final GcmSender instance = new GcmSender();
|
||||
|
||||
public static GcmSender getDefault() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void sendTextMessage(String recipient, String text,
|
||||
PendingIntent sentIntent,
|
||||
PendingIntent deliveredIntent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
154
src/org/thoughtcrime/securesms/gcm/RegistrationSocket.java
Normal file
@ -0,0 +1,154 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class RegistrationSocket {
|
||||
|
||||
private static final String CREATE_ACCOUNT_PATH = "/v1/accounts/%s";
|
||||
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/%s";
|
||||
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/%s";
|
||||
|
||||
private final String localNumber;
|
||||
private final String password;
|
||||
private final TrustManagerFactory trustManagerFactory;
|
||||
|
||||
public RegistrationSocket(Context context, String localNumber, String password) {
|
||||
this.localNumber = localNumber;
|
||||
this.password = password;
|
||||
this.trustManagerFactory = initializeTrustManagerFactory(context);
|
||||
}
|
||||
|
||||
public void createAccount() throws IOException {
|
||||
makeRequest(String.format(CREATE_ACCOUNT_PATH, localNumber), "POST", null);
|
||||
}
|
||||
|
||||
public void verifyAccount(String verificationCode, String password)
|
||||
throws IOException
|
||||
{
|
||||
Verification verification = new Verification(verificationCode, password);
|
||||
makeRequest(String.format(VERIFY_ACCOUNT_PATH, localNumber), "PUT", new Gson().toJson(verification));
|
||||
}
|
||||
|
||||
public void registerGcmId(String gcmRegistrationId) throws IOException {
|
||||
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
|
||||
makeRequest(String.format(REGISTER_GCM_PATH, localNumber), "PUT", new Gson().toJson(registration));
|
||||
}
|
||||
|
||||
private TrustManagerFactory initializeTrustManagerFactory(Context context) {
|
||||
try {
|
||||
AssetManager assetManager = context.getAssets();
|
||||
InputStream keyStoreInputStream = assetManager.open("whisper.store");
|
||||
KeyStore trustStore = KeyStore.getInstance("BKS");
|
||||
|
||||
trustStore.load(keyStoreInputStream, "whisper".toCharArray());
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
|
||||
trustManagerFactory.init(trustStore);
|
||||
|
||||
return trustManagerFactory;
|
||||
} catch (KeyStoreException kse) {
|
||||
throw new AssertionError(kse);
|
||||
} catch (CertificateException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IOException ioe) {
|
||||
throw new AssertionError(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private String makeRequest(String urlFragment, String method, String body) throws IOException {
|
||||
try {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, trustManagerFactory.getTrustManagers(), null);
|
||||
|
||||
URL url = new URL(String.format("https://gcm.textsecure.whispersystems.org/%s", urlFragment));
|
||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||
connection.setSSLSocketFactory(context.getSocketFactory());
|
||||
connection.setRequestMethod(method);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
|
||||
if (password != null) {
|
||||
connection.setRequestProperty("Authorization", getAuthorizationHeader());
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
connection.setDoOutput(true);
|
||||
}
|
||||
|
||||
connection.connect();
|
||||
|
||||
if (body != null) {
|
||||
OutputStream out = connection.getOutputStream();
|
||||
out.write(body.getBytes());
|
||||
out.close();
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() != 200) {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode());
|
||||
}
|
||||
|
||||
return Util.readFully(connection.getInputStream());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (KeyManagementException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAuthorizationHeader() {
|
||||
try {
|
||||
return "Basic " + new String(Base64.encode((localNumber + ":" + password).getBytes("UTF-8"), Base64.NO_WRAP));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class Verification {
|
||||
|
||||
private String verificationCode;
|
||||
private String authenticationToken;
|
||||
|
||||
public Verification() {}
|
||||
|
||||
public Verification(String verificationCode,
|
||||
String authenticationToken)
|
||||
{
|
||||
this.verificationCode = verificationCode;
|
||||
this.authenticationToken = authenticationToken;
|
||||
}
|
||||
}
|
||||
|
||||
private class GcmRegistrationId {
|
||||
private String gcmRegistrationId;
|
||||
|
||||
public GcmRegistrationId() {}
|
||||
|
||||
public GcmRegistrationId(String gcmRegistrationId) {
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
public class AccountVerificationTimeoutException extends Exception {
|
||||
public AccountVerificationTimeoutException() {
|
||||
}
|
||||
|
||||
public AccountVerificationTimeoutException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public AccountVerificationTimeoutException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public AccountVerificationTimeoutException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
330
src/org/thoughtcrime/securesms/service/RegistrationService.java
Normal file
@ -0,0 +1,330 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gcm.GCMRegistrar;
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.gcm.GcmIntentService;
|
||||
import org.thoughtcrime.securesms.gcm.GcmRegistrationTimeoutException;
|
||||
import org.thoughtcrime.securesms.gcm.RegistrationSocket;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* The RegisterationService handles the actual process of registration. If it receives an
|
||||
* intent with a REGISTER_NUMBER_ACTION, it does the following through an executor:
|
||||
*
|
||||
* 1) Generate secrets.
|
||||
* 2) Register the specified number and those secrets with the server.
|
||||
* 3) Wait for a challenge SMS.
|
||||
* 4) Verify the challenge with the server.
|
||||
* 5) Start the GCM registration process.
|
||||
* 6) Retrieve the current directory.
|
||||
*
|
||||
* The RegistrationService broadcasts its state throughout this process, and also makes its
|
||||
* state available through service binding. This enables a View to display progress.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class RegistrationService extends Service {
|
||||
|
||||
public static final String NOTIFICATION_TITLE = "org.thoughtcrime.securesms.NOTIFICATION_TITLE";
|
||||
public static final String NOTIFICATION_TEXT = "org.thoughtcrime.securesms.NOTIFICATION_TEXT";
|
||||
public static final String REGISTER_NUMBER_ACTION = "org.thoughtcrime.securesms.RegistrationService.REGISTER_NUMBER";
|
||||
public static final String CHALLENGE_EVENT = "org.thoughtcrime.securesms.CHALLENGE_EVENT";
|
||||
public static final String REGISTRATION_EVENT = "org.thoughtcrime.securesms.REGISTRATION_EVENT";
|
||||
public static final String GCM_REGISTRATION_EVENT = "org.thoughtcrime.securesms.GCM_REGISTRATION_EVENT";
|
||||
|
||||
public static final String CHALLENGE_EXTRA = "CAAChallenge";
|
||||
public static final String GCM_REGISTRATION_ID = "GCMRegistrationId";
|
||||
|
||||
private static final long REGISTRATION_TIMEOUT_MILLIS = 120000;
|
||||
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final Binder binder = new RegistrationServiceBinder();
|
||||
|
||||
private volatile RegistrationState registrationState = new RegistrationState(RegistrationState.STATE_IDLE);
|
||||
|
||||
private volatile Handler registrationStateHandler;
|
||||
private volatile ChallengeReceiver challengeReceiver;
|
||||
private volatile GcmRegistrationReceiver gcmRegistrationReceiver;
|
||||
private String challenge;
|
||||
private String gcmRegistrationId;
|
||||
private long verificationStartTime;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||
if (intent != null && intent.getAction().equals(REGISTER_NUMBER_ACTION)) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleRegistrationIntent(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
executor.shutdown();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
shutdownChallengeListener();
|
||||
markAsVerifying(false);
|
||||
registrationState = new RegistrationState(RegistrationState.STATE_IDLE);
|
||||
}
|
||||
|
||||
public synchronized int getSecondsRemaining() {
|
||||
long millisPassed;
|
||||
|
||||
if (verificationStartTime == 0) millisPassed = 0;
|
||||
else millisPassed = System.currentTimeMillis() - verificationStartTime;
|
||||
|
||||
return Math.max((int)(REGISTRATION_TIMEOUT_MILLIS - millisPassed) / 1000, 0);
|
||||
}
|
||||
|
||||
public RegistrationState getRegistrationState() {
|
||||
return registrationState;
|
||||
}
|
||||
|
||||
private void initializeChallengeListener() {
|
||||
this.challenge = null;
|
||||
challengeReceiver = new ChallengeReceiver();
|
||||
IntentFilter filter = new IntentFilter(CHALLENGE_EVENT);
|
||||
registerReceiver(challengeReceiver, filter);
|
||||
}
|
||||
|
||||
private void initializeGcmRegistrationListener() {
|
||||
this.gcmRegistrationId = null;
|
||||
gcmRegistrationReceiver = new GcmRegistrationReceiver();
|
||||
IntentFilter filter = new IntentFilter(GCM_REGISTRATION_EVENT);
|
||||
registerReceiver(gcmRegistrationReceiver, filter);
|
||||
}
|
||||
|
||||
private void shutdownChallengeListener() {
|
||||
if (challengeReceiver != null) {
|
||||
unregisterReceiver(challengeReceiver);
|
||||
challengeReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownGcmRegistrationListener() {
|
||||
if (gcmRegistrationReceiver != null) {
|
||||
unregisterReceiver(gcmRegistrationReceiver);
|
||||
gcmRegistrationReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRegistrationIntent(Intent intent) {
|
||||
markAsVerifying(true);
|
||||
|
||||
RegistrationSocket socket;
|
||||
String number = intent.getStringExtra("e164number");
|
||||
|
||||
try {
|
||||
String password = Util.getSecret(18);
|
||||
initializeChallengeListener();
|
||||
initializeGcmRegistrationListener();
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number));
|
||||
socket = new RegistrationSocket(this, number, password);
|
||||
socket.createAccount();
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number));
|
||||
String challenge = waitForChallenge();
|
||||
socket.verifyAccount(challenge, password);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
|
||||
GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID);
|
||||
String gcmRegistrationId = waitForGcmRegistrationId();
|
||||
socket.registerGcmId(gcmRegistrationId);
|
||||
|
||||
markAsVerified(number, password);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number));
|
||||
broadcastComplete(true);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
Log.w("RegistrationService", uoe);
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_UNSUPPORTED, number));
|
||||
broadcastComplete(false);
|
||||
} catch (AccountVerificationTimeoutException avte) {
|
||||
Log.w("RegistrationService", avte);
|
||||
setState(new RegistrationState(RegistrationState.STATE_TIMEOUT, number));
|
||||
broadcastComplete(false);
|
||||
} catch (IOException e) {
|
||||
Log.w("RegistrationService", e);
|
||||
setState(new RegistrationState(RegistrationState.STATE_NETWORK_ERROR, number));
|
||||
broadcastComplete(false);
|
||||
} catch (GcmRegistrationTimeoutException e) {
|
||||
Log.w("RegistrationService", e);
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_TIMEOUT));
|
||||
broadcastComplete(false);
|
||||
} finally {
|
||||
shutdownChallengeListener();
|
||||
shutdownGcmRegistrationListener();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized String waitForChallenge() throws AccountVerificationTimeoutException {
|
||||
this.verificationStartTime = System.currentTimeMillis();
|
||||
|
||||
if (this.challenge == null) {
|
||||
try {
|
||||
wait(REGISTRATION_TIMEOUT_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.challenge == null)
|
||||
throw new AccountVerificationTimeoutException();
|
||||
|
||||
return this.challenge;
|
||||
}
|
||||
|
||||
private synchronized String waitForGcmRegistrationId() throws GcmRegistrationTimeoutException {
|
||||
if (this.gcmRegistrationId == null) {
|
||||
try {
|
||||
wait(10 * 60 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.gcmRegistrationId == null)
|
||||
throw new GcmRegistrationTimeoutException();
|
||||
|
||||
return this.gcmRegistrationId;
|
||||
}
|
||||
|
||||
private synchronized void challengeReceived(String challenge) {
|
||||
this.challenge = challenge;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private synchronized void gcmRegistrationReceived(String gcmRegistrationId) {
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private void markAsVerifying(boolean verifying) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Editor editor = preferences.edit();
|
||||
|
||||
editor.putBoolean(ApplicationPreferencesActivity.VERIFYING_STATE_PREF, verifying);
|
||||
editor.putBoolean(ApplicationPreferencesActivity.REGISTERED_GCM_PREF, false);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void markAsVerified(String number, String password) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Editor editor = preferences.edit();
|
||||
|
||||
editor.putBoolean(ApplicationPreferencesActivity.VERIFYING_STATE_PREF, false);
|
||||
editor.putBoolean(ApplicationPreferencesActivity.REGISTERED_GCM_PREF, true);
|
||||
editor.putString(ApplicationPreferencesActivity.LOCAL_NUMBER_PREF, number);
|
||||
editor.putString(ApplicationPreferencesActivity.GCM_PASSWORD_PREF, password);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void setState(RegistrationState state) {
|
||||
this.registrationState = state;
|
||||
|
||||
if (registrationStateHandler != null) {
|
||||
registrationStateHandler.obtainMessage(state.state, state.number).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastComplete(boolean success) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(REGISTRATION_EVENT);
|
||||
|
||||
if (success) {
|
||||
intent.putExtra(NOTIFICATION_TITLE, "Registration Complete");
|
||||
intent.putExtra(NOTIFICATION_TEXT, "TextSecure registration has successfully completed.");
|
||||
} else {
|
||||
intent.putExtra(NOTIFICATION_TITLE, "Registration Error");
|
||||
intent.putExtra(NOTIFICATION_TEXT, "TextSecure registration has encountered a problem.");
|
||||
}
|
||||
|
||||
this.sendOrderedBroadcast(intent, null);
|
||||
}
|
||||
|
||||
public void setRegistrationStateHandler(Handler registrationStateHandler) {
|
||||
this.registrationStateHandler = registrationStateHandler;
|
||||
}
|
||||
|
||||
public class RegistrationServiceBinder extends Binder {
|
||||
public RegistrationService getService() {
|
||||
return RegistrationService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private class GcmRegistrationReceiver extends BroadcastReceiver {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("RegistrationService", "Got gcm registration broadcast...");
|
||||
gcmRegistrationReceived(intent.getStringExtra(GCM_REGISTRATION_ID));
|
||||
}
|
||||
}
|
||||
|
||||
private class ChallengeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("RegistrationService", "Got a challenge broadcast...");
|
||||
challengeReceived(intent.getStringExtra(CHALLENGE_EXTRA));
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegistrationState {
|
||||
|
||||
public static final int STATE_IDLE = 0;
|
||||
public static final int STATE_CONNECTING = 1;
|
||||
public static final int STATE_VERIFYING = 2;
|
||||
public static final int STATE_TIMER = 3;
|
||||
public static final int STATE_COMPLETE = 4;
|
||||
public static final int STATE_TIMEOUT = 5;
|
||||
public static final int STATE_NETWORK_ERROR = 6;
|
||||
|
||||
public static final int STATE_GCM_UNSUPPORTED = 8;
|
||||
public static final int STATE_GCM_REGISTERING = 9;
|
||||
public static final int STATE_GCM_TIMEOUT = 10;
|
||||
|
||||
public final int state;
|
||||
public final String number;
|
||||
|
||||
public RegistrationState(int state) {
|
||||
this(state, null);
|
||||
}
|
||||
|
||||
public RegistrationState(int state, String number) {
|
||||
this.state = state;
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
}
|
@ -19,11 +19,13 @@ package org.thoughtcrime.securesms.service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
|
||||
public class SmsListener extends BroadcastReceiver {
|
||||
@ -91,11 +93,44 @@ public class SmsListener extends BroadcastReceiver {
|
||||
return WirePrefix.isEncryptedMessage(messageBody) || WirePrefix.isKeyExchange(messageBody);
|
||||
}
|
||||
|
||||
private boolean isChallenge(Context context, Intent intent) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String messageBody = getSmsMessageBodyFromIntent(intent);
|
||||
|
||||
Log.w("SmsListener", "Checking challenge: " + messageBody);
|
||||
|
||||
if (messageBody == null)
|
||||
return false;
|
||||
|
||||
if (messageBody.matches("Your TextSecure verification code: [0-9]{3,4}-[0-9]{3,4}") &&
|
||||
preferences.getBoolean(ApplicationPreferencesActivity.VERIFYING_STATE_PREF, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String parseChallenge(Context context, Intent intent) {
|
||||
String messageBody = getSmsMessageBodyFromIntent(intent);
|
||||
String[] messageParts = messageBody.split(":");
|
||||
String[] codeParts = messageParts[1].trim().split("-");
|
||||
|
||||
return codeParts[0] + codeParts[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("SMSListener", "Got SMS broadcast...");
|
||||
|
||||
if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isRelevant(context, intent)) {
|
||||
if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isChallenge(context, intent)) {
|
||||
Log.w("SmsListener", "Got challenge!");
|
||||
Intent challengeIntent = new Intent(RegistrationService.CHALLENGE_EVENT);
|
||||
challengeIntent.putExtra(RegistrationService.CHALLENGE_EXTRA, parseChallenge(context, intent));
|
||||
context.sendBroadcast(challengeIntent);
|
||||
|
||||
abortBroadcast();
|
||||
} else if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isRelevant(context, intent)) {
|
||||
intent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
|
||||
intent.putExtra("ResultCode", this.getResultCode());
|
||||
intent.setClass(context, SendReceiveService.class);
|
||||
|
116
src/org/thoughtcrime/securesms/util/PhoneNumberFormatter.java
Normal file
@ -0,0 +1,116 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
|
||||
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Phone number formats are a pain.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class PhoneNumberFormatter {
|
||||
|
||||
public static boolean isValidNumber(String number) {
|
||||
return number.matches("^\\+[0-9]{10,}");
|
||||
}
|
||||
|
||||
private static String impreciseFormatNumber(String number, String localNumber) {
|
||||
number = number.replaceAll("[^0-9+]", "");
|
||||
|
||||
if (number.charAt(0) == '+')
|
||||
return number;
|
||||
|
||||
if (localNumber.charAt(0) == '+')
|
||||
localNumber = localNumber.substring(1);
|
||||
|
||||
if (localNumber.length() == number.length() || number.length() > localNumber.length())
|
||||
return "+" + number;
|
||||
|
||||
int difference = localNumber.length() - number.length();
|
||||
|
||||
return "+" + localNumber.substring(0, difference) + number;
|
||||
}
|
||||
|
||||
public static String formatNumberInternational(String number) {
|
||||
try {
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
PhoneNumber parsedNumber = util.parse(number, null);
|
||||
return util.format(parsedNumber, PhoneNumberFormat.INTERNATIONAL);
|
||||
} catch (NumberParseException e) {
|
||||
Log.w("PhoneNumberFormatter", e);
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatNumber(Context context, String number) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String localNumber = preferences.getString(ApplicationPreferencesActivity.LOCAL_NUMBER_PREF, "No Stored Number");
|
||||
number = number.replaceAll("[^0-9+]", "");
|
||||
|
||||
if (number.charAt(0) == '+')
|
||||
return number;
|
||||
|
||||
try {
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
PhoneNumber localNumberObject = util.parse(localNumber, null);
|
||||
|
||||
String localCountryCode = util.getRegionCodeForNumber(localNumberObject);
|
||||
Log.w("PhoneNumberFormatter", "Got local CC: " + localCountryCode);
|
||||
|
||||
PhoneNumber numberObject = util.parse(number, localCountryCode);
|
||||
return util.format(numberObject, PhoneNumberFormat.E164);
|
||||
} catch (NumberParseException e) {
|
||||
Log.w("PhoneNumberFormatter", e);
|
||||
return impreciseFormatNumber(number, localNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRegionDisplayName(String regionCode) {
|
||||
return (regionCode == null || regionCode.equals("ZZ") || regionCode.equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY))
|
||||
? "Unknown country" : new Locale("", regionCode).getDisplayCountry(Locale.getDefault());
|
||||
}
|
||||
|
||||
public static String formatE164(String countryCode, String number) {
|
||||
try {
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
int parsedCountryCode = Integer.parseInt(countryCode);
|
||||
PhoneNumber parsedNumber = util.parse(number,
|
||||
util.getRegionCodeForCountryCode(parsedCountryCode));
|
||||
|
||||
return util.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
} catch (NumberParseException npe) {
|
||||
Log.w("CreateAccountActivity", npe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w("CreateAccountActivity", nfe);
|
||||
}
|
||||
|
||||
return "+" +
|
||||
countryCode.replaceAll("[^0-9]", "").replaceAll("^0*", "") +
|
||||
number.replaceAll("[^0-9]", "");
|
||||
}
|
||||
|
||||
public static String getInternationalFormatFromE164(String e164number) {
|
||||
try {
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
PhoneNumber parsedNumber = util.parse(e164number, null);
|
||||
return util.format(parsedNumber, PhoneNumberFormat.INTERNATIONAL);
|
||||
} catch (NumberParseException e) {
|
||||
Log.w("PhoneNumberFormatter", e);
|
||||
return e164number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -16,12 +16,19 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.widget.EditText;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -94,6 +101,10 @@ public class Util {
|
||||
return value == null || value.getText() == null || isEmpty(value.getText().toString());
|
||||
}
|
||||
|
||||
public static boolean isEmpty(CharSequence value) {
|
||||
return value == null || value.length() == 0;
|
||||
}
|
||||
|
||||
public static CharSequence getBoldedString(String value) {
|
||||
SpannableString spanned = new SpannableString(value);
|
||||
spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
|
||||
@ -112,6 +123,39 @@ public class Util {
|
||||
return spanned;
|
||||
}
|
||||
|
||||
public static void showAlertDialog(Context context, String title, String message) {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
|
||||
dialog.setTitle(title);
|
||||
dialog.setMessage(message);
|
||||
dialog.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
dialog.setPositiveButton(android.R.string.ok, null);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static String getSecret(int size) {
|
||||
try {
|
||||
byte[] secret = new byte[size];
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(secret);
|
||||
return Base64.encodeBytes(secret);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new AssertionError(nsae);
|
||||
}
|
||||
}
|
||||
|
||||
public static String readFully(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
bout.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
in.close();
|
||||
|
||||
return new String(bout.toByteArray());
|
||||
}
|
||||
|
||||
// public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
|
||||
// return BitmapFactory.decodeStream(src);
|
||||
//// BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|