Improve passphrase and onboarding UI. Abstract out routing.

1) Update the create, prompt, and change passphrase activities.
   They are no longer dialog themed, and should look a little
   less ugly.

2) Update the import DB activity to be less ugly and more robust.

3) Abstract all of the state handling stuff out of
   ConversationListActivity.  This is now handled by RoutingActivity,
   which all launch intents move through.
This commit is contained in:
Moxie Marlinspike 2013-02-17 11:42:30 -08:00
parent 9e3da08d45
commit 5eb04328d3
30 changed files with 1177 additions and 718 deletions

View File

@ -35,10 +35,9 @@
android:label="@string/app_name"
android:theme="@style/Theme.Sherlock.Light.DarkActionBar">
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
<activity android:name=".RoutingActivity"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:launchMode="singleTask"
android:uiOptions="splitActionBarWhenNarrow"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
@ -63,21 +62,33 @@
</activity>
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:uiOptions="splitActionBarWhenNarrow"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseMigrationActivity"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseCreateActivity"
android:theme="@style/Theme.Sherlock.Light.Dialog"
android:label="@string/AndroidManifest__create_passphrase"
android:launchMode="singleInstance"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphrasePromptActivity"
android:theme="@style/Theme.Sherlock.Light.Dialog"
android:label="@string/AndroidManifest__enter_passphrase"
android:launchMode="singleInstance"
android:windowSoftInputMode="stateVisible"
android:launchMode="singleTop"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ContactSelectionActivity"
@ -95,7 +106,6 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseChangeActivity"
android:theme="@style/Theme.Sherlock.Light.Dialog"
android:label="@string/AndroidManifest__change_passphrase"
android:launchMode="singleInstance"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap android:src="@drawable/background_pattern" android:tileMode="repeat"
xmlns:android="http://schemas.android.com/apk/res/android" />

View File

@ -1,90 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
android:layout_height="fill_parent"
android:fillViewport="true"
android:background="@drawable/background_pattern_repeat">
<LinearLayout
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_height="fill_parent"
android:gravity="center" >
<LinearLayout android:paddingRight="16dip"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:orientation="vertical" >
<TextView
android:padding="3dip"
android:text="@string/change_passphrase_activity__old_passphrase"
android:textAppearance="?android:attr/textAppearanceMedium"
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="visible"
android:orientation="vertical">
<EditText
android:id="@+id/old_passphrase"
<TextView style="@style/Registration.Label"
android:layout_width="fill_parent"
android:textAllCaps="true"
android:text="@string/change_passphrase_activity__old_passphrase" />
<EditText android:id="@+id/old_passphrase"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:layout_marginBottom="5dip" />
android:layout_marginBottom="10dip"
android:singleLine="true"/>
<TextView
android:padding="3dip"
android:text="@string/change_passphrase_activity__new_passphrase"
<TextView style="@style/Registration.Label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" />
android:textAllCaps="true"
android:text="@string/change_passphrase_activity__new_passphrase" />
<EditText
android:id="@+id/new_passphrase"
<EditText android:id="@+id/new_passphrase"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:layout_marginBottom="5dip" />
android:singleLine="true"/>
<TextView
android:padding="3dip"
<TextView style="@style/Registration.Label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/change_passphrase_activity__repeat_new_passphrase"
android:textAppearance="?android:attr/textAppearanceMedium" />
android:textAllCaps="true"
android:text="@string/change_passphrase_activity__repeat_new_passphrase" />
<EditText
android:id="@+id/repeat_passphrase"
<EditText android:id="@+id/repeat_passphrase"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:layout_marginBottom="5dip" />
android:singleLine="true"/>
<LinearLayout
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip"
android:gravity="right"
android:orientation="horizontal" >
android:layout_marginTop="30dip">
<TableLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stretchColumns="*"
tools:ignore="UselessParent" >
<TableRow>
<Button
<Button style="@android:style/Widget.Button"
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:text="@android:string/cancel"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
android:layout_marginRight="15dip"
android:text="@android:string/cancel" />
android:layout_marginRight="7dip"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<Button
<Button style="@android:style/Widget.Button"
android:id="@+id/ok_button"
android:layout_width="wrap_content"
android:text="@android:string/ok"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="16dip"
android:text="@android:string/ok" />
</TableRow>
</TableLayout>
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>

View File

@ -1,74 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/ScrollView"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
android:fillViewport="true"
android:background="@drawable/background_pattern_repeat">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" >
<LinearLayout android:id="@+id/create_layout"
android:paddingRight="16dip"
android:paddingLeft="16dip"
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:layout_gravity="center"
android:visibility="visible"
android:orientation="vertical">
<TextView android:text="@string/create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase"
android:textAppearance="?android:attr/textAppearanceMedium"
<TextView style="@style/Registration.Description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"/>
android:layout_marginBottom="16dip"
android:layout_marginTop="16dip"
android:text="@string/create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase"/>
<TextView android:text="@string/create_passphrase_activity__passphrase"
android:textAppearance="?android:attr/textAppearanceMedium"
<TextView style="@style/Registration.Label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"/>
android:textAllCaps="true"
android:text="@string/create_passphrase_activity__passphrase" />
<EditText android:id="@+id/passphrase_edit"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:inputType="textPassword"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/repeat_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="5dip"
android:layout_marginTop="10dip"
android:text="@string/create_passphrase_activity__repeat"/>
<EditText
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:inputType="textPassword"
android:id="@+id/passphrase_edit_repeat"/>
android:layout_marginBottom="10dip"
android:singleLine="true"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dip"
android:gravity="right">
<TextView style="@style/Registration.Label"
android:layout_width="fill_parent"
android:textAllCaps="true"
android:text="@string/create_passphrase_activity__repeat" />
<TableLayout android:layout_width="wrap_content"
<EditText android:id="@+id/passphrase_edit_repeat"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<Button android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@android:string/cancel"
android:id="@+id/cancel_button"
android:layout_marginRight="15dip"
android:layout_marginLeft="16dip"/>
android:inputType="textPassword"
android:singleLine="true"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/ok"
<Button style="@android:style/Widget.Button"
android:id="@+id/ok_button"
android:layout_marginRight="16dip"/>
</TableRow>
</TableLayout>
android:text="@string/create_passphrase_activity__continue"
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>
<LinearLayout android:id="@+id/progress_layout"
android:paddingRight="16dip"
android:paddingLeft="16dip"
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:orientation="vertical">
<TextView style="@style/Registration.BigLabel"
android:layout_width="fill_parent"
android:layout_marginBottom="16dip"
android:layout_marginTop="16dip"
android:gravity="center"
android:text="@string/create_passphrase_activity__generating_secrets"/>
<ProgressBar android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center"/>
</LinearLayout>
</FrameLayout>
</ScrollView>

