Enhanced import/export support.
1) Allow imports from the stock SMS database at any time. 2) Provide plaintext export support, in a format compatible with the "SMS Backup And Restore" app. 3) Fix the DB weirdness on encrypted restore that previously required killing the app.
@ -68,12 +68,15 @@
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
|
||||
<activity android:name=".ImportExportActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PromptApnActivity"
|
||||
android:label="Configure MMS Settings"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
|
||||
<activity android:name=".ConversationListActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
|
BIN
res/drawable-hdpi/card.9.png
Normal file
After Width: | Height: | Size: 266 B |
BIN
res/drawable-hdpi/encrypted_backup.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-hdpi/plaintext_backup.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-hdpi/stock_sms.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/card.9.png
Normal file
After Width: | Height: | Size: 266 B |
BIN
res/drawable-mdpi/encrypted_backup.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-mdpi/plaintext_backup.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-mdpi/stock_sms.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-xhdpi/card.9.png
Normal file
After Width: | Height: | Size: 315 B |
BIN
res/drawable-xhdpi/encrypted_backup.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-xhdpi/plaintext_backup.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-xhdpi/stock_sms.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
6
res/drawable/clickable_card.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:drawable/list_selector_background"
|
||||
android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/card" />
|
||||
</selector>
|
98
res/layout/export_fragment.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#ffeaeaea"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="8dip"
|
||||
android:background="#ffeaeaea">
|
||||
|
||||
|
||||
<LinearLayout android:id="@+id/export_encrypted_backup"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/clickable_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="10dip"
|
||||
android:src="@drawable/encrypted_backup"/>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Registration.Description"
|
||||
android:text="@string/export_fragment__export_encrypted_backup"/>
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/export_fragment__export_an_encrypted_backup_to_the_sd_card"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout android:id="@+id/export_plaintext_backup"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/clickable_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="10dip"
|
||||
android:src="@drawable/plaintext_backup"/>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Registration.Description"
|
||||
android:text="@string/export_fragment__export_plaintext_backup"/>
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/export_fragment__export_a_plaintext_backup_compatible_with"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
6
res/layout/import_export_activity.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v4.view.ViewPager
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/import_export_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
136
res/layout/import_fragment.xml
Normal file
@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#ffeaeaea"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="8dip"
|
||||
android:background="#ffeaeaea">
|
||||
|
||||
<LinearLayout android:id="@+id/import_sms"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/clickable_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="10dip"
|
||||
android:src="@drawable/stock_sms"/>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Registration.Description"
|
||||
android:text="@string/import_fragment__import_system_sms_database"/>
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/import_fragment__import_the_database_from_the_default_system"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/import_encrypted_backup"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/clickable_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="10dip"
|
||||
android:src="@drawable/encrypted_backup"/>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Registration.Description"
|
||||
android:text="@string/import_fragment__import_encrypted_backup"/>
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/import_fragment__restore_a_previously_exported_encrypted_textsecure_backup"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout android:id="@+id/import_plaintext_backup"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:background="@drawable/clickable_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="10dip"
|
||||
android:src="@drawable/plaintext_backup"/>
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Registration.Description"
|
||||
android:text="@string/import_fragment__import_plaintext_backup"/>
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/import_fragment__import_a_plaintext_backup_file"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -13,19 +13,6 @@
|
||||
android:id="@+id/menu_mark_all_read"
|
||||
android:icon="@android:drawable/ic_menu_set_as" />
|
||||
|
||||
<item android:title="@string/text_secure_normal__menu_import_export"
|
||||
android:icon="@android:drawable/ic_menu_save">
|
||||
<menu>
|
||||
<item android:title="@string/text_secure_normal__menu_import"
|
||||
android:id="@+id/menu_import"
|
||||
android:icon="@android:drawable/ic_menu_revert" />
|
||||
|
||||
<item android:title="@string/text_secure_normal__menu_export"
|
||||
android:id="@+id/menu_export"
|
||||
android:icon="@android:drawable/ic_menu_save" />
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<item android:title="@string/text_secure_normal__menu_settings"
|
||||
android:id="@+id/menu_settings"
|
||||
android:icon="@android:drawable/ic_menu_preferences" />
|
||||
|
@ -104,7 +104,62 @@
|
||||
|
||||
<!-- ConversationListItem -->
|
||||
<string name="ConversationListItem_key_exchange_message">Key exchange message...</string>
|
||||
|
||||
|
||||
<!-- ExportFragment -->
|
||||
<string name="ExportFragment_export_to_sd_card">Export To SD Card?</string>
|
||||
<string name="ExportFragment_this_will_export_your_encrypted_keys_settings_and_messages">This
|
||||
will export your encrypted keys, settings, and messages to the SD card.
|
||||
</string>
|
||||
<string name="ExportFragment_export">Export</string>
|
||||
<string name="ExportFragment_export_plaintext_to_sd_card">Export Plaintext To SD Card?</string>
|
||||
<string name="ExportFragment_warning_this_will_export_the_plaintext_contents">Warning, this will
|
||||
export the plaintext contents of your TextSecure messages to the SD card.
|
||||
</string>
|
||||
<string name="ExportFragment_cancel">Cancel</string>
|
||||
<string name="ExportFragment_exporting">Exporting</string>
|
||||
<string name="ExportFragment_exporting_plaintext_to_sd_card">Exporting plaintext to SD card...
|
||||
</string>
|
||||
<string name="ExportFragment_error_unable_to_write_to_sd_card">Error, unable to write to SD
|
||||
card!
|
||||
</string>
|
||||
<string name="ExportFragment_error_while_writing_to_sd_card">Error while writing to SD card.
|
||||
</string>
|
||||
<string name="ExportFragment_success">Success!</string>
|
||||
<string name="ExportFragment_exporting_keys_settings_and_messages">Exporting encrypted keys,
|
||||
settings, and messages...
|
||||
</string>
|
||||
|
||||
<!-- ImportFragment -->
|
||||
<string name="ImportFragment_import_system_sms_database">Import System SMS Database?</string>
|
||||
<string name="ImportFragment_this_will_import_messages_from_the_system">This will import
|
||||
messages from the system\'s default SMS database to TextSecure. If you\'ve previously
|
||||
imported the system\'s SMS database, importing again will result in duplicated messages.
|
||||
</string>
|
||||
<string name="ImportFragment_import">Import</string>
|
||||
<string name="ImportFragment_cancel">Cancel</string>
|
||||
<string name="ImportFragment_restore_encrypted_backup">Restore Encrypted Backup?</string>
|
||||
<string name="ImportFragment_restoring_an_encrypted_backup_will_completely_replace_your_existing_keys">
|
||||
Restoring an encrypted backup will completely replace your existing keys, preferences, and
|
||||
messages. You will lose any information that\'s in your current TextSecure install but not
|
||||
in the backup.
|
||||
</string>
|
||||
<string name="ImportFragment_restore">Restore</string>
|
||||
<string name="ImportFragment_import_plaintext_backup">Import Plaintext Backup?</string>
|
||||
<string name="ImportFragment_this_will_import_messages_from_a_plaintext_backup">This will import
|
||||
messages from a plaintext backup. If you\'ve previously imported the system\'s SMS database,
|
||||
importing again will result in duplicated messages.
|
||||
</string>
|
||||
<string name="ImportFragment_importing">Importing</string>
|
||||
<string name="ImportFragment_import_plaintext_backup_elipse">Import plaintext backup...</string>
|
||||
<string name="ImportFragment_no_plaintext_backup_found">No plaintext backup found!</string>
|
||||
<string name="ImportFragment_error_importing_backup">Error importing backup!</string>
|
||||
<string name="ImportFragment_import_complete">Import complete!</string>
|
||||
<string name="ImportFragment_restoring">Restoring</string>
|
||||
<string name="ImportFragment_restoring_encrypted_backup">Restoring encrypted backup...</string>
|
||||
<string name="ImportFragment_no_encrypted_backup_found">No encrypted backup found!</string>
|
||||
<string name="ImportFragment_restore_complete">Restore complete!</string>
|
||||
|
||||
|
||||
<!-- KeyScanningActivity -->
|
||||
<string name="KeyScanningActivity_no_scanned_key_found_exclamation">No scanned key found!</string>
|
||||
|
||||
@ -322,6 +377,26 @@
|
||||
<!-- database_upgrade_activity -->
|
||||
<string name="database_upgrade_activity__updating_database">Updating Database...</string>
|
||||
|
||||
<string name="export_fragment__export_encrypted_backup">Export Encrypted Backup</string>
|
||||
<string name="export_fragment__export_an_encrypted_backup_to_the_sd_card">Export an encrypted
|
||||
backup to the SD card.
|
||||
</string>
|
||||
<string name="export_fragment__export_plaintext_backup">Export Plaintext Backup</string>
|
||||
<string name="export_fragment__export_a_plaintext_backup_compatible_with">
|
||||
Export a plaintext backup compatible with \'SMSBackup And Restore\' to the SD card.</string>
|
||||
<string name="import_fragment__import_system_sms_database">Import System SMS Database</string>
|
||||
<string name="import_fragment__import_the_database_from_the_default_system">Import the database
|
||||
from the default system messenger app.
|
||||
</string>
|
||||
<string name="import_fragment__import_encrypted_backup">Import Encrypted Backup</string>
|
||||
<string name="import_fragment__restore_a_previously_exported_encrypted_textsecure_backup">
|
||||
Restore a previously exported encrypted TextSecure backup.
|
||||
</string>
|
||||
<string name="import_fragment__import_plaintext_backup">Import Plaintext Backup</string>
|
||||
<string name="import_fragment__import_a_plaintext_backup_file">
|
||||
Import a plaintext backup file. Compatible with \'SMSBackup And Restore.\'</string>
|
||||
|
||||
|
||||
<!-- prompt_passphrase_activity -->
|
||||
<string name="prompt_passphrase_activity__textsecure_passphrase">TEXTSECURE PASSPHRASE</string>
|
||||
<string name="prompt_passphrase_activity__unlock">Unlock</string>
|
||||
|
@ -20,8 +20,8 @@
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightTheme.NavigationDrawer" parent="@style/TextSecure.LightTheme">
|
||||
<item name="android:homeAsUpIndicator">@drawable/ic_drawer</item>
|
||||
<item name="homeAsUpIndicator">@drawable/ic_drawer</item>
|
||||
|
||||
<item name="navigation_drawer_background">@color/abs__background_holo_light</item>
|
||||
<item name="navigation_drawer_text_color">#ff333333</item>
|
||||
<item name="navigation_drawer_icons">@array/navigation_drawer_icons_light</item>
|
||||
@ -49,6 +49,7 @@
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkTheme.NavigationDrawer" parent="@style/TextSecure.DarkTheme">
|
||||
<item name="android:homeAsUpIndicator">@drawable/ic_drawer</item>
|
||||
<item name="homeAsUpIndicator">@drawable/ic_drawer</item>
|
||||
<item name="navigation_drawer_background">#ff333333</item>
|
||||
<item name="navigation_drawer_text_color">#ffdddddd</item>
|
||||
|
@ -1,147 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.database.ApplicationExporter;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ApplicationExportManager extends Handler implements Runnable {
|
||||
|
||||
private static final int ERROR_NO_SD = 0;
|
||||
private static final int ERROR_IO = 1;
|
||||
private static final int COMPLETE = 2;
|
||||
|
||||
private static final int TASK_EXPORT = 0;
|
||||
private static final int TASK_IMPORT = 1;
|
||||
|
||||
private int task;
|
||||
private ProgressDialog progressDialog;
|
||||
private ApplicationExportListener listener;
|
||||
|
||||
private final Context context;
|
||||
|
||||
public ApplicationExportManager(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setListener(ApplicationExportListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void importDatabase() {
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
|
||||
alertBuilder.setTitle(R.string.ApplicationExportManager_import_database_and_settings_title);
|
||||
alertBuilder.setMessage(R.string.ApplicationExportManager_import_database_and_settings_message);
|
||||
alertBuilder.setCancelable(false);
|
||||
alertBuilder.setPositiveButton(R.string.ApplicationExportManager_import, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
task = TASK_IMPORT;
|
||||
progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setTitle(context.getString(R.string.ApplicationExportManager_importing_database_and_keys));
|
||||
progressDialog.setMessage(context
|
||||
.getString(R.string.ApplicationExportManager_importing_your_sms_database_keys_and_settings));
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progressDialog.show();
|
||||
|
||||
if (listener != null)
|
||||
listener.onPrepareForImport();
|
||||
|
||||
new Thread(ApplicationExportManager.this).start();
|
||||
}
|
||||
});
|
||||
|
||||
alertBuilder.setNegativeButton(android.R.string.cancel, null);
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
|
||||
|
||||
public void exportDatabase() {
|
||||
Log.w("ApplicationExportManager", "Context: " + context);
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
|
||||
alertBuilder.setTitle(R.string.ApplicationExportManager_export_database_question);
|
||||
alertBuilder.setMessage(R.string.ApplicationExportManager_export_textsecure_database_keys_and_settings_prompt);
|
||||
alertBuilder.setCancelable(false);
|
||||
|
||||
alertBuilder.setPositiveButton(R.string.ApplicationExportManager_export, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
task = TASK_EXPORT;
|
||||
progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setTitle(context.getString(R.string.ApplicationExportManager_exporting_database_and_keys));
|
||||
progressDialog.setMessage(context
|
||||
.getString(R.string.ApplicationExportManager_exporting_your_sms_database_keys_and_settings));
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progressDialog.show();
|
||||
|
||||
new Thread(ApplicationExportManager.this).start();
|
||||
}
|
||||
});
|
||||
|
||||
alertBuilder.setNegativeButton(android.R.string.cancel, null);
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
switch (task) {
|
||||
case TASK_EXPORT: ApplicationExporter.exportToSd(context); break;
|
||||
case TASK_IMPORT: ApplicationExporter.importFromSd(context); break;
|
||||
}
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("SecureSMS", e);
|
||||
this.obtainMessage(ERROR_NO_SD).sendToTarget();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.w("SecureSMS", e);
|
||||
this.obtainMessage(ERROR_IO).sendToTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
this.obtainMessage(COMPLETE).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case ERROR_NO_SD:
|
||||
Toast.makeText(context, R.string.ApplicationExportManager_no_sd_card_found_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ERROR_IO:
|
||||
Toast.makeText(context, R.string.ApplicationExportManager_error_exporting_to_sd_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case COMPLETE:
|
||||
switch (task) {
|
||||
case TASK_IMPORT:
|
||||
Toast.makeText(context, R.string.ApplicationExportManager_import_successful_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case TASK_EXPORT:
|
||||
Toast.makeText(context, R.string.ApplicationExportManager_export_successful_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
|
||||
public interface ApplicationExportListener {
|
||||
public void onPrepareForImport();
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ import android.widget.SimpleAdapter;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import org.thoughtcrime.securesms.ApplicationExportManager.ApplicationExportListener;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -100,7 +99,8 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
|
||||
Intent intent;
|
||||
|
||||
if (selected.equals("import_export")) {
|
||||
intent = new Intent();
|
||||
intent = new Intent(this, ImportExportActivity.class);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
} else if (selected.equals("my_identity_key")) {
|
||||
intent = new Intent(this, ViewIdentityActivity.class);
|
||||
intent.putExtra("identity_key", IdentityKeyUtil.getIdentityKey(this));
|
||||
@ -126,8 +126,6 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_new_message: createConversation(-1, null, defaultType); 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;
|
||||
case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
|
||||
case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
|
||||
case android.R.id.home: handleNavigationDrawerToggle(); return true;
|
||||
@ -165,25 +163,6 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
|
||||
startActivity(preferencesIntent);
|
||||
}
|
||||
|
||||
private void handleExportDatabase() {
|
||||
ApplicationExportManager exportManager = new ApplicationExportManager(this);
|
||||
exportManager.exportDatabase();
|
||||
}
|
||||
|
||||
private void handleImportDatabase() {
|
||||
ApplicationExportManager exportManager = new ApplicationExportManager(this);
|
||||
ApplicationExportListener listener = new ApplicationExportManager.ApplicationExportListener() {
|
||||
@Override
|
||||
public void onPrepareForImport() {
|
||||
onMasterSecretCleared();
|
||||
handleClearPassphrase();
|
||||
}
|
||||
};
|
||||
|
||||
exportManager.setListener(listener);
|
||||
exportManager.importDatabase();
|
||||
}
|
||||
|
||||
private void handleClearPassphrase() {
|
||||
Intent intent = new Intent(this, KeyCachingService.class);
|
||||
intent.setAction(KeyCachingService.CLEAR_KEY_ACTION);
|
||||
|
@ -65,6 +65,11 @@ public class DatabaseMigrationActivity extends PassphraseRequiredSherlockActivit
|
||||
shutdownServiceBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
||||
}
|
||||
|
||||
private void initializeServiceBinding() {
|
||||
Intent intent = new Intent(this, ApplicationMigrationService.class);
|
||||
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
|
197
src/org/thoughtcrime/securesms/ExportFragment.java
Normal file
@ -0,0 +1,197 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.EncryptedBackupExporter;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.database.PlaintextBackupExporter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class ExportFragment extends SherlockFragment {
|
||||
|
||||
private static final int SUCCESS = 0;
|
||||
private static final int NO_SD_CARD = 1;
|
||||
private static final int IO_ERROR = 2;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
public void setMasterSecret(MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
View layout = inflater.inflate(R.layout.export_fragment, container, false);
|
||||
View exportEncryptedView = layout.findViewById(R.id.export_encrypted_backup);
|
||||
View exportPlaintextView = layout.findViewById(R.id.export_plaintext_backup);
|
||||
|
||||
exportEncryptedView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleExportEncryptedBackup();
|
||||
}
|
||||
});
|
||||
|
||||
exportPlaintextView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleExportPlaintextBackup();
|
||||
}
|
||||
});
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
private void handleExportEncryptedBackup() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setTitle(getActivity().getString(R.string.ExportFragment_export_to_sd_card));
|
||||
builder.setMessage(getActivity().getString(R.string.ExportFragment_this_will_export_your_encrypted_keys_settings_and_messages));
|
||||
builder.setPositiveButton(getActivity().getString(R.string.ExportFragment_export), new Dialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new ExportEncryptedTask().execute();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getActivity().getString(R.string.ExportFragment_cancel), null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleExportPlaintextBackup() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(getActivity().getString(R.string.ExportFragment_export_plaintext_to_sd_card));
|
||||
builder.setMessage(getActivity().getString(R.string.ExportFragment_warning_this_will_export_the_plaintext_contents));
|
||||
builder.setPositiveButton(getActivity().getString(R.string.ExportFragment_export), new Dialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new ExportPlaintextTask().execute();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getActivity().getString(R.string.ExportFragment_cancel), null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private class ExportPlaintextTask extends AsyncTask<Void, Void, Integer> {
|
||||
private ProgressDialog dialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.ExportFragment_exporting),
|
||||
getActivity().getString(R.string.ExportFragment_exporting_plaintext_to_sd_card),
|
||||
true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
PlaintextBackupExporter.exportPlaintextToSd(getActivity(), masterSecret);
|
||||
return SUCCESS;
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("ExportFragment", e);
|
||||
return NO_SD_CARD;
|
||||
} catch (IOException e) {
|
||||
Log.w("ExportFragment", e);
|
||||
return IO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
Context context = getActivity();
|
||||
|
||||
if (dialog != null)
|
||||
dialog.dismiss();
|
||||
|
||||
if (context == null)
|
||||
return;
|
||||
|
||||
switch (result) {
|
||||
case NO_SD_CARD:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ExportFragment_error_unable_to_write_to_sd_card),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case IO_ERROR:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ExportFragment_error_while_writing_to_sd_card),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case SUCCESS:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ExportFragment_success),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ExportEncryptedTask extends AsyncTask<Void, Void, Integer> {
|
||||
private ProgressDialog dialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.ExportFragment_exporting),
|
||||
getActivity().getString(R.string.ExportFragment_exporting_keys_settings_and_messages),
|
||||
true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
Context context = getActivity();
|
||||
|
||||
if (dialog != null) dialog.dismiss();
|
||||
|
||||
if (context == null) return;
|
||||
|
||||
switch (result) {
|
||||
case NO_SD_CARD:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ExportFragment_error_unable_to_write_to_sd_card),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case IO_ERROR:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ExportFragment_error_while_writing_to_sd_card),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case SUCCESS:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ExportFragment_success),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
EncryptedBackupExporter.exportToSd(getActivity());
|
||||
return SUCCESS;
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("ExportFragment", e);
|
||||
return NO_SD_CARD;
|
||||
} catch (IOException e) {
|
||||
Log.w("ExportFragment", e);
|
||||
return IO_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
src/org/thoughtcrime/securesms/ImportExportActivity.java
Normal file
@ -0,0 +1,109 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.ViewPager;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
||||
|
||||
public class ImportExportActivity extends PassphraseRequiredSherlockFragmentActivity {
|
||||
|
||||
private TabPagerAdapter tabPagerAdapter;
|
||||
private ViewPager viewPager;
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.import_export_activity);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
initializeResources();
|
||||
initializeViewPager();
|
||||
initializeTabs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: finish(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.masterSecret = getIntent().getParcelableExtra("master_secret");
|
||||
this.viewPager = (ViewPager) findViewById(R.id.import_export_pager);
|
||||
this.tabPagerAdapter = new TabPagerAdapter(getSupportFragmentManager());
|
||||
|
||||
viewPager.setAdapter(tabPagerAdapter);
|
||||
}
|
||||
|
||||
private void initializeViewPager() {
|
||||
viewPager.setAdapter(tabPagerAdapter);
|
||||
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
getSupportActionBar().setSelectedNavigationItem(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeTabs() {
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
|
||||
ActionBar.TabListener tabListener = new ActionBar.TabListener() {
|
||||
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
|
||||
viewPager.setCurrentItem(tab.getPosition());
|
||||
}
|
||||
|
||||
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {}
|
||||
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {}
|
||||
};
|
||||
|
||||
actionBar.addTab(actionBar.newTab().setText("Import").setTabListener(tabListener));
|
||||
actionBar.addTab(actionBar.newTab().setText("Export").setTabListener(tabListener));
|
||||
}
|
||||
|
||||
private class TabPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private final ImportFragment importFragment;
|
||||
private final ExportFragment exportFragment;
|
||||
|
||||
public TabPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
|
||||
this.importFragment = new ImportFragment();
|
||||
this.exportFragment = new ExportFragment();
|
||||
this.importFragment.setMasterSecret(masterSecret);
|
||||
this.exportFragment.setMasterSecret(masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int i) {
|
||||
if (i == 0) return importFragment;
|
||||
else return exportFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int i) {
|
||||
if (i == 0) return "Import";
|
||||
else return "Export";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
248
src/org/thoughtcrime/securesms/ImportFragment.java
Normal file
@ -0,0 +1,248 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.EncryptedBackupExporter;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.database.PlaintextBackupImporter;
|
||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class ImportFragment extends SherlockFragment {
|
||||
|
||||
private static final int SUCCESS = 0;
|
||||
private static final int NO_SD_CARD = 1;
|
||||
private static final int ERROR_IO = 2;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
public void setMasterSecret(MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
View layout = inflater.inflate(R.layout.import_fragment, container, false);
|
||||
View importSmsView = layout.findViewById(R.id.import_sms );
|
||||
View importEncryptedView = layout.findViewById(R.id.import_encrypted_backup);
|
||||
View importPlaintextView = layout.findViewById(R.id.import_plaintext_backup);
|
||||
|
||||
importSmsView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleImportSms();
|
||||
}
|
||||
});
|
||||
|
||||
importEncryptedView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleImportEncryptedBackup();
|
||||
}
|
||||
});
|
||||
|
||||
importPlaintextView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleImportPlaintextBackup();
|
||||
}
|
||||
});
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (progressDialog != null && progressDialog.isShowing()) {
|
||||
progressDialog.dismiss();
|
||||
progressDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleImportSms() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setTitle(getActivity().getString(R.string.ImportFragment_import_system_sms_database));
|
||||
builder.setMessage(getActivity().getString(R.string.ImportFragment_this_will_import_messages_from_the_system));
|
||||
builder.setPositiveButton(getActivity().getString(R.string.ImportFragment_import), new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(getActivity(), ApplicationMigrationService.class);
|
||||
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
getActivity().startService(intent);
|
||||
|
||||
Intent nextIntent = new Intent(getActivity(), ConversationListActivity.class);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
|
||||
Intent activityIntent = new Intent(getActivity(), DatabaseMigrationActivity.class);
|
||||
activityIntent.putExtra("master_secret", masterSecret);
|
||||
activityIntent.putExtra("next_intent", nextIntent);
|
||||
getActivity().startActivity(activityIntent);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getActivity().getString(R.string.ImportFragment_cancel), null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleImportEncryptedBackup() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(getActivity().getString(R.string.ImportFragment_restore_encrypted_backup));
|
||||
builder.setMessage(getActivity().getString(R.string.ImportFragment_restoring_an_encrypted_backup_will_completely_replace_your_existing_keys));
|
||||
builder.setPositiveButton(getActivity().getString(R.string.ImportFragment_restore), new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new ImportEncryptedBackupTask().execute();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getActivity().getString(R.string.ImportFragment_cancel), null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleImportPlaintextBackup() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(getActivity().getString(R.string.ImportFragment_import_plaintext_backup));
|
||||
builder.setMessage(getActivity().getString(R.string.ImportFragment_this_will_import_messages_from_a_plaintext_backup));
|
||||
builder.setPositiveButton(getActivity().getString(R.string.ImportFragment_import), new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new ImportPlaintextBackupTask().execute();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getActivity().getString(R.string.ImportFragment_cancel), null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private class ImportPlaintextBackupTask extends AsyncTask<Void, Void, Integer> {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.ImportFragment_importing),
|
||||
getActivity().getString(R.string.ImportFragment_import_plaintext_backup_elipse),
|
||||
true, false);
|
||||
}
|
||||
|
||||
protected void onPostExecute(Integer result) {
|
||||
Context context = getActivity();
|
||||
|
||||
if (progressDialog != null)
|
||||
progressDialog.dismiss();
|
||||
|
||||
if (context == null)
|
||||
return;
|
||||
|
||||
switch (result) {
|
||||
case NO_SD_CARD:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ImportFragment_no_plaintext_backup_found),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ERROR_IO:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ImportFragment_error_importing_backup),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case SUCCESS:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ImportFragment_import_complete),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
PlaintextBackupImporter.importPlaintextFromSd(getActivity(), masterSecret);
|
||||
return SUCCESS;
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("ImportFragment", e);
|
||||
return NO_SD_CARD;
|
||||
} catch (IOException e) {
|
||||
Log.w("ImportFragment", e);
|
||||
return ERROR_IO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ImportEncryptedBackupTask extends AsyncTask<Void, Void, Integer> {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.ImportFragment_restoring),
|
||||
getActivity().getString(R.string.ImportFragment_restoring_encrypted_backup),
|
||||
true, false);
|
||||
}
|
||||
|
||||
protected void onPostExecute(Integer result) {
|
||||
Context context = getActivity();
|
||||
|
||||
if (progressDialog != null)
|
||||
progressDialog.dismiss();
|
||||
|
||||
if (context == null)
|
||||
return;
|
||||
|
||||
switch (result) {
|
||||
case NO_SD_CARD:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ImportFragment_no_encrypted_backup_found),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ERROR_IO:
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ImportFragment_error_importing_backup),
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case SUCCESS:
|
||||
DatabaseFactory.getInstance(context).reset(context);
|
||||
Intent intent = new Intent(context, KeyCachingService.class);
|
||||
intent.setAction(KeyCachingService.CLEAR_KEY_ACTION);
|
||||
context.startService(intent);
|
||||
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.ImportFragment_restore_complete),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
EncryptedBackupExporter.importFromSd(getActivity());
|
||||
return SUCCESS;
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("ImportFragment", e);
|
||||
return NO_SD_CARD;
|
||||
} catch (IOException e) {
|
||||
Log.w("ImportFragment", e);
|
||||
return ERROR_IO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -44,7 +44,7 @@ public class CanonicalAddressDatabase {
|
||||
private static final Object lock = new Object();
|
||||
|
||||
private static CanonicalAddressDatabase instance;
|
||||
private final DatabaseHelper databaseHelper;
|
||||
private DatabaseHelper databaseHelper;
|
||||
|
||||
private final Map<String,Long> addressCache = Collections.synchronizedMap(new HashMap<String,Long>());
|
||||
private final Map<String,String> idCache = Collections.synchronizedMap(new HashMap<String,String>());
|
||||
@ -63,6 +63,13 @@ public class CanonicalAddressDatabase {
|
||||
fillCache();
|
||||
}
|
||||
|
||||
public void reset(Context context) {
|
||||
DatabaseHelper old = this.databaseHelper;
|
||||
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
old.close();
|
||||
fillCache();
|
||||
}
|
||||
|
||||
private void fillCache() {
|
||||
Cursor cursor = null;
|
||||
|
||||
|
@ -29,7 +29,7 @@ public abstract class Database {
|
||||
private static final String CONVERSATION_URI = "content://textsecure/thread/";
|
||||
private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
|
||||
|
||||
protected final SQLiteOpenHelper databaseHelper;
|
||||
protected SQLiteOpenHelper databaseHelper;
|
||||
protected final Context context;
|
||||
|
||||
public Database(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
@ -58,4 +58,8 @@ public abstract class Database {
|
||||
cursor.setNotificationUri(context.getContentResolver(), Uri.parse(CONVERSATION_LIST_URI));
|
||||
}
|
||||
|
||||
public void reset(SQLiteOpenHelper databaseHelper) {
|
||||
this.databaseHelper = databaseHelper;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public class DatabaseFactory {
|
||||
private static DatabaseFactory instance;
|
||||
private static EncryptingPartDatabase encryptingPartInstance;
|
||||
|
||||
private final DatabaseHelper databaseHelper;
|
||||
private DatabaseHelper databaseHelper;
|
||||
|
||||
private final SmsDatabase sms;
|
||||
private final EncryptingSmsDatabase encryptingSms;
|
||||
@ -146,10 +146,22 @@ public class DatabaseFactory {
|
||||
this.draftDatabase = new DraftDatabase(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
databaseHelper.close();
|
||||
address.close();
|
||||
instance = null;
|
||||
public void reset(Context context) {
|
||||
DatabaseHelper old = this.databaseHelper;
|
||||
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
|
||||
this.sms.reset(databaseHelper);
|
||||
this.encryptingSms.reset(databaseHelper);
|
||||
this.mms.reset(databaseHelper);
|
||||
this.part.reset(databaseHelper);
|
||||
this.thread.reset(databaseHelper);
|
||||
this.mmsAddress.reset(databaseHelper);
|
||||
this.mmsSmsDatabase.reset(databaseHelper);
|
||||
this.identityDatabase.reset(databaseHelper);
|
||||
this.draftDatabase.reset(databaseHelper);
|
||||
old.close();
|
||||
|
||||
this.address.reset(context);
|
||||
}
|
||||
|
||||
public void onApplicationLevelUpgrade(Context context, MasterSecret masterSecret, int fromVersion,
|
||||
|
@ -26,7 +26,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
public class ApplicationExporter {
|
||||
public class EncryptedBackupExporter {
|
||||
|
||||
public static void exportToSd(Context context) throws NoExternalStorageException, IOException {
|
||||
verifyExternalStorageForExport();
|
||||
@ -71,7 +71,7 @@ public class ApplicationExporter {
|
||||
destination.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ApplicationExporter", ioe);
|
||||
Log.w("EncryptedBackupExporter", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ public class ApplicationExporter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w("ApplicationExporter", "Could not find directory: " + directory.getAbsolutePath());
|
||||
Log.w("EncryptedBackupExporter", "Could not find directory: " + directory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,11 @@ public class EncryptingSmsDatabase extends SmsDatabase {
|
||||
Types.ENCRYPTION_SYMMETRIC_BIT);
|
||||
}
|
||||
|
||||
public Reader getMessages(MasterSecret masterSecret, int skip, int limit) {
|
||||
Cursor cursor = super.getMessages(skip, limit);
|
||||
return new DecryptingReader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public Reader getOutgoingMessages(MasterSecret masterSecret) {
|
||||
Cursor cursor = super.getOutgoingMessages();
|
||||
return new DecryptingReader(masterSecret, cursor);
|
||||
|
@ -61,6 +61,10 @@ public interface MmsSmsColumns {
|
||||
(type & BASE_TYPE_MASK) == BASE_SENDING_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isInboxType(long type) {
|
||||
return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isSecureType(long type) {
|
||||
return (type & SECURE_MESSAGE_BIT) != 0;
|
||||
}
|
||||
@ -112,6 +116,14 @@ public interface MmsSmsColumns {
|
||||
return BASE_INBOX_TYPE;
|
||||
}
|
||||
|
||||
public static int translateToSystemBaseType(long type) {
|
||||
if (isInboxType(type)) return 1;
|
||||
else if (isOutgoingMessageType(type)) return 2;
|
||||
else if (isFailedMessageType(type)) return 5;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
|
@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PlaintextBackupExporter {
|
||||
|
||||
public static void exportPlaintextToSd(Context context, MasterSecret masterSecret)
|
||||
throws NoExternalStorageException, IOException
|
||||
{
|
||||
verifyExternalStorageForPlaintextExport();
|
||||
exportPlaintext(context, masterSecret);
|
||||
}
|
||||
|
||||
private static void verifyExternalStorageForPlaintextExport() throws NoExternalStorageException {
|
||||
if (!Environment.getExternalStorageDirectory().canWrite())
|
||||
throw new NoExternalStorageException();
|
||||
}
|
||||
|
||||
private static String getPlaintextExportDirectoryPath() {
|
||||
File sdDirectory = Environment.getExternalStorageDirectory();
|
||||
return sdDirectory.getAbsolutePath() + File.separator + "TextSecurePlaintextBackup.xml";
|
||||
}
|
||||
|
||||
private static void exportPlaintext(Context context, MasterSecret masterSecret)
|
||||
throws IOException
|
||||
{
|
||||
int count = DatabaseFactory.getSmsDatabase(context).getMessageCount();
|
||||
XmlBackup.Writer writer = new XmlBackup.Writer(getPlaintextExportDirectoryPath(), count);
|
||||
|
||||
|
||||
SmsMessageRecord record;
|
||||
EncryptingSmsDatabase.Reader reader = null;
|
||||
int skip = 0;
|
||||
int ROW_LIMIT = 500;
|
||||
|
||||
do {
|
||||
if (reader != null)
|
||||
reader.close();
|
||||
|
||||
reader = DatabaseFactory.getEncryptingSmsDatabase(context).getMessages(masterSecret, skip, ROW_LIMIT);
|
||||
|
||||
while ((record = reader.getNext()) != null) {
|
||||
XmlBackup.XmlBackupItem item =
|
||||
new XmlBackup.XmlBackupItem(0, record.getIndividualRecipient().getNumber(),
|
||||
record.getDateReceived(),
|
||||
MmsSmsColumns.Types.translateToSystemBaseType(record.getType()),
|
||||
null, record.getDisplayBody().toString(), null,
|
||||
1, record.getDeliveryStatus());
|
||||
|
||||
writer.writeItem(item);
|
||||
}
|
||||
|
||||
skip += ROW_LIMIT;
|
||||
} while (reader.getCount() > 0);
|
||||
|
||||
writer.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class PlaintextBackupImporter {
|
||||
|
||||
public static void importPlaintextFromSd(Context context, MasterSecret masterSecret)
|
||||
throws NoExternalStorageException, IOException
|
||||
{
|
||||
Log.w("PlaintextBackupImporter", "Importing plaintext...");
|
||||
verifyExternalStorageForPlaintextImport();
|
||||
importPlaintext(context, masterSecret);
|
||||
}
|
||||
|
||||
private static void verifyExternalStorageForPlaintextImport() throws NoExternalStorageException {
|
||||
if (!Environment.getExternalStorageDirectory().canRead() ||
|
||||
!(new File(getPlaintextExportDirectoryPath()).exists()))
|
||||
throw new NoExternalStorageException();
|
||||
}
|
||||
|
||||
private static String getPlaintextExportDirectoryPath() {
|
||||
File sdDirectory = Environment.getExternalStorageDirectory();
|
||||
return sdDirectory.getAbsolutePath() + File.separator + "TextSecurePlaintextBackup.xml";
|
||||
}
|
||||
|
||||
private static void importPlaintext(Context context, MasterSecret masterSecret)
|
||||
throws IOException
|
||||
{
|
||||
Log.w("PlaintextBackupImporter", "importPlaintext()");
|
||||
SmsDatabase db = DatabaseFactory.getSmsDatabase(context);
|
||||
SQLiteDatabase transaction = db.beginTransaction();
|
||||
|
||||
try {
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
XmlBackup backup = new XmlBackup(getPlaintextExportDirectoryPath());
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
Set<Long> modifiedThreads = new HashSet<Long>();
|
||||
XmlBackup.XmlBackupItem item;
|
||||
|
||||
while ((item = backup.getNext()) != null) {
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, item.getAddress(), false);
|
||||
long threadId = threads.getThreadIdFor(recipients);
|
||||
SQLiteStatement statement = db.createInsertStatement(transaction);
|
||||
|
||||
if (item.getAddress() == null || item.getAddress().equals("null"))
|
||||
continue;
|
||||
|
||||
addStringToStatement(statement, 1, item.getAddress());
|
||||
addNullToStatement(statement, 2);
|
||||
addLongToStatement(statement, 3, item.getDate());
|
||||
addLongToStatement(statement, 4, item.getDate());
|
||||
addLongToStatement(statement, 5, item.getProtocol());
|
||||
addLongToStatement(statement, 6, item.getRead());
|
||||
addLongToStatement(statement, 7, item.getStatus());
|
||||
addTranslatedTypeToStatement(statement, 8, item.getType());
|
||||
addNullToStatement(statement, 9);
|
||||
addStringToStatement(statement, 10, item.getSubject());
|
||||
addEncryptedStingToStatement(masterCipher, statement, 11, item.getBody());
|
||||
addStringToStatement(statement, 12, item.getServiceCenter());
|
||||
addLongToStatement(statement, 13, threadId);
|
||||
modifiedThreads.add(threadId);
|
||||
statement.execute();
|
||||
} catch (RecipientFormattingException rfe) {
|
||||
Log.w("PlaintextBackupImporter", rfe);
|
||||
}
|
||||
}
|
||||
|
||||
for (long threadId : modifiedThreads) {
|
||||
threads.update(threadId);
|
||||
}
|
||||
|
||||
Log.w("PlaintextBackupImporter", "Exited loop");
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.w("PlaintextBackupImporter", e);
|
||||
throw new IOException("XML Parsing error!");
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addEncryptedStingToStatement(MasterCipher masterCipher, SQLiteStatement statement, int index, String value) {
|
||||
if (value == null || value.equals("null")) {
|
||||
statement.bindNull(index);
|
||||
} else {
|
||||
statement.bindString(index, masterCipher.encryptBody(value));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addTranslatedTypeToStatement(SQLiteStatement statement, int index, int type) {
|
||||
statement.bindLong(index, SmsDatabase.Types.translateFromSystemBaseType(type) | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
|
||||
}
|
||||
|
||||
private static void addStringToStatement(SQLiteStatement statement, int index, String value) {
|
||||
if (value == null || value.equals("null")) statement.bindNull(index);
|
||||
else statement.bindString(index, value);
|
||||
}
|
||||
|
||||
private static void addNullToStatement(SQLiteStatement statement, int index) {
|
||||
statement.bindNull(index);
|
||||
}
|
||||
|
||||
private static void addLongToStatement(SQLiteStatement statement, int index, long value) {
|
||||
statement.bindLong(index, value);
|
||||
}
|
||||
|
||||
}
|
@ -121,6 +121,21 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
}
|
||||
}
|
||||
|
||||
public int getMessageCount() {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, null, null, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) return cursor.getInt(0);
|
||||
else return 0;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public int getMessageCountForThread(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
@ -290,6 +305,11 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
return messageIds;
|
||||
}
|
||||
|
||||
Cursor getMessages(int skip, int limit) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, null, null, null, null, ID, skip + "," + limit);
|
||||
}
|
||||
|
||||
Cursor getOutgoingMessages() {
|
||||
String outgoingSelection = TYPE + " & " + Types.BASE_TYPE_MASK + " = " + Types.BASE_OUTBOX_TYPE;
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
@ -415,6 +435,11 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
return getCurrent();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
if (cursor == null) return 0;
|
||||
else return cursor.getCount();
|
||||
}
|
||||
|
||||
public SmsMessageRecord getCurrent() {
|
||||
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||||
|
@ -191,8 +191,8 @@ public class SmsMigrator {
|
||||
MasterSecret masterSecret,
|
||||
SmsMigrationProgressListener listener)
|
||||
{
|
||||
if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
|
||||
return;
|
||||
// if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
|
||||
// return;
|
||||
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
Cursor cursor = null;
|
||||
|
184
src/org/thoughtcrime/securesms/database/XmlBackup.java
Normal file
@ -0,0 +1,184 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class XmlBackup {
|
||||
|
||||
private static final String PROTOCOL = "protocol";
|
||||
private static final String ADDRESS = "address";
|
||||
private static final String DATE = "date";
|
||||
private static final String TYPE = "type";
|
||||
private static final String SUBJECT = "subject";
|
||||
private static final String BODY = "body";
|
||||
private static final String SERVICE_CENTER = "service_center";
|
||||
private static final String READ = "read";
|
||||
private static final String STATUS = "status";
|
||||
|
||||
private final XmlPullParser parser;
|
||||
|
||||
public XmlBackup(String path) throws XmlPullParserException, FileNotFoundException {
|
||||
this.parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(new FileInputStream(path), null);
|
||||
}
|
||||
|
||||
public XmlBackupItem getNext() throws IOException, XmlPullParserException {
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = parser.getName();
|
||||
|
||||
if (!name.equals("sms")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int attributeCount = parser.getAttributeCount();
|
||||
|
||||
if (attributeCount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
XmlBackupItem item = new XmlBackupItem();
|
||||
|
||||
for (int i=0;i<attributeCount;i++) {
|
||||
String attributeName = parser.getAttributeName(i);
|
||||
|
||||
if (attributeName.equals(PROTOCOL )) item.protocol = Integer.parseInt(parser.getAttributeValue(i));
|
||||
else if (attributeName.equals(ADDRESS )) item.address = parser.getAttributeValue(i);
|
||||
else if (attributeName.equals(DATE )) item.date = Long.parseLong(parser.getAttributeValue(i));
|
||||
else if (attributeName.equals(TYPE )) item.type = Integer.parseInt(parser.getAttributeValue(i));
|
||||
else if (attributeName.equals(SUBJECT )) item.subject = parser.getAttributeValue(i);
|
||||
else if (attributeName.equals(BODY )) item.body = parser.getAttributeValue(i);
|
||||
else if (attributeName.equals(SERVICE_CENTER)) item.serviceCenter = parser.getAttributeValue(i);
|
||||
else if (attributeName.equals(READ )) item.read = Integer.parseInt(parser.getAttributeValue(i));
|
||||
else if (attributeName.equals(STATUS )) item.status = Integer.parseInt(parser.getAttributeValue(i));
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class XmlBackupItem {
|
||||
private int protocol;
|
||||
private String address;
|
||||
private long date;
|
||||
private int type;
|
||||
private String subject;
|
||||
private String body;
|
||||
private String serviceCenter;
|
||||
private int read;
|
||||
private int status;
|
||||
|
||||
public XmlBackupItem() {}
|
||||
|
||||
public XmlBackupItem(int protocol, String address, long date, int type, String subject,
|
||||
String body, String serviceCenter, int read, int status)
|
||||
{
|
||||
this.protocol = protocol;
|
||||
this.address = address;
|
||||
this.date = date;
|
||||
this.type = type;
|
||||
this.subject = subject;
|
||||
this.body = body;
|
||||
this.serviceCenter = serviceCenter;
|
||||
this.read = read;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public long getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getServiceCenter() {
|
||||
return serviceCenter;
|
||||
}
|
||||
|
||||
public int getRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Writer {
|
||||
|
||||
private BufferedWriter writer;
|
||||
private XmlSerializer serializer;
|
||||
|
||||
public Writer(String path, int count) throws IOException {
|
||||
this.writer = new BufferedWriter(new FileWriter(path));
|
||||
this.serializer = Xml.newSerializer();
|
||||
|
||||
this.serializer.setOutput(writer);
|
||||
this.serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
this.serializer.startDocument("UTF-8", true);
|
||||
this.serializer.startTag("", "smses");
|
||||
this.serializer.attribute("", "count", count+"");
|
||||
}
|
||||
|
||||
public void writeItem(XmlBackupItem item) throws IOException {
|
||||
this.serializer.startTag("", "sms");
|
||||
this.serializer.attribute("", "protocol", item.getProtocol() + "");
|
||||
this.serializer.attribute("", "address", item.getAddress());
|
||||
this.serializer.attribute("", "date", item.getDate()+"");
|
||||
this.serializer.attribute("", "type", item.getType()+"");
|
||||
this.serializer.attribute("", "subject", item.getSubject()+"");
|
||||
try {
|
||||
this.serializer.attribute("", "body", item.getBody()+"");
|
||||
} catch (IllegalArgumentException ise) {
|
||||
// XXX - Fucking Android. Their serializer includes a bug that doesn't
|
||||
// handle some unicode characters correctly.
|
||||
Log.w("XmlBackup", ise);
|
||||
}
|
||||
this.serializer.attribute("", "toa", null+"");
|
||||
this.serializer.attribute("", "sc_toa", null+"");
|
||||
this.serializer.attribute("", "service_center", item.getServiceCenter()+"");
|
||||
this.serializer.attribute("", "read", item.getRead()+"");
|
||||
this.serializer.attribute("" , "status", item.getStatus()+"");
|
||||
this.serializer.attribute("", "locked", "0");
|
||||
this.serializer.endTag("", "sms");
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.serializer.endTag("", "smses");
|
||||
this.serializer.endDocument();
|
||||
}
|
||||
}
|
||||
}
|