Initial GCM registration

This commit is contained in:
Moxie Marlinspike 2013-03-25 21:26:03 -07:00
parent f2475491fe
commit 2f39283da3
37 changed files with 2264 additions and 12 deletions

View File

@ -30,6 +30,13 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" /> <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" <application android:icon="@drawable/icon"
android:label="@string/app_name" android:label="@string/app_name"
@ -146,9 +153,18 @@
android:label="@string/AndroidManifest__verify_imported_identity" android:label="@string/AndroidManifest__verify_imported_identity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> 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.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/> <service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.SendReceiveService"/> <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" --> <!-- <receiver android:name=".service.BootListener" -->
<!-- android:enabled="true" --> <!-- android:enabled="true" -->
@ -158,6 +174,14 @@
<!-- </intent-filter>--> <!-- </intent-filter>-->
<!-- </receiver>--> <!-- </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" <receiver android:name=".service.SmsListener"
android:enabled="true" android:enabled="true"
android:exported="true"> android:exported="true">

BIN
assets/whisper.store Normal file

Binary file not shown.

BIN
libs/gcm.jar Normal file

Binary file not shown.

BIN
libs/libphonenumber-5.3.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
res/drawable-hdpi/alert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
res/drawable-hdpi/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/drawable-mdpi/alert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/drawable-mdpi/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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_LENGTH = "pref_trim_length";
public static final String THREAD_TRIM_NOW = "pref_trim_now"; 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 @Override
protected void onCreate(Bundle icicle) { protected void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);

View 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();
}
}

View 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) {
}
}
}

View 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) {
}
}
}

View 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();
}
}
}

View File

@ -94,18 +94,22 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
} }
private void handleDisplayConversationOrList() { private void handleDisplayConversationOrList() {
ConversationParameters parameters = getConversationParameters(); Intent intent = new Intent(this, RegistrationActivity.class);
Intent intent;
if (isShareAction() || parameters.recipients != null) {
intent = getConversationIntent(parameters);
} else {
intent = getConversationListIntent();
}
startActivity(intent); startActivity(intent);
finish(); 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) { private Intent getConversationIntent(ConversationParameters parameters) {

View File

@ -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"));
}
}
}

View 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";
}
}

View 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.
}
}

View File

@ -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);
}
}

View 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)
{
}
}

View 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;
}
}
}

View File

@ -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);
}
}

View 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;
}
}
}

View File

@ -19,11 +19,13 @@ package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.protocol.WirePrefix;
public class SmsListener extends BroadcastReceiver { public class SmsListener extends BroadcastReceiver {
@ -91,11 +93,44 @@ public class SmsListener extends BroadcastReceiver {
return WirePrefix.isEncryptedMessage(messageBody) || WirePrefix.isKeyExchange(messageBody); 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 @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.w("SMSListener", "Got SMS broadcast..."); 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.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
intent.putExtra("ResultCode", this.getResultCode()); intent.putExtra("ResultCode", this.getResultCode());
intent.setClass(context, SendReceiveService.class); intent.setClass(context, SendReceiveService.class);

View 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;
}
}
}

View File

@ -16,12 +16,19 @@
*/ */
package org.thoughtcrime.securesms.util; package org.thoughtcrime.securesms.util;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.widget.EditText; 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.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -94,6 +101,10 @@ public class Util {
return value == null || value.getText() == null || isEmpty(value.getText().toString()); 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) { public static CharSequence getBoldedString(String value) {
SpannableString spanned = new SpannableString(value); SpannableString spanned = new SpannableString(value);
spanned.setSpan(new StyleSpan(Typeface.BOLD), 0, spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
@ -112,6 +123,39 @@ public class Util {
return spanned; 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) { // public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
// return BitmapFactory.decodeStream(src); // return BitmapFactory.decodeStream(src);
//// BitmapFactory.Options options = new BitmapFactory.Options(); //// BitmapFactory.Options options = new BitmapFactory.Options();