View File

@ -0,0 +1,126 @@
<?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:id="@+id/prompt_layout"
android:paddingRight="16dip"
android:paddingLeft="16dip"
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:orientation="vertical">
<TextView style="@style/Registration.Description"
android:layout_width="fill_parent"
android:layout_marginBottom="16dip"
android:text="@string/database_migration_activity__would_you_like_to_import_your_existing_text_messages"/>
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/import_database"/>
<TextView style="@style/Registration.Description"
android:layout_width="fill_parent"
android:layout_marginBottom="16dip"
android:layout_marginTop="16dip"
android:text="@string/database_migration_activity__the_default_system_database_will_not_be_modified"/>
<LinearLayout android:orientation="horizontal"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dip"
android:layout_marginRight="20dip">
<Button style="@android:style/Widget.Button"
android:id="@+id/skip_button"
android:text="@string/database_migration_activity__skip"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="7dip"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<Button style="@android:style/Widget.Button"
android:id="@+id/import_button"
android:text="@string/database_migration_activity__import"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</LinearLayout>
<LinearLayout android:id="@+id/progress_layout"
android:paddingRight="16dip"
android:paddingLeft="16dip"
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="visible"
android:orientation="vertical">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/import_database"/>
<TextView style="@style/Registration.Description"
android:layout_width="fill_parent"
android:layout_marginTop="34dip"
android:layout_marginBottom="16dip"
android:text="@string/database_migration_activity__this_could_take_a_moment_please_be_patient"/>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dip"
android:layout_weight="1.0" >
<TextView
android:id="@+id/import_status"
style="@style/Registration.Constant"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="0/100"
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="@string/database_migration_activity__importing"
android:textAllCaps="true"
android:textSize="12.0sp"
android:textStyle="normal" />
</RelativeLayout>
<ProgressBar
android:id="@+id/import_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" />
</LinearLayout>
</FrameLayout>
</ScrollView>

View File

@ -1,39 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:id="@+id/prompt_layout"
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">
<EditText android:inputType="textPassword"
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="30dip"
android:src="@drawable/padlock_prompt"/>
<TextView style="@style/Registration.Label"
android:layout_width="fill_parent"
android:id="@+id/passphrase_edit"
android:password="true"
android:layout_margin="16dip"/>
android:textAllCaps="true"
android:text="@string/prompt_passphrase_activity__textsecure_passphrase" />
<LinearLayout android:layout_width="fill_parent"
<EditText android:id="@+id/passphrase_edit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dip"
android:gravity="right">
android:inputType="textPassword"
android:layout_marginBottom="10dip"
android:singleLine="true"/>
<TableLayout android:layout_width="wrap_content"
<LinearLayout android:orientation="horizontal"
android:gravity="right"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<Button android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@android:string/cancel"
android:id="@+id/cancel_button"
android:layout_marginRight="15dip"
android:layout_marginLeft="16dip"/>
android:layout_marginTop="20dip"
android:layout_marginRight="20dip">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/ok"
<Button style="@android:style/Widget.Button"
android:id="@+id/ok_button"
android:layout_marginRight="16dip"/>
</TableRow>
</TableLayout>
android:text="@string/prompt_passphrase_activity__unlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>

View File

@ -118,9 +118,8 @@
<string name="PassphraseChangeActivity_incorrect_old_passphrase_exclamation">Incorrect old passphrase!</string>
<!-- PassphraseCreateActivity -->
<string name="PassphraseCreateActivity_passphrases_dont_match_exclamation">Passphrases Don\'t Match!</string>
<string name="PassphraseCreateActivity_generating_keypair">Generating KeyPair</string>
<string name="PassphraseCreateActivity_generating_a_local_encryption_keypair">Generating a local encryption keypair...</string>
<string name="PassphraseCreateActivity_passphrases_dont_match">Passphrases don\'t match</string>
<string name="PassphraseCreateActivity_you_must_specify_a_password">You must specify a password</string>
<!-- PassphrasePromptActivity -->
<string name="PassphrasePromptActivity_invalid_passphrase_exclamation">Invalid Passphrase!</string>
@ -225,8 +224,9 @@
<string name="MmsSender_currently_unable_to_send_your_mms_message">Currently unable to send your MMS message. It will be sent once service becomes available.</string>
<!-- ApplicationMigrationService -->
<string name="ApplicationMigrationService_migrating">Migrating</string>
<string name="ApplicationMigrationService_migrating_system_text_messages">Migrating System Text Messages</string>
<string name="ApplicationMigrationService_import_in_progress">Import in progress</string>
<string name="ApplicationMigrationService_importing_text_messages">Importing Text Messages</string>
<string name="ApplicationMigrationService_import_complete">Import complete!</string>
<!-- KeyCachingService -->
<string name="KeyCachingService_textsecure_passphrase_cached">TextSecure Passphrase Cached</string>
@ -251,9 +251,9 @@
<string name="auto_initiate_activity__initiate_exchange">Initiate Exchange</string>
<!-- change_passphrase_activity -->
<string name="change_passphrase_activity__old_passphrase">Old passphrase:</string>
<string name="change_passphrase_activity__new_passphrase">New passphrase:</string>
<string name="change_passphrase_activity__repeat_new_passphrase">Repeat new passphrase:</string>
<string name="change_passphrase_activity__old_passphrase">OLD PASSPHRASE:</string>
<string name="change_passphrase_activity__new_passphrase">NEW PASSPHRASE:</string>
<string name="change_passphrase_activity__repeat_new_passphrase">REPEAT NEW PASSPHRASE:</string>
<!-- contact_selection_group_activity -->
<!-- contact_selection_list_activity -->
@ -282,9 +282,23 @@
<string name="conversation_fragment_cab__batch_selection_mode">Batch Selection Mode</string>
<!-- create_passphrase_activity -->
<string name="create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase">Please choose a passphrase that will be used to locally encrypt your data. This should be a strong passphrase.</string>
<string name="create_passphrase_activity__passphrase">Passphrase:</string>
<string name="create_passphrase_activity__repeat">Repeat:</string>
<string name="create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase">Please choose a passphrase that will be used to locally encrypt your data.\n\nThis should be a strong passphrase.</string>
<string name="create_passphrase_activity__passphrase">PASSPHRASE:</string>
<string name="create_passphrase_activity__repeat">REPEAT:</string>
<string name="create_passphrase_activity__continue">Continue</string>
<string name="create_passphrase_activity__generating_secrets">GENERATING SECRETS</string>
<!-- database_migration_activity -->
<string name="database_migration_activity__would_you_like_to_import_your_existing_text_messages">Would you like to import your existing text messages into TextSecure\\\'s encrypted database?</string>
<string name="database_migration_activity__the_default_system_database_will_not_be_modified">The default system database will not be modified or altered in any way.</string>
<string name="database_migration_activity__skip">Skip</string>
<string name="database_migration_activity__import">Import</string>
<string name="database_migration_activity__this_could_take_a_moment_please_be_patient">This could take a moment. Please be patient, we\'ll notify you when the import is complete.</string>
<string name="database_migration_activity__importing">IMPORTING</string>
<!-- prompt_passphrase_activity -->
<string name="prompt_passphrase_activity__textsecure_passphrase">TEXTSECURE PASSPHRASE</string>
<string name="prompt_passphrase_activity__unlock">Unlock</string>
<!-- receive_key_activity -->
<string name="receive_key_activity__session">Session</string>

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NoAnimation.Theme.Sherlock.Light.DarkActionBar" parent="@style/Theme.Sherlock.Light.DarkActionBar">
<item name="android:windowAnimationStyle">@null</item>
</style>
<style name="transparent_progress">
<item name="android:windowFrame">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
@ -22,4 +26,48 @@
<item name="android:textStyle">bold</item>
</style>
<style name="Registration.Description" parent="@android:style/TextAppearance">
<item name="android:textSize">16.0sp</item>
<item name="android:typeface">sans</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">#ff333333</item>
<item name="android:gravity">left</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:shadowColor">#ffffff</item>
<item name="android:shadowDx">1.0</item>
<item name="android:shadowDy">1.0</item>
<item name="android:shadowRadius">0.0</item>
<item name="android:lineSpacingMultiplier">1.25</item>
</style>
<style name="Registration.Label" parent="@android:style/TextAppearance">
<item name="android:textSize">12.0sp</item>
<item name="android:typeface">sans</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">#ff808080</item>
<item name="android:gravity">left</item>
<item name="android:layout_gravity">left</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:shadowColor">#ffffff</item>
<item name="android:shadowDx">1.0</item>
<item name="android:shadowDy">1.0</item>
<item name="android:shadowRadius">0.0</item>
<item name="android:lineSpacingMultiplier">1.25</item>
</style>
<style name="Registration.BigLabel" parent="@style/Registration.Label">
<item name="android:textSize">20sp</item>
</style>
<style name="Registration.Constant" parent="@android:style/TextAppearance">
<item name="android:typeface">sans</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">#ff808080</item>
<item name="android:shadowColor">#ffffff</item>
<item name="android:shadowDx">1.0</item>
<item name="android:shadowDy">1.0</item>
<item name="android:shadowRadius">0.0</item>
<item name="android:lineSpacingMultiplier">1.25</item>
</style>
</resources>

View File

@ -1,121 +0,0 @@
package org.thoughtcrime.securesms;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
public class ApplicationMigrationManager extends Handler {
private ProgressDialog progressDialog;
private ApplicationMigrationListener listener;
private final Context context;
private final MasterSecret masterSecret;
public ApplicationMigrationManager(Context context,
MasterSecret masterSecret)
{
this.masterSecret = masterSecret;
this.context = context;
}
public void setMigrationListener(ApplicationMigrationListener listener) {
this.listener = listener;
}
private void displayMigrationProgress() {
progressDialog = new ProgressDialog(context);
progressDialog.setTitle(context.getString(R.string.ApplicationMigrationManager_migrating_database));
progressDialog.setMessage(context.getString(R.string.ApplicationMigrationManager_migrating_text_message_database));
progressDialog.setMax(10000);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
}
public void migrate() {
context.bindService(new Intent(context, ApplicationMigrationService.class),
serviceConnection, Context.BIND_AUTO_CREATE);
}
private void displayMigrationPrompt() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setTitle(R.string.ApplicationMigrationManager_copy_system_text_message_database_question);
alertBuilder.setMessage(R.string.ApplicationMigrationManager_copy_system_text_message_database_explanation);
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton(R.string.ApplicationMigrationManager_copy,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
displayMigrationProgress();
Intent intent = new Intent(context, ApplicationMigrationService.class);
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
intent.putExtra("master_secret", masterSecret);
context.startService(intent);
}
});
alertBuilder.setNegativeButton(R.string.ApplicationMigrationManager_dont_copy,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE)
.edit()
.putBoolean("migrated", true).commit();
listener.applicationMigrationComplete();
}
});
alertBuilder.create().show();
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case ApplicationMigrationService.PROGRESS_UPDATE:
if (progressDialog != null) {
progressDialog.setProgress(message.arg1);
progressDialog.setSecondaryProgress(message.arg2);
}
break;
case ApplicationMigrationService.PROGRESS_COMPLETE:
if (progressDialog != null) {
progressDialog.dismiss();
}
if (listener != null) {
listener.applicationMigrationComplete();
}
break;
}
}
public static interface ApplicationMigrationListener {
public void applicationMigrationComplete();
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
ApplicationMigrationService applicationMigrationService
= ((ApplicationMigrationService.ApplicationMigrationBinder)service).getService();
if (applicationMigrationService.isMigrating()) displayMigrationProgress();
else displayMigrationPrompt();
applicationMigrationService.setHandler(ApplicationMigrationManager.this);
}
public void onServiceDisconnected(ComponentName name) {}
};
}

View File

@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.Trimmer;
@ -138,7 +138,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
case android.R.id.home:
Intent intent = new Intent(this, ConversationListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
return true;
}
return false;
@ -313,9 +318,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
SharedPreferences settings = getSharedPreferences(KeyCachingService.PREFERENCES_NAME, 0);
if (settings.getBoolean("passphrase_initialized", false)) {
if (MasterSecretUtil.isPassphraseInitialized(ApplicationPreferencesActivity.this)) {
startActivity(new Intent(ApplicationPreferencesActivity.this, PassphraseChangeActivity.class));
} else {
Toast.makeText(ApplicationPreferencesActivity.this,

View File

@ -236,7 +236,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
case R.id.menu_verify_recipient: handleVerifyRecipient(); return true;
case R.id.menu_verify_session: handleVerifySession(); return true;
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
case android.R.id.home: finish(); return true;
case android.R.id.home: handleReturnToConversationList(); return true;
}
return false;
@ -261,6 +261,14 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
//////// Event Handlers
private void handleReturnToConversationList() {
Intent intent = new Intent(this, ConversationListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("master_secret", masterSecret);
startActivity(intent);
finish();
}
private void handleVerifyRecipient() {
Intent verifyIdentityIntent = new Intent(this, VerifyIdentityActivity.class);
verifyIdentityIntent.putExtra("recipient", getRecipients().getPrimaryRecipient());

View File

@ -1,25 +1,16 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.ContactsContract;
import android.util.Log;
import android.view.WindowManager;
import org.thoughtcrime.securesms.ApplicationExportManager.ApplicationExportListener;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.SendReceiveService;
@ -36,11 +27,6 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
private ConversationListFragment fragment;
private MasterSecret masterSecret;
private ApplicationMigrationManager migrationManager;
private boolean havePromptedForPassphrase = false;
private boolean isVisible = false;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@ -52,32 +38,6 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
initializeContactUpdatesReceiver();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
this.setIntent(intent);
}
@Override
public void onResume() {
super.onResume();
isVisible = true;
}
@Override
public void onPause() {
super.onPause();
isVisible = false;
}
@Override
public void onStop() {
super.onStop();
havePromptedForPassphrase = false;
}
@Override
public void onDestroy() {
Log.w("ConversationListActivity", "onDestroy...");
@ -87,47 +47,17 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
@Override
public void onMasterSecretCleared() {
this.masterSecret = null;
this.fragment.setMasterSecret(null);
this.invalidateOptionsMenu();
if (!havePromptedForPassphrase && isVisible) {
promptForPassphrase();
// this.fragment.setMasterSecret(null);
startActivity(new Intent(this, RoutingActivity.class));
super.onMasterSecretCleared();
}
}
@Override
public void onNewMasterSecret(MasterSecret masterSecret) {
this.masterSecret = masterSecret;
if (masterSecret != null) {
if (!IdentityKeyUtil.hasIdentityKey(this)) {
new Thread(new IdentityKeyInitializer()).start();
}
if (!MasterSecretUtil.hasAsymmericMasterSecret(this)) {
new Thread(new AsymmetricMasteSecretInitializer()).start();
}
if (!isDatabaseMigrated()) initializeDatabaseMigration();
else DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
}
this.fragment.setMasterSecret(masterSecret);
this.invalidateOptionsMenu();
this.havePromptedForPassphrase = false;
createConversationIfNecessary(this.getIntent());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
Log.w("ConversationListActivity", "onPrepareOptionsMenu...");
MenuInflater inflater = this.getSupportMenuInflater();
menu.clear();
if (this.masterSecret == null) inflater.inflate(R.menu.text_secure_locked, menu);
else inflater.inflate(R.menu.text_secure_normal, menu);
inflater.inflate(R.menu.text_secure_normal, menu);
super.onPrepareOptionsMenu(menu);
return true;
@ -138,8 +68,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_new_message: createConversation(-1, null, null, null, null); return true;
case R.id.menu_unlock: promptForPassphrase(); return true;
case R.id.menu_new_message: createConversation(-1, null); return true;
case R.id.menu_settings: handleDisplaySettings(); return true;
case R.id.menu_export: handleExportDatabase(); return true;
case R.id.menu_import: handleImportDatabase(); return true;
@ -151,39 +80,18 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
@Override
public void onCreateConversation(long threadId, Recipients recipients) {
createConversation(threadId, recipients, null, null, null);
}
private void createConversation(long threadId, Recipients recipients,
String text, Uri imageUri, Uri audioUri)
{
if (this.masterSecret == null) {
promptForPassphrase();
return;
createConversation(threadId, recipients);
}
private void createConversation(long threadId, Recipients recipients) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, text);
intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, imageUri);
intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, audioUri);
startActivity(intent);
}
private void promptForPassphrase() {
havePromptedForPassphrase = true;
if (hasSelectedPassphrase()) startActivity(new Intent(this, PassphrasePromptActivity.class));
else startActivity(new Intent(this, PassphraseCreateActivity.class));
}
private boolean hasSelectedPassphrase() {
SharedPreferences settings = getSharedPreferences(KeyCachingService.PREFERENCES_NAME, 0);
return settings.getBoolean("passphrase_initialized", false);
}
private void handleDisplaySettings() {
Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
preferencesIntent.putExtra("master_secret", masterSecret);
@ -237,89 +145,17 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
startService(mmsSenderIntent);
}
private void initializeDatabaseMigration() {
if (migrationManager == null) {
migrationManager = new ApplicationMigrationManager(this, masterSecret);
ApplicationMigrationManager.ApplicationMigrationListener listener =
new ApplicationMigrationManager.ApplicationMigrationListener() {
@Override
public void applicationMigrationComplete() {
if (masterSecret != null)
DecryptingQueue.schedulePendingDecrypts(ConversationListActivity.this,
masterSecret);
}
};
migrationManager.setMigrationListener(listener);
migrationManager.migrate();
}
}
private void initializeResources() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
}
this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
this.fragment = (ConversationListFragment)this.getSupportFragmentManager()
.findFragmentById(R.id.fragment_content);
}
private boolean isDatabaseMigrated() {
return this.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE)
.getBoolean("migrated", false);
}
private void createConversationIfNecessary(Intent intent) {
long thread = intent.getLongExtra("thread_id", -1L);
String type = intent.getType();
Recipients recipients = null;
String draftText = null;
Uri draftImage = null;
Uri draftAudio = null;
if (Intent.ACTION_SENDTO.equals(intent.getAction())) {
try {
recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart(), false);
thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
} catch (RecipientFormattingException rfe) {
recipients = null;
}
} else if (Intent.ACTION_SEND.equals(intent.getAction())) {
if ("text/plain".equals(type)) {
draftText = intent.getStringExtra(Intent.EXTRA_TEXT);
} else if (type.startsWith("image/")) {
draftImage = intent.getParcelableExtra(Intent.EXTRA_STREAM);
} else if (type.startsWith("audio/")) {
draftAudio = intent.getParcelableExtra(Intent.EXTRA_STREAM);
}
} else {
recipients = intent.getParcelableExtra("recipients");
}
if (recipients != null || Intent.ACTION_SEND.equals(intent.getAction())) {
createConversation(thread, recipients, draftText, draftImage, draftAudio);
intent.putExtra("thread_id", -1L);
intent.putExtra("recipients", (Parcelable)null);
intent.putExtra(Intent.EXTRA_TEXT, (String)null);
intent.putExtra(Intent.EXTRA_STREAM, (Parcelable)null);
intent.setAction(null);
}
}
private class IdentityKeyInitializer implements Runnable {
@Override
public void run() {
IdentityKeyUtil.generateIdentityKeys(ConversationListActivity.this, masterSecret);
}
}
private class AsymmetricMasteSecretInitializer implements Runnable {
@Override
public void run() {
MasterSecretUtil.generateAsymmetricMasterSecret(ConversationListActivity.this, masterSecret);
}
this.fragment.setMasterSecret(masterSecret);
}
}

View File

@ -0,0 +1,189 @@
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.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.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
public class DatabaseMigrationActivity extends PassphraseRequiredSherlockActivity {
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
private final ImportStateHandler importStateHandler = new ImportStateHandler();
private final BroadcastReceiver completedReceiver = new NullReceiver();
private LinearLayout promptLayout;
private LinearLayout progressLayout;
private Button skipButton;
private Button importButton;
private ProgressBar progress;
private TextView progressLabel;
private ApplicationMigrationService importService;
private boolean isVisible = false;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.database_migration_activity);
initializeResources();
initializeServiceBinding();
}
@Override
public void onResume() {
super.onResume();
isVisible = true;
registerForCompletedNotification();
}
@Override
public void onPause() {
super.onPause();
isVisible = false;
unregisterForCompletedNotification();
}
@Override
public void onDestroy() {
super.onDestroy();
shutdownServiceBinding();
}
private void initializeServiceBinding() {
Intent intent = new Intent(this, ApplicationMigrationService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void initializeResources() {
this.promptLayout = (LinearLayout)findViewById(R.id.prompt_layout);
this.progressLayout = (LinearLayout)findViewById(R.id.progress_layout);
this.skipButton = (Button) findViewById(R.id.skip_button);
this.importButton = (Button) findViewById(R.id.import_button);
this.progress = (ProgressBar) findViewById(R.id.import_progress);
this.progressLabel = (TextView) findViewById(R.id.import_status);
this.progressLayout.setVisibility(View.GONE);
this.promptLayout.setVisibility(View.GONE);
this.importButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(DatabaseMigrationActivity.this, ApplicationMigrationService.class);
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
intent.putExtra("master_secret", getIntent().getParcelableExtra("master_secret"));
startService(intent);
promptLayout.setVisibility(View.GONE);
progressLayout.setVisibility(View.VISIBLE);
}
});
this.skipButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ApplicationMigrationService.setDatabaseImported(DatabaseMigrationActivity.this);
handleImportComplete();
}
});
}
private void registerForCompletedNotification() {
IntentFilter filter = new IntentFilter();
filter.addAction(ApplicationMigrationService.COMPLETED_ACTION);
filter.setPriority(1000);
registerReceiver(completedReceiver, filter);
}
private void unregisterForCompletedNotification() {
unregisterReceiver(completedReceiver);
}
private void shutdownServiceBinding() {
unbindService(serviceConnection);
}
private void handleStateIdle() {
this.promptLayout.setVisibility(View.VISIBLE);
this.progressLayout.setVisibility(View.GONE);
}
private void handleStateProgress(ProgressDescription update) {
this.promptLayout.setVisibility(View.GONE);
this.progressLayout.setVisibility(View.VISIBLE);
this.progressLabel.setText(update.primaryComplete + "/" + update.primaryTotal);
double max = this.progress.getMax();
double primaryTotal = update.primaryTotal;
double primaryComplete = update.primaryComplete;
double secondaryTotal = update.secondaryTotal;
double secondaryComplete = update.secondaryComplete;
this.progress.setProgress((int)Math.round((primaryComplete / primaryTotal) * max));
this.progress.setSecondaryProgress((int)Math.round((secondaryComplete / secondaryTotal) * max));
}
private void handleImportComplete() {
if (isVisible) {
if (getIntent().hasExtra("next_intent")) {
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
} else {
startActivity(new Intent(this, ConversationListActivity.class));
}
}
finish();
}
private class ImportStateHandler extends Handler {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case ImportState.STATE_IDLE: handleStateIdle(); break;
case ImportState.STATE_MIGRATING_IN_PROGRESS: handleStateProgress((ProgressDescription)message.obj); break;
case ImportState.STATE_MIGRATING_COMPLETE: handleImportComplete(); break;
}
}
}
private class ImportServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
importService = ((ApplicationMigrationService.ApplicationMigrationBinder)service).getService();
importService.setImportStateHandler(importStateHandler);
ImportState state = importService.getState();
importStateHandler.obtainMessage(state.state, state.progress).sendToTarget();
}
@Override
public void onServiceDisconnected(ComponentName name) {
importService.setImportStateHandler(null);
}
}
private class NullReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
abortBroadcast();
}
}
}

View File

@ -22,12 +22,12 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import com.actionbarsherlock.app.SherlockActivity;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import com.actionbarsherlock.app.SherlockActivity;
/**
* Base Activity for changing/prompting local encryption passphrase.
*
@ -44,9 +44,14 @@ public abstract class PassphraseActivity extends SherlockActivity {
bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
protected MasterSecret getMasterSecret() {
return masterSecret;
}
protected abstract void cleanup();
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
keyCachingService.setMasterSecret(masterSecret);
@ -55,9 +60,12 @@ public abstract class PassphraseActivity extends SherlockActivity {
MemoryCleaner.clean(masterSecret);
cleanup();
PassphraseActivity.this.setResult(RESULT_OK);
PassphraseActivity.this.finish();
}
@Override
public void onServiceDisconnected(ComponentName name) {
keyCachingService = null;
}

View File

@ -16,18 +16,19 @@
*/
package org.thoughtcrime.securesms;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.Util;
/**
* Activity for creating a user's local encryption passphrase.
@ -37,10 +38,12 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
public class PassphraseCreateActivity extends PassphraseActivity {
private LinearLayout createLayout;
private LinearLayout progressLayout;
private EditText passphraseEdit;
private EditText passphraseRepeatEdit;
private Button okButton;
private Button cancelButton;
public PassphraseCreateActivity() { }
@ -54,10 +57,11 @@ public class PassphraseCreateActivity extends PassphraseActivity {
}
private void initializeResources() {
this.createLayout = (LinearLayout)findViewById(R.id.create_layout);
this.progressLayout = (LinearLayout)findViewById(R.id.progress_layout);
this.passphraseEdit = (EditText) findViewById(R.id.passphrase_edit);
this.passphraseRepeatEdit = (EditText) findViewById(R.id.passphrase_edit_repeat);
this.okButton = (Button) findViewById(R.id.ok_button);
this.cancelButton = (Button) findViewById(R.id.cancel_button);
this.okButton.setOnClickListener(new View.OnClickListener() {
@Override
@ -65,45 +69,36 @@ public class PassphraseCreateActivity extends PassphraseActivity {
verifyAndSavePassphrases();
}
});
this.cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void verifyAndSavePassphrases() {
if (Util.isEmpty(this.passphraseEdit) || Util.isEmpty(this.passphraseRepeatEdit)) {
Toast.makeText(this, R.string.PassphraseCreateActivity_you_must_specify_a_password, Toast.LENGTH_SHORT).show();
return;
}
String passphrase = this.passphraseEdit.getText().toString();
String passphraseRepeat = this.passphraseRepeatEdit.getText().toString();
if (!passphrase.equals(passphraseRepeat)) {
Toast.makeText(getApplicationContext(),
R.string.PassphraseCreateActivity_passphrases_dont_match_exclamation,
Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.PassphraseCreateActivity_passphrases_dont_match, Toast.LENGTH_SHORT).show();
this.passphraseEdit.setText("");
this.passphraseRepeatEdit.setText("");
} else {
return;
}
// We do this, but the edit boxes are basically impossible to clean up.
MemoryCleaner.clean(passphraseRepeat);
new SecretGenerator().execute(passphrase);
}
}
private class SecretGenerator extends AsyncTask<String, Void, Void> {
private ProgressDialog progressDialog;
private MasterSecret masterSecret;
@Override
protected void onPreExecute() {
progressDialog = new ProgressDialog(PassphraseCreateActivity.this);
progressDialog.setTitle(R.string.PassphraseCreateActivity_generating_keypair);
progressDialog.setMessage(getString(R.string.PassphraseCreateActivity_generating_a_local_encryption_keypair));
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.show();
createLayout.setVisibility(View.GONE);
progressLayout.setVisibility(View.VISIBLE);
}
@Override
@ -123,7 +118,6 @@ public class PassphraseCreateActivity extends PassphraseActivity {
@Override
protected void onPostExecute(Void param) {
progressDialog.dismiss();
setMasterSecret(masterSecret);
}
}

View File

@ -38,7 +38,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private EditText passphraseText;
private Button okButton;
private Button cancelButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -51,13 +50,12 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private void initializeResources() {
passphraseText = (EditText)findViewById(R.id.passphrase_edit);
okButton = (Button)findViewById(R.id.ok_button);
cancelButton = (Button)findViewById(R.id.cancel_button);
okButton.setOnClickListener(new OkButtonClickListener());
cancelButton.setOnClickListener(new CancelButtonClickListener());
}
private class OkButtonClickListener implements OnClickListener {
@Override
public void onClick(View v) {
try {
Editable text = passphraseText.getText();
@ -74,16 +72,9 @@ public class PassphrasePromptActivity extends PassphraseActivity {
}
}
private class CancelButtonClickListener implements OnClickListener {
public void onClick(View v) {
finish();
}
}
@Override
protected void cleanup() {
this.passphraseText = null;
System.gc();
}
}

View File

@ -0,0 +1,218 @@
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.net.Uri;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
public class RoutingActivity extends PassphraseRequiredSherlockActivity {
private static final int STATE_CREATE_PASSPHRASE = 1;
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_IMPORT_DATABASE = 3;
private static final int STATE_CONVERSATION_OR_LIST = 4;
private MasterSecret masterSecret = null;
private boolean isVisible = false;
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onResume() {
this.isVisible = true;
super.onResume();
}
@Override
public void onPause() {
this.isVisible = false;
super.onPause();
}
@Override
public void onNewMasterSecret(MasterSecret masterSecret) {
this.masterSecret = masterSecret;
if (isVisible) {
routeApplicationState();
}
}
@Override
public void onMasterSecretCleared() {
this.masterSecret = null;
if (isVisible) {
routeApplicationState();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_CANCELED)
finish();
}
private void routeApplicationState() {
int state = getApplicationState();
switch (state) {
case STATE_CREATE_PASSPHRASE: handleCreatePassphrase(); break;
case STATE_PROMPT_PASSPHRASE: handlePromptPassphrase(); break;
case STATE_IMPORT_DATABASE: handleImportDatabase(); break;
case STATE_CONVERSATION_OR_LIST: handleDisplayConversationOrList(); break;
}
}
private void handleCreatePassphrase() {
Intent intent = new Intent(this, PassphraseCreateActivity.class);
startActivityForResult(intent, 1);
}
private void handlePromptPassphrase() {
Intent intent = new Intent(this, PassphrasePromptActivity.class);
startActivityForResult(intent, 2);
}
private void handleImportDatabase() {
Intent intent = new Intent(this, DatabaseMigrationActivity.class);
intent.putExtra("master_secret", masterSecret);
intent.putExtra("next_intent", getConversationListIntent());
startActivity(intent);
finish();
}
private void handleDisplayConversationOrList() {
ConversationParameters parameters = getConversationParameters();
Intent intent;
if (isShareAction() || parameters.recipients != null) {
intent = getConversationIntent(parameters);
} else {
intent = getConversationListIntent();
}
startActivity(intent);
finish();
}
private Intent getConversationIntent(ConversationParameters parameters) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, parameters.recipients);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, parameters.thread);
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, parameters.draftText);
intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, parameters.draftImage);
intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, parameters.draftAudio);
return intent;
}
private Intent getConversationListIntent() {
Intent intent = new Intent(this, ConversationListActivity.class);
intent.putExtra("master_secret", masterSecret);
return intent;
}
private int getApplicationState() {
if (!MasterSecretUtil.isPassphraseInitialized(this))
return STATE_CREATE_PASSPHRASE;
if (masterSecret == null)
return STATE_PROMPT_PASSPHRASE;
if (!ApplicationMigrationService.isDatabaseImported(this))
return STATE_IMPORT_DATABASE;
return STATE_CONVERSATION_OR_LIST;
}
private ConversationParameters getConversationParameters() {
if (isSendAction()) {
return getConversationParametersForSendAction();
} else if (isShareAction()) {
return getConversationParametersForShareAction();
} else {
return getConversationParametersForInternalAction();
}
}
private ConversationParameters getConversationParametersForSendAction() {
Recipients recipients = null;
long threadId = getIntent().getLongExtra("thread_id", -1);
try {
String data = getIntent().getData().getSchemeSpecificPart();
recipients = RecipientFactory.getRecipientsFromString(this, data, false);
threadId = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
} catch (RecipientFormattingException rfe) {
recipients = null;
}
return new ConversationParameters(threadId, recipients, null, null, null);
}
private ConversationParameters getConversationParametersForShareAction() {
String type = getIntent().getType();
String draftText = null;
Uri draftImage = null;
Uri draftAudio = null;
if ("text/plain".equals(type)) {
draftText = getIntent().getStringExtra(Intent.EXTRA_TEXT);
} else if (type.startsWith("image/")) {
draftImage = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
} else if (type.startsWith("audio/")) {
draftAudio = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
}
return new ConversationParameters(-1, null, draftText, draftImage, draftAudio);
}
private ConversationParameters getConversationParametersForInternalAction() {
long threadId = getIntent().getLongExtra("thread_id", -1);
Recipients recipients = getIntent().getParcelableExtra("recipients");
return new ConversationParameters(threadId, recipients, null, null, null);
}
private boolean isShareAction() {
return Intent.ACTION_SEND.equals(getIntent().getAction());
}
private boolean isSendAction() {
return Intent.ACTION_SENDTO.equals(getIntent().getAction());
}
private static class ConversationParameters {
public final long thread;
public final Recipients recipients;
public final String draftText;
public final Uri draftImage;
public final Uri draftAudio;
public ConversationParameters(long thread, Recipients recipients,
String draftText, Uri draftImage, Uri draftAudio)
{
this.thread = thread;
this.recipients = recipients;
this.draftText = draftText;
this.draftImage = draftImage;
this.draftAudio = draftAudio;
}
}
}

View File

@ -16,6 +16,15 @@
*/
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.thoughtcrime.securesms.util.Base64;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
@ -31,15 +40,6 @@ import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.thoughtcrime.securesms.util.Base64;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
/**
* Helper class for generating and securely storing a MasterSecret.
*
@ -134,6 +134,11 @@ public class MasterSecretUtil {
return settings.contains(ASYMMETRIC_LOCAL_PUBLIC);
}
public static boolean isPassphraseInitialized(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, 0);
return preferences.getBoolean("passphrase_initialized", false);
}
private static void encryptWithPassphraseAndSave(Context context, byte[] masterSecret, String passphrase) throws GeneralSecurityException {
byte[] encryptedMasterSecret = encryptWithPassphrase(context, masterSecret, passphrase);
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(context, encryptedMasterSecret, passphrase);

View File

@ -150,7 +150,7 @@ public class SmsMigrator {
private static void migrateConversation(Context context, MasterSecret masterSecret,
SmsMigrationProgressListener listener,
int primaryProgress,
ProgressDescription progress,
long theirThreadId, long ourThreadId)
{
SmsDatabase ourSmsDatabase = DatabaseFactory.getSmsDatabase(context);
@ -166,11 +166,7 @@ public class SmsMigrator {
getContentValuesForRow(context, masterSecret, cursor, ourThreadId, statement);
statement.execute();
double position = cursor.getPosition();
double count = cursor.getCount();
double progress = position / count;
listener.progressUpdate(primaryProgress, (int)(progress * 10000));
listener.progressUpdate(new ProgressDescription(progress, cursor.getCount(), cursor.getPosition()));
}
ourSmsDatabase.endTransaction(transaction);
@ -192,7 +188,6 @@ public class SmsMigrator {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Cursor cursor = null;
int primaryProgress = 0;
try {
Uri threadListUri = Uri.parse("content://mms-sms/conversations?simple=true");
@ -202,21 +197,17 @@ public class SmsMigrator {
long theirThreadId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
String theirRecipients = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
Recipients ourRecipients = getOurRecipients(context, theirRecipients);
ProgressDescription progress = new ProgressDescription(cursor.getCount(), cursor.getPosition(), 100, 0);
if (ourRecipients != null) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients);
migrateConversation(context, masterSecret,
listener, primaryProgress,
listener, progress,
theirThreadId, ourThreadId);
}
double position = cursor.getPosition() + 1;
double count = cursor.getCount();
double progress = position / count;
primaryProgress = (int)(progress * 10000);
listener.progressUpdate(primaryProgress, 0);
progress.incrementPrimaryComplete();
listener.progressUpdate(progress);
}
} finally {
if (cursor != null)
@ -228,6 +219,34 @@ public class SmsMigrator {
}
public interface SmsMigrationProgressListener {
public void progressUpdate(int primaryProgress, int secondaryProgress);
public void progressUpdate(ProgressDescription description);
}
public static class ProgressDescription {
public final int primaryTotal;
public int primaryComplete;
public final int secondaryTotal;
public final int secondaryComplete;
public ProgressDescription(int primaryTotal, int primaryComplete,
int secondaryTotal, int secondaryComplete)
{
this.primaryTotal = primaryTotal;
this.primaryComplete = primaryComplete;
this.secondaryTotal = secondaryTotal;
this.secondaryComplete = secondaryComplete;
}
public ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
this.primaryComplete = that.primaryComplete;
this.primaryTotal = that.primaryTotal;
this.secondaryComplete = secondaryComplete;
this.secondaryTotal = secondaryTotal;
}
public void incrementPrimaryComplete() {
primaryComplete += 1;
}
}
}

View File

@ -37,8 +37,8 @@ import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@ -79,7 +79,7 @@ public class MessageNotifier {
if (visibleThread == threadId) {
sendInThreadNotification(context);
} else {
Intent intent = new Intent(context, ConversationListActivity.class);
Intent intent = new Intent(context, RoutingActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("recipients", recipients);
intent.putExtra("thread_id", threadId);
@ -187,7 +187,7 @@ public class MessageNotifier {
notificationState.getMessageCount()));
builder.setContentText(String.format(context.getString(R.string.MessageNotifier_most_recent_from_s),
notifications.get(0).getRecipientName()));
builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, ConversationListActivity.class), 0));
builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, RoutingActivity.class), 0));
InboxStyle style = new InboxStyle();

View File

@ -6,7 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.text.SpannableStringBuilder;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util;
@ -62,7 +62,7 @@ public class NotificationItem {
}
public PendingIntent getPendingIntent(Context context) {
Intent intent = new Intent(context, ConversationListActivity.class);
Intent intent = new Intent(context, RoutingActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (recipients.getPrimaryRecipient() != null) {

View File

@ -4,41 +4,64 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.widget.RemoteViews;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.SmsMigrator;
import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class ApplicationMigrationService extends Service
implements SmsMigrator.SmsMigrationProgressListener
{
public static final int PROGRESS_UPDATE = 1;
public static final int PROGRESS_COMPLETE = 2;
public static final String MIGRATE_DATABASE = "org.thoughtcrime.securesms.ApplicationMigration.MIGRATE_DATABSE";
public static final String COMPLETED_ACTION = "org.thoughtcrime.securesms.ApplicationMigrationService.COMPLETED";
private static final String PREFERENCES_NAME = "SecureSMS";
private static final String DATABASE_MIGRATED = "migrated";
private final BroadcastReceiver completedReceiver = new CompletedReceiver();
private final Binder binder = new ApplicationMigrationBinder();
private boolean isMigrating = false;
private final Executor executor = Executors.newSingleThreadExecutor();
private Handler handler = null;
private Notification notification = null;
private NotificationCompat.Builder notification = null;
private ImportState state = new ImportState(ImportState.STATE_IDLE, null);
@Override
public void onStart(Intent intent, int startId) {
if (intent == null) return;
public void onCreate() {
registerCompletedReceiver();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_NOT_STICKY;
if (intent.getAction() != null && intent.getAction().equals(MIGRATE_DATABASE)) {
handleDatabaseMigration((MasterSecret)intent.getParcelableExtra("master_secret"));
executor.execute(new ImportRunnable(intent));
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
unregisterCompletedReceiver();
}
@Override
@ -46,70 +69,106 @@ public class ApplicationMigrationService extends Service
return binder;
}
private void handleDatabaseMigration(final MasterSecret masterSecret) {
this.notification = initializeBackgroundNotification();
public void setImportStateHandler(Handler handler) {
this.handler = handler;
}
final PowerManager power = (PowerManager)getSystemService(Context.POWER_SERVICE);
final WakeLock wakeLock = power.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Migration");
private void registerCompletedReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(COMPLETED_ACTION);
registerReceiver(completedReceiver, filter);
}
private void unregisterCompletedReceiver() {
unregisterReceiver(completedReceiver);
}
private void notifyImportComplete() {
Intent intent = new Intent();
intent.setAction(COMPLETED_ACTION);
sendOrderedBroadcast(intent, null);
}
@Override
public void progressUpdate(ProgressDescription progress) {
setState(new ImportState(ImportState.STATE_MIGRATING_IN_PROGRESS, progress));
}
public ImportState getState() {
return state;
}
private void setState(ImportState state) {
this.state = state;
if (handler != null) {
handler.obtainMessage(state.state, state.progress).sendToTarget();
}
if (state.progress != null && state.progress.secondaryComplete == 0) {
updateBackgroundNotification(state.progress.primaryTotal, state.progress.primaryComplete);
}
}
private void updateBackgroundNotification(int total, int complete) {
notification.setProgress(total, complete, false);
((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE))
.notify(4242, notification.build());
}
private NotificationCompat.Builder initializeBackgroundNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.icon_notification);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_notification));
builder.setContentTitle(getString(R.string.ApplicationMigrationService_importing_text_messages));
builder.setContentText(getString(R.string.ApplicationMigrationService_import_in_progress));
builder.setOngoing(true);
builder.setProgress(100, 0, false);
builder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, RoutingActivity.class), 0));
stopForeground(true);
startForeground(4242, builder.build());
return builder;
}
private class ImportRunnable implements Runnable {
private final MasterSecret masterSecret;
public ImportRunnable(Intent intent) {
this.masterSecret = intent.getParcelableExtra("master_secret");
Log.w("ApplicationMigrationService", "Service got mastersecret: " + masterSecret);
}
new Thread() {
@Override
public void run() {
notification = initializeBackgroundNotification();
PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Migration");
try {
wakeLock.acquire();
setMigrating(true);
setState(new ImportState(ImportState.STATE_MIGRATING_BEGIN, null));
SmsMigrator.migrateDatabase(ApplicationMigrationService.this,
masterSecret,
ApplicationMigrationService.this);
setMigrating(false);
if (handler != null) {
handler.obtainMessage(PROGRESS_COMPLETE).sendToTarget();
}
setState(new ImportState(ImportState.STATE_MIGRATING_COMPLETE, null));
setDatabaseImported(ApplicationMigrationService.this);
stopForeground(true);
notifyImportComplete();
stopSelf();
} finally {
wakeLock.release();
stopService(new Intent(ApplicationMigrationService.this,
ApplicationMigrationService.class));
}
}
}.start();
}
private Notification initializeBackgroundNotification() {
Intent intent = new Intent(this, ConversationListActivity.class);
Notification notification = new Notification(R.drawable.icon,
getString(R.string.ApplicationMigrationService_migrating),
System.currentTimeMillis());
notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT;
notification.contentView = new RemoteViews(getApplicationContext().getPackageName(),
R.layout.migration_notification_progress);
notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
notification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
notification.contentView.setTextViewText(R.id.status_text,
getString(R.string.ApplicationMigrationService_migrating_system_text_messages));
notification.contentView.setProgressBar(R.id.status_progress, 10000, 0, false);
stopForeground(true);
startForeground(4242, notification);
return notification;
}
private synchronized void setMigrating(boolean isMigrating) {
this.isMigrating = isMigrating;
}
public synchronized boolean isMigrating() {
return isMigrating;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
public class ApplicationMigrationBinder extends Binder {
@ -118,20 +177,44 @@ public class ApplicationMigrationService extends Service
}
}
private class CompletedReceiver extends BroadcastReceiver {
@Override
public void progressUpdate(int primaryProgress, int secondaryProgress) {
if (handler != null) {
handler.obtainMessage(PROGRESS_UPDATE, primaryProgress, secondaryProgress).sendToTarget();
public void onReceive(Context context, Intent intent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.icon_notification);
builder.setContentTitle("Import Complete");
builder.setContentText("TextSecure system database import is complete.");
builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, RoutingActivity.class), 0));
builder.setWhen(System.currentTimeMillis());
builder.setDefaults(Notification.DEFAULT_VIBRATE);
builder.setAutoCancel(true);
Notification notification = builder.build();
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(31337, notification);
}
}
if (notification != null && secondaryProgress == 0) {
notification.contentView.setProgressBar(R.id.status_progress, 10000, primaryProgress, false);
public static class ImportState {
public static final int STATE_IDLE = 0;
public static final int STATE_MIGRATING_BEGIN = 1;
public static final int STATE_MIGRATING_IN_PROGRESS = 2;
public static final int STATE_MIGRATING_COMPLETE = 3;
NotificationManager notificationManager =
(NotificationManager)getApplicationContext()
.getSystemService(Context.NOTIFICATION_SERVICE);
public int state;
public ProgressDescription progress;
notificationManager.notify(4242, notification);
public ImportState(int state, ProgressDescription progress) {
this.state = state;
this.progress = progress;
}
}
public static boolean isDatabaseImported(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
.getBoolean(DATABASE_MIGRATED, false);
}
public static void setDatabaseImported(Context context) {
context.getSharedPreferences(PREFERENCES_NAME, 0).edit().putBoolean(DATABASE_MIGRATED, true).commit();
}
}

View File

@ -32,8 +32,9 @@ import android.util.Log;
import android.widget.RemoteViews;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -54,8 +55,6 @@ public class KeyCachingService extends Service {
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
public static final String ACTIVITY_START_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_START_EVENT";
public static final String ACTIVITY_STOP_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_STOP_EVENT";
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
private PendingIntent pending;
private int activitiesRunning = 0;
@ -75,6 +74,7 @@ public class KeyCachingService extends Service {
foregroundService();
broadcastNewSecret();
startTimeoutIfAppropriate();
DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
new Thread() {
@Override
@ -178,7 +178,9 @@ public class KeyCachingService extends Service {
Notification notification = new Notification(R.drawable.icon_cached,
getString(R.string.KeyCachingService_textsecure_passphrase_cached),
System.currentTimeMillis());
Intent intent = new Intent(this, ConversationListActivity.class);
Intent intent = new Intent(this, RoutingActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent launchIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
notification.setLatestEventInfo(getApplicationContext(),
getString(R.string.KeyCachingService_passphrase_cached),