Move to a Fragment world.

1) Start breaking the UI out into Fragments.
2) Switch to Cursor loaders from managed cursors.
3) Switch to inflated menu resources.
4) Break out some basic functionality into helper classes.
This commit is contained in:
Moxie Marlinspike 2012-07-18 15:35:13 -07:00
parent b93bb69c28
commit 3d9475676f
20 changed files with 1709 additions and 1173 deletions

View File

@ -10,7 +10,7 @@
android:label="@string/app_name"
android:theme="@style/Theme.Sherlock.Light.DarkActionBar">
<activity android:name=".SecureSMS"
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:uiOptions="splitActionBarWhenNarrow"

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<fragment
android:id="@+id/fragment_content"
android:name="org.thoughtcrime.securesms.ConversationListFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="false"
android:scrollbarStyle="insideOverlay"
android:fadingEdgeLength="16dip"/>
</LinearLayout>

View File

@ -5,43 +5,12 @@
android:layout_height="fill_parent"
android:orientation="vertical">
<EditText android:id="@+id/search_text"
android:layout_height="wrap_content"
android:capitalize="sentences"
android:autoText="true"
android:singleLine="true"
android:hint="Search"
android:paddingRight="50dip"
android:layout_margin="10dip"
android:layout_width="fill_parent"
android:layout_alignParentBottom="true"
android:visibility="gone"/>
<ImageView android:id="@+id/search_close"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="16dip"
android:layout_marginBottom="20dip"
android:src="@android:drawable/btn_dialog"
android:visibility="gone"
/>
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="false"
android:scrollbarStyle="insideOverlay"
android:fadingEdgeLength="16dip"
android:layout_alignParentTop="true"
android:layout_above="@id/search_text" />
<ProgressBar android:id="@+id/search_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_centerInParent="true"
android:visibility="gone" />
android:layout_alignParentTop="true"/>
</RelativeLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Batch Mode"
android:id="@+id/menu_batch_mode"
android:icon="@android:drawable/ic_menu_share" />
<item android:title="Search"
android:id="@+id/menu_search"
android:icon="@drawable/ic_menu_search_holo_dark"
android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom|collapseActionView" />
</menu>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Normal Mode"
android:id="@+id/menu_normal_mode"
android:icon="@android:drawable/ic_menu_set_as" />
<item android:title="Delete Selected"
android:id="@+id/menu_delete_selected"
android:icon="@drawable/ic_menu_trash_holo_dark"
android:showAsAction="ifRoom" />
<item android:title="Select All"
android:id="@+id/menu_select_all"
android:icon="@android:drawable/ic_menu_add" />
<item android:title="Unselect All"
android:id="@+id/menu_unselect_all"
android:icon="@android:drawable/ic_menu_revert" />
</menu>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="View Thread"
android:id="@+id/menu_context_view" />
<item android:title="Delete Thread"
android:id="@+id/menu_context_delete" />
</menu>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Batch Mode"
android:id="@+id/menu_batch_mode"
android:icon="@android:drawable/ic_menu_share" />
</menu>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Unlock"
android:id="@+id/menu_unlock"
android:showAsAction="ifRoom" />
</menu>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="New Message"
android:id="@+id/menu_new_message"
android:icon="@drawable/ic_menu_msg_compose_holo_dark"
android:showAsAction="ifRoom" />
<item android:title="Settings"
android:id="@+id/menu_settings"
android:icon="@android:drawable/ic_menu_preferences" />
<item android:title="Import/Export"
android:icon="@android:drawable/ic_menu_save">
<menu>
<item android:title="Import"
android:id="@+id/menu_import"
android:icon="@android:drawable/ic_menu_revert" />
<item android:title="Export"
android:id="@+id/menu_export"
android:icon="@android:drawable/ic_menu_save" />
</menu>
</item>
<item android:title="Clear Passphrase"
android:id="@+id/menu_clear_passphrase"
android:icon="@android:drawable/ic_menu_close_clear_cancel" />
</menu>

View File

@ -0,0 +1,143 @@
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("Import Database and Settings?");
alertBuilder.setMessage("Import TextSecure database, keys, and settings from the SD Card?" +
"\n\nWARNING: This will clobber any existing messages, keys, and " +
"settings!");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Import", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
task = TASK_IMPORT;
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Importing Database and Keys");
progressDialog.setMessage("Importnig 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("Cancel", null);
alertBuilder.create().show();
}
public void exportDatabase() {
Log.w("ApplicationExportManager", "Context: " + context);
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setTitle("Export Database?");
alertBuilder.setMessage("Export TextSecure database, keys, and settings to the SD Card?");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Export", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
task = TASK_EXPORT;
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Exporting Database and Keys");
progressDialog.setMessage("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("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, "No SD card found!", Toast.LENGTH_LONG).show();
break;
case ERROR_IO:
Toast.makeText(context, "Error exporting to SD!", Toast.LENGTH_LONG).show();
break;
case COMPLETE:
switch (task) {
case TASK_IMPORT:
Toast.makeText(context, "Import Successful!", Toast.LENGTH_LONG).show();
break;
case TASK_EXPORT:
Toast.makeText(context, "Export Successful!", Toast.LENGTH_LONG).show();
break;
}
break;
}
progressDialog.dismiss();
}
public interface ApplicationExportListener {
public void onPrepareForImport();
}
}

View File

@ -0,0 +1,91 @@
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 org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.SmsMigrator;
public class ApplicationMigrationManager extends Handler implements Runnable {
private ProgressDialog progressDialog;
private final Context context;
private final MasterSecret masterSecret;
private ApplicationMigrationListener listener;
public ApplicationMigrationManager(Context context,
MasterSecret masterSecret)
{
this.masterSecret = masterSecret;
this.context = context;
}
public void setMigrationListener(ApplicationMigrationListener listener) {
this.listener = listener;
}
public void run() {
SmsMigrator.migrateDatabase(context, masterSecret, this);
}
public void migrate() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setTitle("Copy System Text Message Database?");
alertBuilder.setMessage("Current versions of TextSecure use an encrypted database that is " +
"separate from the default system database. Would you like to " +
"copy your existing text messages into TextSecure's encrypted " +
"database? Your default system database will be unaffected.");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Copy", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Migrating Database");
progressDialog.setMessage("Migrating your SMS database...");
progressDialog.setMax(10000);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
new Thread(ApplicationMigrationManager.this).start();
}
});
alertBuilder.setNegativeButton("Don't 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 SmsMigrator.PROGRESS_UPDATE:
progressDialog.incrementProgressBy(message.arg1);
progressDialog.setSecondaryProgress(0);
break;
case SmsMigrator.SECONDARY_PROGRESS_UPDATE:
progressDialog.incrementSecondaryProgressBy(message.arg1);
break;
case SmsMigrator.COMPLETE:
progressDialog.dismiss();
listener.applicationMigrationComplete();
break;
}
}
public static interface ApplicationMigrationListener {
public void applicationMigrationComplete();
}
}

View File

@ -0,0 +1,988 @@
package org.thoughtcrime.securesms;
import android.app.NotificationManager;
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.content.SharedPreferences;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.util.Log;
import com.actionbarsherlock.app.SherlockFragmentActivity;
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.contacts.ContactAccessor;
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;
import org.thoughtcrime.securesms.util.MemoryCleaner;
public class ConversationListActivity extends SherlockFragmentActivity
implements ConversationListFragment.ConversationSelectedListener
{
private ConversationListFragment fragment;
private MasterSecret masterSecret;
private BroadcastReceiver killActivityReceiver;
private BroadcastReceiver newKeyReceiver;
private boolean havePromptedForPassphrase = false;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.conversation_list_activity);
getSupportActionBar().setTitle("TextSecure");
initializeKillReceiver();
initializeSenderReceiverService();
initializeResources();
initializeContactUpdatesReceiver();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.w("SecureSMS", "Got onNewIntent...");
createConversationIfNecessary(intent);
}
@Override
public void onPause() {
super.onPause();
if (newKeyReceiver != null) {
Log.w("ConversationListActivity", "Unregistering receiver...");
unregisterReceiver(newKeyReceiver);
newKeyReceiver = null;
}
}
@Override
public void onResume() {
super.onResume();
Log.w("ConversationListActivity", "onResume called...");
clearNotifications();
initializeKeyCachingServiceRegistration();
}
@Override
public void onStart() {
super.onStart();
registerPassphraseActivityStarted();
}
@Override
public void onStop() {
super.onStop();
havePromptedForPassphrase = false;
registerPassphraseActivityStopped();
}
@Override
public void onDestroy() {
Log.w("SecureSMS", "onDestroy...");
unregisterReceiver(killActivityReceiver);
MemoryCleaner.clean(masterSecret);
super.onDestroy();
}
@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);
super.onPrepareOptionsMenu(menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_new_message: createConversation(-1, null); return true;
case R.id.menu_unlock: promptForPassphrase(); 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;
}
return false;
}
@Override
public void onCreateConversation(long threadId, Recipients recipients) {
createConversation(threadId, recipients);
}
private void createConversation(long threadId, Recipients recipients) {
if (this.masterSecret == null) {
promptForPassphrase();
return;
}
Intent intent = new Intent(this, ComposeMessageActivity.class);
intent.putExtra("recipients", recipients);
intent.putExtra("thread_id", threadId);
intent.putExtra("master_secret", masterSecret);
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);
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() {
initializeWithMasterSecret(null);
Intent clearKeyIntent = new Intent(KeyCachingService.CLEAR_KEY_ACTION, null,
ConversationListActivity.this, KeyCachingService.class);
startService(clearKeyIntent);
DatabaseFactory.getInstance(ConversationListActivity.this).close();
}
};
exportManager.setListener(listener);
exportManager.importDatabase();
}
private void handleClearPassphrase() {
Intent keyService = new Intent(this, KeyCachingService.class);
keyService.setAction(KeyCachingService.CLEAR_KEY_ACTION);
startService(keyService);
this.masterSecret = null;
fragment.setMasterSecret(null);
promptForPassphrase();
}
private void initializeWithMasterSecret(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();
createConversationIfNecessary(this.getIntent());
}
private void initializeKillReceiver() {
this.killActivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
registerReceiver(this.killActivityReceiver,
new IntentFilter(KeyCachingService.PASSPHRASE_EXPIRED_EVENT),
KeyCachingService.KEY_PERMISSION, null);
}
private void initializeContactUpdatesReceiver() {
ContentObserver observer = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
RecipientFactory.clearCache();
}
};
getContentResolver().registerContentObserver(ContactAccessor.getInstance().getContactsUri(),
true, observer);
}
private void initializeSenderReceiverService() {
Intent smsSenderIntent = new Intent(SendReceiveService.SEND_SMS_ACTION, null, this,
SendReceiveService.class);
Intent mmsSenderIntent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, this,
SendReceiveService.class);
startService(smsSenderIntent);
startService(mmsSenderIntent);
}
private void initializeDatabaseMigration() {
ApplicationMigrationManager 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 initializeKeyCachingServiceRegistration() {
Log.w("ConversationListActivity", "Checking caching service...");
this.newKeyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.w("ConversationListActivity", "Got a key broadcast...");
initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
}
};
IntentFilter filter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT);
registerReceiver(newKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
Intent bindIntent = new Intent(this, KeyCachingService.class);
bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void initializeResources() {
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 clearNotifications() {
NotificationManager manager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(KeyCachingService.NOTIFICATION_ID);
}
private void createConversationIfNecessary(Intent intent) {
long thread = intent.getLongExtra("thread_id", -1L);
Recipients recipients = null;
if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) {
try {
recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart());
thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
} catch (RecipientFormattingException rfe) {
recipients = null;
}
} else {
recipients = intent.getParcelableExtra("recipients");
}
if (recipients != null) {
createConversation(thread, recipients);
intent.putExtra("thread_id", -1L);
intent.putExtra("recipients", (Parcelable)null);
intent.setAction(null);
}
}
private void registerPassphraseActivityStarted() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
startService(intent);
}
private void registerPassphraseActivityStopped() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
startService(intent);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
MasterSecret masterSecret = keyCachingService.getMasterSecret();
initializeWithMasterSecret(masterSecret);
if (masterSecret == null && !havePromptedForPassphrase)
promptForPassphrase();
Intent cachingIntent = new Intent(ConversationListActivity.this, KeyCachingService.class);
startService(cachingIntent);
try {
ConversationListActivity.this.unbindService(this);
} catch (IllegalArgumentException iae) {
Log.w("SecureSMS", iae);
}
}
public void onServiceDisconnected(ComponentName name) {}
};
private class IdentityKeyInitializer implements Runnable {
public void run() {
IdentityKeyUtil.generateIdentityKeys(ConversationListActivity.this, masterSecret);
}
}
private class AsymmetricMasteSecretInitializer implements Runnable {
public void run() {
MasterSecretUtil.generateAsymmetricMasterSecret(ConversationListActivity.this, masterSecret);
}
}
// @Override
// public boolean onPrepareOptionsMenu(Menu menu) {
// super.onPrepareOptionsMenu(menu);
//
// menu.clear();
//
// if (!batchMode) prepareNormalMenu(menu);
// else prepareBatchModeMenu(menu);
//
// return true;
// }
//
// private void prepareNormalMenu(Menu menu) {
// menu.add(0, MENU_BATCH_MODE, Menu.NONE, "Batch Mode")
// .setIcon(android.R.drawable.ic_menu_share);
//
// if (masterSecret != null) {
// menu.add(0, MENU_SEND_KEY, Menu.NONE, "Secure Session")
// .setIcon(R.drawable.ic_lock_message_sms);
// } else {
// menu.add(0, MENU_PASSPHRASE_KEY, Menu.NONE, "Enter passphrase")
// .setIcon(R.drawable.ic_lock_message_sms);
// }
//
// menu.add(0, MENU_NEW_MESSAGE, Menu.NONE, "New Message")
// .setIcon(R.drawable.ic_menu_msg_compose_holo_dark)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
//
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// android.widget.SearchView searchView =
// new android.widget.SearchView(this.getApplicationContext());
//
// menu.add(0, MENU_SEARCH, Menu.NONE, "Search")
// .setActionView(searchView)
// .setIcon(R.drawable.ic_menu_search_holo_dark)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
// MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
//
//// initializeSearch(searchView);
// } else {
// ImageView image = new ImageView(this);
// image.setImageResource(R.drawable.ic_menu_search_holo_dark);
// image.setVisibility(View.INVISIBLE);
// FrameLayout searchView = new FrameLayout(this);
// searchView.addView(image);
//
// menu.add(0, -1, Menu.NONE, "")
// .setActionView(searchView)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
// }
//
// menu.add(0, MENU_PREFERENCES_KEY, Menu.NONE, "Settings")
// .setIcon(android.R.drawable.ic_menu_preferences);
//
// SubMenu importExportMenu = menu.addSubMenu("Import/Export")
// .setIcon(android.R.drawable.ic_menu_save);
// importExportMenu.add(0, MENU_EXPORT, Menu.NONE, "Export To SD Card")
// .setIcon(android.R.drawable.ic_menu_save);
// importExportMenu.add(0, MENU_IMPORT, Menu.NONE, "Import From SD Card")
// .setIcon(android.R.drawable.ic_menu_revert);
//
// if (masterSecret != null) {
// menu.add(0, MENU_CLEAR_PASSPHRASE, Menu.NONE, "Clear Passphrase")
// .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
// }
// }
//
// private void prepareBatchModeMenu(Menu menu) {
// menu.add(0, MENU_EXIT_BATCH, Menu.NONE, "Normal Mode")
// .setIcon(android.R.drawable.ic_menu_set_as);
//
// menu.add(0, MENU_DELETE_SELECTED_THREADS, Menu.NONE, "Delete Selected")
// .setIcon(R.drawable.ic_menu_trash_holo_dark)
// .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
//
// menu.add(0, MENU_SELECT_ALL_THREADS, Menu.NONE, "Select All")
// .setIcon(android.R.drawable.ic_menu_add);
//
// menu.add(0, MENU_CLEAR_SELECTION, Menu.NONE, "Unselect All")
// .setIcon(android.R.drawable.ic_menu_revert);
// }
// @Override
// public boolean onOptionsItemSelected(MenuItem item) {
// super.onOptionsItemSelected(item);
//
// switch (item.getItemId()) {
// case MENU_NEW_MESSAGE:
// createConversation(-1, null);
// return true;
// case MENU_SEND_KEY:
// Intent intent = new Intent(this, SendKeyActivity.class);
// intent.putExtra("master_secret", masterSecret);
//
// startActivity(intent);
// return true;
// case MENU_PASSPHRASE_KEY:
// promptForPassphrase();
// return true;
//// case MENU_BATCH_MODE:
//// initiateBatchMode();
//// return true;
// case MENU_PREFERENCES_KEY:
// Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
// preferencesIntent.putExtra("master_secret", masterSecret);
// startActivity(preferencesIntent);
// return true;
// case MENU_EXPORT:
// ExportHandler exportHandler = new ExportHandler();
// exportHandler.export();
// return true;
// case MENU_IMPORT:
// ExportHandler importHandler = new ExportHandler();
// importHandler.importFromSd();
// return true;
//// case MENU_DELETE_SELECTED_THREADS: deleteSelectedThreads(); return true;
//// case MENU_SELECT_ALL_THREADS: selectAllThreads(); return true;
//// case MENU_CLEAR_SELECTION: unselectAllThreads(); return true;
//// case MENU_EXIT_BATCH: stopBatchMode(); return true;
//// case MENU_CLEAR_PASSPHRASE:
//// Intent keyService = new Intent(this, KeyCachingService.class);
//// keyService.setAction(KeyCachingService.CLEAR_KEY_ACTION);
//// startService(keyService);
//// addConversationItems();
//// promptForPassphrase();
//// // finish();
//// return true;
// }
//
// return false;
// }
// public void eulaComplete() {
// clearNotifications();
// initializeReceivers();
// checkCachingService();
// }
// @Override
// public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
// if (((AdapterView.AdapterContextMenuInfo)menuInfo).position > 0) {
// Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
// String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
// Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientId);
//
// menu.add(0, VIEW_THREAD_ID, Menu.NONE, "View thread");
//
// if (recipients.isSingleRecipient()) {
// if (recipients.getPrimaryRecipient().getName() != null) {
// menu.add(0, VIEW_CONTACT_ID, Menu.NONE, "View contact");
// } else {
// menu.add(0, ADD_CONTACT_ID, Menu.NONE, "Add to contacts");
// }
// }
//
// menu.add(0, DELETE_THREAD_ID, Menu.NONE, "Delete thread");
// }
// }
// @Override
// public boolean onContextItemSelected(android.view.MenuItem item) {
// Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
// long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
// String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
// Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientId);
//
// switch(item.getItemId()) {
// case VIEW_THREAD_ID:
// createConversation(threadId, recipients);
// return true;
// case VIEW_CONTACT_ID:
// viewContact(recipients.getPrimaryRecipient());
// return true;
// case ADD_CONTACT_ID:
// addContact(recipients.getPrimaryRecipient());
// return true;
// case DELETE_THREAD_ID:
// deleteThread(threadId);
// return true;
// }
// return false;
// }
// private void initiateBatchMode() {
// this.batchMode = true;
// ((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
// }
//
// private void stopBatchMode() {
// this.batchMode = false;
// ((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
// }
//
// private void selectAllThreads() {
// ((ConversationListAdapter)this.getListAdapter()).selectAllThreads();
// }
//
// private void unselectAllThreads() {
// ((ConversationListAdapter)this.getListAdapter()).unselectAllThreads();
// }
// private void deleteSelectedThreads() {
// AlertDialog.Builder alert = new AlertDialog.Builder(this);
// alert.setIcon(android.R.drawable.ic_dialog_alert);
// alert.setTitle("Delete threads?");
// alert.setMessage("Are you sure you wish to delete ALL selected conversation threads?");
// alert.setCancelable(true);
// alert.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// Set<Long> selectedConversations = ((ConversationListAdapter)getListAdapter()).getBatchSelections();
//
// if (!selectedConversations.isEmpty())
// DatabaseFactory.getThreadDatabase(SecureSMS.this).deleteConversations(selectedConversations);
// }
// });
// alert.setNegativeButton("Cancel", null);
// alert.show();
// }
// private void registerForContactsUpdates() {
// Log.w("SecureSMS", "Registering for contacts update...");
// getContentResolver().registerContentObserver(ContactAccessor.getInstance().getContactsUri(), true, new ContactUpdateObserver());
// }
// private void addContact(Recipient recipient) {
// RecipientFactory.getRecipientProvider().addContact(this, recipient);
// }
//
// private void viewContact(Recipient recipient) {
// if (recipient.getPersonId() > 0) {
// RecipientFactory.getRecipientProvider().viewContact(this, recipient);
// }
// }
// private void initializeWithMasterSecret(MasterSecret masterSecret) {
// this.masterSecret = masterSecret;
//
// if (masterSecret != null) {
// if (!IdentityKeyUtil.hasIdentityKey(this)) initializeIdentityKeys();
// if (!MasterSecretUtil.hasAsymmericMasterSecret(this)) initializeAsymmetricMasterSecret();
// if (!isDatabaseMigrated()) initializeDatabaseMigration();
// else DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
// }
//
// this.fragment.setMasterSecret(masterSecret);
// createConversationIfNecessary(this.getIntent());
// }
//
// private void initializeAsymmetricMasterSecret() {
// new Thread(new AsymmetricMasteSecretInitializer()).start();
// }
//
// private void initializeIdentityKeys() {
// new Thread(new IdentityKeyInitializer()).start();
// }
// private void initializeSearch(android.widget.SearchView searchView) {
// searchView.setOnQueryTextListener(new android.widget.SearchView.OnQueryTextListener() {
// @Override
// public boolean onQueryTextSubmit(String query) {
// Cursor cursor = null;
//
// if (query.length() > 0) {
// List<String> numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(query, getContentResolver());
// cursor = DatabaseFactory.getThreadDatabase(SecureSMS.this).getFilteredConversationList(numbers);
// } else {
// cursor = DatabaseFactory.getThreadDatabase(SecureSMS.this).getConversationList();
// }
//
// if (cursor != null)
// startManagingCursor(cursor);
//
// if (getListAdapter() != null)
// ((CursorAdapter)getListAdapter()).changeCursor(cursor);
//
// return true;
// }
//
// @Override
// public boolean onQueryTextChange(String newText) {
// return onQueryTextSubmit(newText);
// }
// });
// }
// private void initializeReceivers() {
// this.newKeyReceiver = new BroadcastReceiver() {
// @Override
// public void onReceive(Context context, Intent intent) {
// Log.w("ConversationListActivity", "Got a key broadcast...");
// initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
// }
// };
//
// IntentFilter filter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT);
// registerReceiver(newKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
// }
// @Override
// protected void onListItemClick(ListView l, View v, int position, long id) {
// if (v instanceof ConversationHeaderView) {
// ConversationHeaderView headerView = (ConversationHeaderView) v;
// createConversation(headerView.getThreadId(), headerView.getRecipients());
// }
// }
// private void createConversationIfNecessary(Intent intent) {
// long thread = intent.getLongExtra("thread_id", -1L);
// Recipients recipients = null;
//
// if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) {
// try {
// recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart());
// thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
// } catch (RecipientFormattingException rfe) {
// recipients = null;
// }
// } else {
// recipients = intent.getParcelableExtra("recipients");
// }
//
// if (recipients != null) {
// createConversation(thread, recipients);
// intent.putExtra("thread_id", -1L);
// intent.putExtra("recipients", (Parcelable)null);
// intent.setAction(null);
// }
// }
// private void createConversation(long threadId, Recipients recipients) {
// if (this.masterSecret == null) {
// promptForPassphrase();
// return;
// }
//
// Intent intent = new Intent(this, ComposeMessageActivity.class);
// intent.putExtra("recipients", recipients);
// intent.putExtra("thread_id", threadId);
// intent.putExtra("master_secret", masterSecret);
// startActivity(intent);
// }
// private void addConversationItems() {
//// Cursor cursor = DatabaseFactory.getThreadDatabase(this).getConversationList();
//// startManagingCursor(cursor);
// this.getLoaderManager();
////
// if (masterSecret == null) setListAdapter(new ConversationListAdapter(this, null));
// else setListAdapter(new DecryptingConversationListAdapter(this, null, masterSecret));
// }
// private void deleteThread(long threadId) {
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("Delete Thread Confirmation");
// builder.setIcon(android.R.drawable.ic_dialog_alert);
// builder.setCancelable(true);
// builder.setMessage("Are you sure that you want to permanently delete this conversation?");
// builder.setPositiveButton(R.string.yes, new DeleteThreadListener(threadId));
// builder.setNegativeButton(R.string.no, null);
// builder.show();
// }
// private class DeleteThreadListener implements DialogInterface.OnClickListener {
// private final long threadId;
//
// public DeleteThreadListener(long threadId) {
// this.threadId = threadId;
// }
//
// public void onClick(DialogInterface dialog, int which) {
// if (threadId > 0) {
// DatabaseFactory.getThreadDatabase(ConversationListActivity.this).deleteConversation(threadId);
// }
// }
// };
// private class NewKeyReceiver extends BroadcastReceiver {
// @Override
// public void onReceive(Context context, Intent intent) {
// Log.w("securesms", "Got a broadcast...");
// initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
// }
// }
// private class KillActivityReceiver extends BroadcastReceiver {
// @Override
// public void onReceive(Context arg0, Intent arg1) {
// finish();
// }
// }
// private class SettingsClickListener implements View.OnClickListener {
// public void onClick(View v) {
// startActivity(new Intent(SecureSMS.this, ApplicationPreferencesActivity.class));
// }
// }
// private class ExportHandler 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;
//
// public void run() {
// try {
// switch (task) {
// case TASK_EXPORT: ApplicationExporter.exportToSd(ConversationListActivity.this); break;
// case TASK_IMPORT: ApplicationExporter.importFromSd(ConversationListActivity.this); 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();
// }
//
// private void continueExport() {
// task = TASK_EXPORT;
// progressDialog = new ProgressDialog(ConversationListActivity.this);
// progressDialog.setTitle("Exporting Database and Keys");
// progressDialog.setMessage("Exporting your SMS database, keys, and settings...");
// progressDialog.setCancelable(false);
// progressDialog.setIndeterminate(true);
// progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
// progressDialog.show();
// new Thread(this).start();
// }
//
// private void continueImport() {
// task = TASK_IMPORT;
// progressDialog = new ProgressDialog(ConversationListActivity.this);
// progressDialog.setTitle("Importing Database and Keys");
// progressDialog.setMessage("Importnig your SMS database, keys, and settings...");
// progressDialog.setCancelable(false);
// progressDialog.setIndeterminate(true);
// progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
// progressDialog.show();
//
// initializeWithMasterSecret(null);
// Intent clearKeyIntent = new Intent(KeyCachingService.CLEAR_KEY_ACTION, null, ConversationListActivity.this, KeyCachingService.class);
// startService(clearKeyIntent);
//
// DatabaseFactory.getInstance(ConversationListActivity.this).close();
// new Thread(this).start();
// }
//
// public void importFromSd() {
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationListActivity.this);
// alertBuilder.setTitle("Import Database and Settings?");
// alertBuilder.setMessage("Import TextSecure database, keys, and settings from the SD Card?\n\nWARNING: This will clobber any existing messages, keys, and settings!");
// alertBuilder.setCancelable(false);
// alertBuilder.setPositiveButton("Import", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// continueImport();
// }
// });
// alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// }
// });
// alertBuilder.create().show();
// }
//
// public void export() {
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationListActivity.this);
// alertBuilder.setTitle("Export Database?");
// alertBuilder.setMessage("Export TextSecure database, keys, and settings to the SD Card?");
// alertBuilder.setCancelable(false);
// alertBuilder.setPositiveButton("Export", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// continueExport();
// }
// });
// alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// }
// });
// alertBuilder.create().show();
// }
//
// @Override
// public void handleMessage(Message message) {
// switch (message.what) {
// case ERROR_NO_SD:
// Toast.makeText(ConversationListActivity.this, "No SD card found!", Toast.LENGTH_LONG).show();
// break;
// case ERROR_IO:
// Toast.makeText(ConversationListActivity.this, "Error exporting to SD!", Toast.LENGTH_LONG).show();
// break;
// case COMPLETE:
// switch (task) {
// case TASK_IMPORT:
// Toast.makeText(ConversationListActivity.this, "Import Successful!", Toast.LENGTH_LONG).show();
//// addConversationItems();
// promptForPassphrase();
// break;
// case TASK_EXPORT:
// Toast.makeText(ConversationListActivity.this, "Export Successful!", Toast.LENGTH_LONG).show();
// break;
// }
// break;
// }
//
// progressDialog.dismiss();
// }
// }
// private class ContactUpdateObserver extends ContentObserver {
// public ContactUpdateObserver() {
// super(null);
// }
//
// @Override
// public void onChange(boolean selfChange) {
// super.onChange(selfChange);
// Log.w("SesureSMS", "Got contact update, clearing cache...");
// RecipientFactory.clearCache();
// }
// }
// private class MigrationHandler extends Handler implements Runnable {
// private ProgressDialog progressDialog;
//
// public void run() {
// SmsMigrator.migrateDatabase(ConversationListActivity.this, masterSecret, this);
// }
//
// private void continueMigration() {
// progressDialog = new ProgressDialog(ConversationListActivity.this);
// progressDialog.setTitle("Migrating Database");
// progressDialog.setMessage("Migrating your SMS database...");
// progressDialog.setMax(10000);
// progressDialog.setCancelable(false);
// progressDialog.setIndeterminate(false);
// progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
// progressDialog.show();
// new Thread(this).start();
// }
//
// private void cancelMigration() {
// ConversationListActivity.this.getSharedPreferences("SecureSMS", MODE_PRIVATE).edit().putBoolean("migrated", true).commit();
// ConversationListActivity.this.migrateDatabaseComplete();
// }
//
// public void migrate() {
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationListActivity.this);
// alertBuilder.setTitle("Copy System Text Message Database?");
// alertBuilder.setMessage("Current versions of TextSecure use an encrypted database that is separate from the default system database. Would you like to copy your existing text messages into TextSecure's encrypted database? Your default system database will be unaffected.");
// alertBuilder.setCancelable(false);
// alertBuilder.setPositiveButton("Copy", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// continueMigration();
// }
// });
// alertBuilder.setNegativeButton("Don't copy", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int which) {
// cancelMigration();
// }
// });
// alertBuilder.create().show();
// }
//
// @Override
// public void handleMessage(Message message) {
// switch (message.what) {
// case SmsMigrator.PROGRESS_UPDATE:
// progressDialog.incrementProgressBy(message.arg1);
// progressDialog.setSecondaryProgress(0);
// break;
// case SmsMigrator.SECONDARY_PROGRESS_UPDATE:
// progressDialog.incrementSecondaryProgressBy(message.arg1);
// break;
// case SmsMigrator.COMPLETE:
// progressDialog.dismiss();
// ConversationListActivity.this.migrateDatabaseComplete();
// break;
// }
// }
// }
//
}

View File

@ -0,0 +1,230 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ListView;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.Set;
public class ConversationListFragment extends SherlockListFragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
private ConversationSelectedListener listener;
private MasterSecret masterSecret;
private boolean isBatchMode = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
return inflater.inflate(R.layout.conversation_list_fragment, container, false);
}
@Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
setHasOptionsMenu(true);
initializeListAdapter();
registerForContextMenu(getListView());
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.listener = (ConversationSelectedListener)activity;
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = this.getSherlockActivity().getSupportMenuInflater();
if (this.isBatchMode) inflater.inflate(R.menu.conversation_list_batch, menu);
else if (this.masterSecret == null) inflater.inflate(R.menu.conversation_list_locked, menu);
else inflater.inflate(R.menu.conversation_list, menu);
super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_batch_mode: handleSwitchBatchMode(true); return true;
case R.id.menu_delete_selected: handleDeleteAllSelected(); return true;
case R.id.menu_select_all: handleSelectAllThreads(); return true;
case R.id.menu_unselect_all: handleUnselectAllThreads(); return true;
case R.id.menu_normal_mode: handleSwitchBatchMode(false); return true;
}
return false;
}
@Override
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
android.view.MenuInflater inflater = this.getSherlockActivity().getMenuInflater();
menu.clear();
inflater.inflate(R.menu.conversation_list_context, menu);
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
Recipients recipients = RecipientFactory.getRecipientsForIds(getActivity(), recipientId);
switch(item.getItemId()) {
case R.id.menu_context_view: handleCreateConversation(threadId, recipients); return true;
case R.id.menu_context_delete: handleDeleteThread(threadId); return true;
}
return false;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (v instanceof ConversationHeaderView) {
ConversationHeaderView headerView = (ConversationHeaderView) v;
handleCreateConversation(headerView.getThreadId(), headerView.getRecipients());
}
}
public void setMasterSecret(MasterSecret masterSecret) {
this.masterSecret = masterSecret;
initializeListAdapter();
}
private void initializeListAdapter() {
if (this.masterSecret == null) {
this.setListAdapter(new ConversationListAdapter(getActivity(), null));
} else {
this.setListAdapter(new DecryptingConversationListAdapter(getActivity(), null, masterSecret));
}
getLoaderManager().restartLoader(0, null, this);
}
private void handleSwitchBatchMode(boolean batchMode) {
this.isBatchMode = batchMode;
((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
this.getSherlockActivity().invalidateOptionsMenu();
}
private void handleDeleteAllSelected() {
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setIcon(android.R.drawable.ic_dialog_alert);
alert.setTitle("Delete threads?");
alert.setMessage("Are you sure you wish to delete ALL selected conversation threads?");
alert.setCancelable(true);
alert.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Set<Long> selectedConversations = ((ConversationListAdapter)getListAdapter())
.getBatchSelections();
if (!selectedConversations.isEmpty()) {
DatabaseFactory.getThreadDatabase(getActivity())
.deleteConversations(selectedConversations);
}
}
});
alert.setNegativeButton("Cancel", null);
alert.show();
}
private void handleDeleteThread(final long threadId) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Delete Thread Confirmation");
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setCancelable(true);
builder.setMessage("Are you sure that you want to permanently delete this conversation?");
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (threadId > 0) {
DatabaseFactory.getThreadDatabase(getActivity()).deleteConversation(threadId);
}
}
});
builder.setNegativeButton(R.string.no, null);
builder.show();
}
private void handleSelectAllThreads() {
((ConversationListAdapter)this.getListAdapter()).selectAllThreads();
}
private void handleUnselectAllThreads() {
((ConversationListAdapter)this.getListAdapter()).selectAllThreads();
}
private void handleCreateConversation(long threadId, Recipients recipients) {
listener.onCreateConversation(threadId, recipients);
}
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new ConversationListLoader(getActivity(), null);
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
((CursorAdapter)getListAdapter()).changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
((CursorAdapter)getListAdapter()).changeCursor(null);
}
public interface ConversationSelectedListener {
public void onCreateConversation(long threadId, Recipients recipients);
}
}

View File

@ -1,913 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcelable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockListActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.SubMenu;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
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.ApplicationExporter;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.database.SmsMigrator;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
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;
import org.thoughtcrime.securesms.util.Eula;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
*
* The main Activity for TextSecure. Manages the conversation list, search
* access to the conversation list, and import/export handling.
*
* @author Moxie Marlinspike
*/
public class SecureSMS extends SherlockListActivity {
private static final int MENU_NEW_MESSAGE = 0;
private static final int MENU_SEND_KEY = 1;
private static final int MENU_PASSPHRASE_KEY = 2;
private static final int MENU_PREFERENCES_KEY = 3;
private static final int MENU_EXPORT = 5;
private static final int MENU_IMPORT = 6;
private static final int MENU_SEARCH = 11;
private static final int MENU_CLEAR_PASSPHRASE = 12;
private static final int MENU_DELETE_SELECTED_THREADS = 13;
private static final int MENU_BATCH_MODE = 14;
private static final int MENU_EXIT_BATCH = 15;
private static final int MENU_SELECT_ALL_THREADS = 16;
private static final int MENU_CLEAR_SELECTION = 17;
private static final int VIEW_THREAD_ID = 100;
private static final int VIEW_CONTACT_ID = 101;
private static final int DELETE_THREAD_ID = 102;
private static final int ADD_CONTACT_ID = 103;
private EditText searchBox;
private ConversationHeaderView headerView;
private MasterSecret masterSecret;
private KillActivityReceiver killActivityReceiver;
private NewKeyReceiver receiver;
private boolean havePromptedForPassphrase = false;
private boolean batchMode = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("TextSecure");
setContentView(R.layout.main);
initializeKillReceiver();
initializeSenderReceiverService();
initializeSearchListener();
registerForContextMenu(getListView());
registerForContactsUpdates();
}
@Override
public void onPause() {
super.onPause();
if (receiver != null) {
Log.w("securesms", "Unregistering receiver...");
unregisterReceiver(receiver);
receiver = null;
}
}
@Override
public void onResume() {
super.onResume();
Log.w("securesms", "restart called...");
Eula.showDisclaimer(this);
}
@Override
public void onStart() {
super.onStart();
registerPassphraseActivityStarted();
}
@Override
public void onStop() {
super.onStop();
havePromptedForPassphrase = false;
registerPassphraseActivityStopped();
}
@Override
public void onDestroy() {
Log.w("SecureSMS", "onDestroy...");
unregisterReceiver(killActivityReceiver);
MemoryCleaner.clean(masterSecret);
super.onDestroy();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
if (!batchMode) prepareNormalMenu(menu);
else prepareBatchModeMenu(menu);
return true;
}
private void prepareNormalMenu(Menu menu) {
menu.add(0, MENU_BATCH_MODE, Menu.NONE, "Batch Mode")
.setIcon(android.R.drawable.ic_menu_share);
if (masterSecret != null) {
menu.add(0, MENU_SEND_KEY, Menu.NONE, "Secure Session")
.setIcon(R.drawable.ic_lock_message_sms);
} else {
menu.add(0, MENU_PASSPHRASE_KEY, Menu.NONE, "Enter passphrase")
.setIcon(R.drawable.ic_lock_message_sms);
}
menu.add(0, MENU_NEW_MESSAGE, Menu.NONE, "New Message")
.setIcon(R.drawable.ic_menu_msg_compose_holo_dark)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENU_SEARCH, Menu.NONE, "Search")
.setIcon(R.drawable.ic_menu_search_holo_dark)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENU_PREFERENCES_KEY, Menu.NONE, "Settings")
.setIcon(android.R.drawable.ic_menu_preferences);
SubMenu importExportMenu = menu.addSubMenu("Import/Export")
.setIcon(android.R.drawable.ic_menu_save);
importExportMenu.add(0, MENU_EXPORT, Menu.NONE, "Export To SD Card")
.setIcon(android.R.drawable.ic_menu_save);
importExportMenu.add(0, MENU_IMPORT, Menu.NONE, "Import From SD Card")
.setIcon(android.R.drawable.ic_menu_revert);
if (masterSecret != null) {
menu.add(0, MENU_CLEAR_PASSPHRASE, Menu.NONE, "Clear Passphrase")
.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
}
}
private void prepareBatchModeMenu(Menu menu) {
menu.add(0, MENU_EXIT_BATCH, Menu.NONE, "Normal Mode")
.setIcon(android.R.drawable.ic_menu_set_as);
menu.add(0, MENU_DELETE_SELECTED_THREADS, Menu.NONE, "Delete Selected")
.setIcon(R.drawable.ic_menu_trash_holo_dark)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENU_SELECT_ALL_THREADS, Menu.NONE, "Select All")
.setIcon(android.R.drawable.ic_menu_add);
menu.add(0, MENU_CLEAR_SELECTION, Menu.NONE, "Unselect All")
.setIcon(android.R.drawable.ic_menu_revert);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case MENU_NEW_MESSAGE:
createConversation(-1, null);
return true;
case MENU_SEND_KEY:
Intent intent = new Intent(this, SendKeyActivity.class);
intent.putExtra("master_secret", masterSecret);
startActivity(intent);
return true;
case MENU_PASSPHRASE_KEY:
promptForPassphrase();
return true;
case MENU_BATCH_MODE:
initiateBatchMode();
return true;
case MENU_PREFERENCES_KEY:
Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
preferencesIntent.putExtra("master_secret", masterSecret);
startActivity(preferencesIntent);
return true;
case MENU_EXPORT:
ExportHandler exportHandler = new ExportHandler();
exportHandler.export();
return true;
case MENU_IMPORT:
ExportHandler importHandler = new ExportHandler();
importHandler.importFromSd();
return true;
case MENU_SEARCH:
findViewById(R.id.search_text).setVisibility(View.VISIBLE);
findViewById(R.id.search_close).setVisibility(View.VISIBLE);
findViewById(R.id.search_text).requestFocus();
return true;
case MENU_DELETE_SELECTED_THREADS: deleteSelectedThreads(); return true;
case MENU_SELECT_ALL_THREADS: selectAllThreads(); return true;
case MENU_CLEAR_SELECTION: unselectAllThreads(); return true;
case MENU_EXIT_BATCH: stopBatchMode(); return true;
case MENU_CLEAR_PASSPHRASE:
Intent keyService = new Intent(this, KeyCachingService.class);
keyService.setAction(KeyCachingService.CLEAR_KEY_ACTION);
startService(keyService);
addConversationItems();
promptForPassphrase();
// finish();
return true;
}
return false;
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.w("SecureSMS", "Got onNewIntent...");
createConversationIfNecessary(intent);
}
public void eulaComplete() {
clearNotifications();
initializeReceivers();
checkCachingService();
}
@Override
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (((AdapterView.AdapterContextMenuInfo)menuInfo).position > 0) {
Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientId);
menu.add(0, VIEW_THREAD_ID, Menu.NONE, "View thread");
if (recipients.isSingleRecipient()) {
if (recipients.getPrimaryRecipient().getName() != null) {
menu.add(0, VIEW_CONTACT_ID, Menu.NONE, "View contact");
} else {
menu.add(0, ADD_CONTACT_ID, Menu.NONE, "Add to contacts");
}
}
menu.add(0, DELETE_THREAD_ID, Menu.NONE, "Delete thread");
}
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS));
Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientId);
switch(item.getItemId()) {
case VIEW_THREAD_ID:
createConversation(threadId, recipients);
return true;
case VIEW_CONTACT_ID:
viewContact(recipients.getPrimaryRecipient());
return true;
case ADD_CONTACT_ID:
addContact(recipients.getPrimaryRecipient());
return true;
case DELETE_THREAD_ID:
deleteThread(threadId);
return true;
}
return false;
}
private void initiateBatchMode() {
this.batchMode = true;
((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
}
private void stopBatchMode() {
this.batchMode = false;
((ConversationListAdapter)this.getListAdapter()).initializeBatchMode(batchMode);
}
private void selectAllThreads() {
((ConversationListAdapter)this.getListAdapter()).selectAllThreads();
}
private void unselectAllThreads() {
((ConversationListAdapter)this.getListAdapter()).unselectAllThreads();
}
private void deleteSelectedThreads() {
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setIcon(android.R.drawable.ic_dialog_alert);
alert.setTitle("Delete threads?");
alert.setMessage("Are you sure you wish to delete ALL selected conversation threads?");
alert.setCancelable(true);
alert.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Set<Long> selectedConversations = ((ConversationListAdapter)getListAdapter()).getBatchSelections();
if (!selectedConversations.isEmpty())
DatabaseFactory.getThreadDatabase(SecureSMS.this).deleteConversations(selectedConversations);
}
});
alert.setNegativeButton("Cancel", null);
alert.show();
}
private void registerForContactsUpdates() {
Log.w("SecureSMS", "Registering for contacts update...");
getContentResolver().registerContentObserver(ContactAccessor.getInstance().getContactsUri(), true, new ContactUpdateObserver());
}
private void registerPassphraseActivityStarted() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
startService(intent);
}
private void registerPassphraseActivityStopped() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
startService(intent);
}
private void addContact(Recipient recipient) {
RecipientFactory.getRecipientProvider().addContact(this, recipient);
}
private void viewContact(Recipient recipient) {
if (recipient.getPersonId() > 0) {
RecipientFactory.getRecipientProvider().viewContact(this, recipient);
}
}
private void clearNotifications() {
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(KeyCachingService.NOTIFICATION_ID);
}
private void initializeKillReceiver() {
killActivityReceiver = new KillActivityReceiver();
registerReceiver(killActivityReceiver, new IntentFilter(KeyCachingService.PASSPHRASE_EXPIRED_EVENT),
KeyCachingService.KEY_PERMISSION, null);
}
private void initializeSenderReceiverService() {
Intent smsSenderIntent = new Intent(SendReceiveService.SEND_SMS_ACTION, null, this, SendReceiveService.class);
Intent mmsSenderIntent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, this, SendReceiveService.class);
startService(smsSenderIntent);
startService(mmsSenderIntent);
}
private void checkCachingService() {
Log.w("securesms", "Checking caching service...");
Intent bindIntent = new Intent(this, KeyCachingService.class);
bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void migrateDatabaseComplete() {
if (masterSecret != null)
DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
}
private void migrateDatabase() {
MigrationHandler mh = new MigrationHandler();
mh.migrate();
}
private boolean isMigrated() {
return this.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false);
}
private void initializeWithMasterSecret(MasterSecret masterSecret) {
this.masterSecret = masterSecret;
if (masterSecret != null) {
if (!IdentityKeyUtil.hasIdentityKey(this)) initializeIdentityKeys();
if (!MasterSecretUtil.hasAsymmericMasterSecret(this)) initializeAsymmetricMasterSecret();
if (!isMigrated()) migrateDatabase();
else DecryptingQueue.schedulePendingDecrypts(this, masterSecret);
}
addConversationItems();
createConversationIfNecessary(this.getIntent());
}
private void initializeAsymmetricMasterSecret() {
new Thread(new AsymmetricMasteSecretInitializer()).start();
}
private void initializeIdentityKeys() {
new Thread(new IdentityKeyInitializer()).start();
}
private void initializeSearchListener() {
SearchTextListener listener = new SearchTextListener();
searchBox = (EditText)findViewById(R.id.search_text);
searchBox.addTextChangedListener(listener);
this.getListView().setOnKeyListener(listener);
}
private void initializeReceivers() {
Log.w("securesms", "Registering receiver...");
receiver = new NewKeyReceiver();
IntentFilter filter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT);
registerReceiver(receiver, filter, KeyCachingService.KEY_PERMISSION, null);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if (v instanceof ConversationHeaderView) {
ConversationHeaderView headerView = (ConversationHeaderView) v;
createConversation(headerView.getThreadId(), headerView.getRecipients());
}
}
private void createConversationIfNecessary(Intent intent) {
long thread = intent.getLongExtra("thread_id", -1L);
Recipients recipients = null;
if (intent.getAction() != null && intent.getAction().equals("android.intent.action.SENDTO")) {
try {
recipients = RecipientFactory.getRecipientsFromString(this, intent.getData().getSchemeSpecificPart());
thread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
} catch (RecipientFormattingException rfe) {
recipients = null;
}
} else {
recipients = intent.getParcelableExtra("recipients");
}
if (recipients != null) {
createConversation(thread, recipients);
intent.putExtra("thread_id", -1L);
intent.putExtra("recipients", (Parcelable)null);
intent.setAction(null);
}
}
private void createConversation(long threadId, Recipients recipients) {
if (this.masterSecret == null) {
promptForPassphrase();
return;
}
Intent intent = new Intent(this, ComposeMessageActivity.class);
intent.putExtra("recipients", recipients);
intent.putExtra("thread_id", threadId);
intent.putExtra("master_secret", masterSecret);
startActivity(intent);
}
private void addConversationItems() {
Cursor cursor = DatabaseFactory.getThreadDatabase(this).getConversationList();
startManagingCursor(cursor);
if (masterSecret == null) setListAdapter(new ConversationListAdapter(this, cursor));
else setListAdapter(new DecryptingConversationListAdapter(this, cursor, masterSecret));
}
private void deleteThread(long threadId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Delete Thread Confirmation");
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setCancelable(true);
builder.setMessage("Are you sure that you want to permanently delete this conversation?");
builder.setPositiveButton(R.string.yes, new DeleteThreadListener(threadId));
builder.setNegativeButton(R.string.no, null);
builder.show();
}
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 ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
MasterSecret masterSecret = keyCachingService.getMasterSecret();
initializeWithMasterSecret(masterSecret);
if (masterSecret == null && !havePromptedForPassphrase)
promptForPassphrase();
Intent cachingIntent = new Intent(SecureSMS.this, KeyCachingService.class);
startService(cachingIntent);
try {
SecureSMS.this.unbindService(this);
} catch (IllegalArgumentException iae) {
Log.w("SecureSMS", iae);
}
}
public void onServiceDisconnected(ComponentName name) {}
};
private class DeleteThreadListener implements DialogInterface.OnClickListener {
private final long threadId;
public DeleteThreadListener(long threadId) {
this.threadId = threadId;
}
public void onClick(DialogInterface dialog, int which) {
if (threadId > 0) {
DatabaseFactory.getThreadDatabase(SecureSMS.this).deleteConversation(threadId);
}
}
};
private class NewKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.w("securesms", "Got a broadcast...");
initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
}
}
private class KillActivityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context arg0, Intent arg1) {
finish();
}
}
// private class SettingsClickListener implements View.OnClickListener {
// public void onClick(View v) {
// startActivity(new Intent(SecureSMS.this, ApplicationPreferencesActivity.class));
// }
// }
private class IdentityKeyInitializer implements Runnable {
public void run() {
IdentityKeyUtil.generateIdentityKeys(SecureSMS.this, masterSecret);
}
}
private class AsymmetricMasteSecretInitializer implements Runnable {
public void run() {
MasterSecretUtil.generateAsymmetricMasterSecret(SecureSMS.this, masterSecret);
}
}
private class ExportHandler 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;
public void run() {
try {
switch (task) {
case TASK_EXPORT: ApplicationExporter.exportToSd(SecureSMS.this); break;
case TASK_IMPORT: ApplicationExporter.importFromSd(SecureSMS.this); 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();
}
private void continueExport() {
task = TASK_EXPORT;
progressDialog = new ProgressDialog(SecureSMS.this);
progressDialog.setTitle("Exporting Database and Keys");
progressDialog.setMessage("Exporting your SMS database, keys, and settings...");
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.show();
new Thread(this).start();
}
private void continueImport() {
task = TASK_IMPORT;
progressDialog = new ProgressDialog(SecureSMS.this);
progressDialog.setTitle("Importing Database and Keys");
progressDialog.setMessage("Importnig your SMS database, keys, and settings...");
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.show();
initializeWithMasterSecret(null);
Intent clearKeyIntent = new Intent(KeyCachingService.CLEAR_KEY_ACTION, null, SecureSMS.this, KeyCachingService.class);
startService(clearKeyIntent);
DatabaseFactory.getInstance(SecureSMS.this).close();
new Thread(this).start();
}
public void importFromSd() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(SecureSMS.this);
alertBuilder.setTitle("Import Database and Settings?");
alertBuilder.setMessage("Import TextSecure database, keys, and settings from the SD Card?\n\nWARNING: This will clobber any existing messages, keys, and settings!");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Import", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
continueImport();
}
});
alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
});
alertBuilder.create().show();
}
public void export() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(SecureSMS.this);
alertBuilder.setTitle("Export Database?");
alertBuilder.setMessage("Export TextSecure database, keys, and settings to the SD Card?");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Export", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
continueExport();
}
});
alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
});
alertBuilder.create().show();
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case ERROR_NO_SD:
Toast.makeText(SecureSMS.this, "No SD card found!", Toast.LENGTH_LONG).show();
break;
case ERROR_IO:
Toast.makeText(SecureSMS.this, "Error exporting to SD!", Toast.LENGTH_LONG).show();
break;
case COMPLETE:
switch (task) {
case TASK_IMPORT:
Toast.makeText(SecureSMS.this, "Import Successful!", Toast.LENGTH_LONG).show();
addConversationItems();
promptForPassphrase();
break;
case TASK_EXPORT:
Toast.makeText(SecureSMS.this, "Export Successful!", Toast.LENGTH_LONG).show();
break;
}
break;
}
progressDialog.dismiss();
}
}
private class SearchTextListener extends Handler implements TextWatcher, Runnable, View.OnClickListener, View.OnKeyListener {
private final ImageView closeButton;
private final ProgressBar progressDialog;
private final KeyCharacterMap keyMap;
private int outstandingThreads;
public SearchTextListener() {
closeButton = (ImageView)findViewById(R.id.search_close);
progressDialog = (ProgressBar)findViewById(R.id.search_progress);
keyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
outstandingThreads = 0;
closeButton.setOnClickListener(this);
}
private Cursor getCursorForFilter(String text) {
if (text.length() > 0) {
List<String> numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(text, getContentResolver());
return DatabaseFactory.getThreadDatabase(SecureSMS.this).getFilteredConversationList(numbers);
} else {
return DatabaseFactory.getThreadDatabase(SecureSMS.this).getConversationList();
}
}
public void afterTextChanged(Editable arg0) {
synchronized (this) {
if (outstandingThreads == 0)
progressDialog.setVisibility(View.VISIBLE);
outstandingThreads++;
}
new Thread(this).start();
}
@Override
public void handleMessage(Message message) {
Cursor cursor = (Cursor)message.obj;
if (cursor != null)
startManagingCursor(cursor);
if (getListAdapter() != null)
((CursorAdapter)getListAdapter()).changeCursor(cursor);
synchronized (this) {
outstandingThreads--;
if (outstandingThreads == 0)
progressDialog.setVisibility(View.GONE);
}
}
public void run() {
String text = searchBox.getText().toString();
Cursor cursor = getCursorForFilter(text);
this.obtainMessage(0, cursor).sendToTarget();
}
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
public void onClick(View v) {
searchBox.setVisibility(View.GONE);
closeButton.setVisibility(View.GONE);
searchBox.setText("");
}
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyMap.isPrintingKey(keyCode))) {
int character = keyMap.get(keyCode, event.getMetaState());
searchBox.setVisibility(View.VISIBLE);
closeButton.setVisibility(View.VISIBLE);
searchBox.setText(new String(Character.toChars(character)));
searchBox.requestFocus();
return true;
}
return false;
}
}
private class ContactUpdateObserver extends ContentObserver {
public ContactUpdateObserver() {
super(null);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.w("SesureSMS", "Got contact update, clearing cache...");
RecipientFactory.clearCache();
}
}
private class MigrationHandler extends Handler implements Runnable {
private ProgressDialog progressDialog;
public void run() {
SmsMigrator.migrateDatabase(SecureSMS.this, masterSecret, this);
}
private void continueMigration() {
progressDialog = new ProgressDialog(SecureSMS.this);
progressDialog.setTitle("Migrating Database");
progressDialog.setMessage("Migrating your SMS database...");
progressDialog.setMax(10000);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
new Thread(this).start();
}
private void cancelMigration() {
SecureSMS.this.getSharedPreferences("SecureSMS", MODE_PRIVATE).edit().putBoolean("migrated", true).commit();
SecureSMS.this.migrateDatabaseComplete();
}
public void migrate() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(SecureSMS.this);
alertBuilder.setTitle("Copy System Text Message Database?");
alertBuilder.setMessage("Current versions of TextSecure use an encrypted database that is separate from the default system database. Would you like to copy your existing text messages into TextSecure's encrypted database? Your default system database will be unaffected.");
alertBuilder.setCancelable(false);
alertBuilder.setPositiveButton("Copy", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
continueMigration();
}
});
alertBuilder.setNegativeButton("Don't copy", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
cancelMigration();
}
});
alertBuilder.create().show();
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case SmsMigrator.PROGRESS_UPDATE:
progressDialog.incrementProgressBy(message.arg1);
progressDialog.setSecondaryProgress(0);
break;
case SmsMigrator.SECONDARY_PROGRESS_UPDATE:
progressDialog.incrementSecondaryProgressBy(message.arg1);
break;
case SmsMigrator.COMPLETE:
progressDialog.dismiss();
SecureSMS.this.migrateDatabaseComplete();
break;
}
}
}
}

View File

@ -1,6 +1,6 @@
/**
/**
* Copyright (C) 2011 Whisper Systems
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@ -10,75 +10,85 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
public class ApplicationExporter {
public static void exportToSd(Context context) throws NoExternalStorageException, IOException {
verifyExternalStorageForExport();
exportDirectory(context, "");
}
public static void importFromSd(Context context) throws NoExternalStorageException, IOException {
verifyExternalStorageForImport();
importDirectory(context, "");
}
private static String getExportDirectoryPath() {
File sdDirectory = Environment.getExternalStorageDirectory();
return sdDirectory.getAbsolutePath() + File.separator + "TextSecureExport";
return sdDirectory.getAbsolutePath() + File.separator + "TextSecureExport";
}
private static void verifyExternalStorageForExport() throws NoExternalStorageException {
if (!Environment.getExternalStorageDirectory().canWrite())
throw new NoExternalStorageException();
String exportDirectoryPath = getExportDirectoryPath();
File exportDirectory = new File(exportDirectoryPath);
if (!exportDirectory.exists())
exportDirectory.mkdir();
}
private static void verifyExternalStorageForImport() throws NoExternalStorageException {
if (!Environment.getExternalStorageDirectory().canRead() ||
!(new File(getExportDirectoryPath()).exists()))
throw new NoExternalStorageException();
!(new File(getExportDirectoryPath()).exists()))
throw new NoExternalStorageException();
}
private static void migrateFile(File from, File to) throws IOException {
if (from.exists()) {
if (from.exists()) {
FileChannel source = new FileInputStream(from).getChannel();
FileChannel destination = new FileOutputStream(to).getChannel();
destination.transferFrom(source, 0, source.size());
source.close();
destination.close();
}
}
}
private static void exportDirectory(Context context, String directoryName) throws IOException {
File directory = new File(context.getFilesDir().getParent() + File.separatorChar + directoryName);
File exportDirectory = new File(getExportDirectoryPath() + File.separatorChar + directoryName);
if (directory.exists()) {
exportDirectory.mkdirs();
File[] contents = directory.listFiles();
for (int i=0;i<contents.length;i++) {
File localFile = contents[i];
if (localFile.isFile()) {
File exportedFile = new File(exportDirectory.getAbsolutePath() + File.separator + localFile.getName());
migrateFile(localFile, exportedFile);
} else {
exportDirectory(context, directoryName + File.separator + localFile.getName());
}
File localFile = contents[i];
if (localFile.isFile()) {
File exportedFile = new File(exportDirectory.getAbsolutePath() + File.separator + localFile.getName());
migrateFile(localFile, exportedFile);
} else {
exportDirectory(context, directoryName + File.separator + localFile.getName());
}
}
} else {
Log.w("ApplicationExporter", "Could not find directory: " + directory.getAbsolutePath());
@ -88,40 +98,22 @@ public class ApplicationExporter {
private static void importDirectory(Context context, String directoryName) throws IOException {
File directory = new File(getExportDirectoryPath() + File.separator + directoryName);
File importDirectory = new File(context.getFilesDir().getParent() + File.separator + directoryName);
if (directory.exists()) {
importDirectory.mkdirs();
File[] contents = directory.listFiles();
for (int i=0;i<contents.length;i++) {
File exportedFile = contents[i];
if (exportedFile.isFile()) {
File localFile = new File(importDirectory.getAbsolutePath() + File.separator + exportedFile.getName());
migrateFile(exportedFile, localFile);
} else {
importDirectory(context, directoryName + File.separator + exportedFile.getName());
}
File exportedFile = contents[i];
if (exportedFile.isFile()) {
File localFile = new File(importDirectory.getAbsolutePath() + File.separator + exportedFile.getName());
migrateFile(exportedFile, localFile);
} else {
importDirectory(context, directoryName + File.separator + exportedFile.getName());
}
}
}
}
public static void exportToSd(Context context) throws NoExternalStorageException, IOException {
verifyExternalStorageForExport();
exportDirectory(context, "");
// exportDirectory(context, "databases");
// exportDirectory(context, "sessions");
// exportDirectory(context, "shared_prefs");
}
public static void importFromSd(Context context) throws NoExternalStorageException, IOException {
verifyExternalStorageForImport();
importDirectory(context, "");
// importDirectory(context, "databases");
// importDirectory(context, "sessions");
// importDirectory(context, "shared_prefs");
}
}

View File

@ -0,0 +1,25 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.CursorLoader;
import org.thoughtcrime.securesms.database.DatabaseFactory;
public class ConversationListLoader extends CursorLoader {
private final String filter;
private final Context context;
public ConversationListLoader(Context context, String filter) {
super(context);
this.filter = filter;
this.context = context.getApplicationContext();
}
@Override
public Cursor loadInBackground() {
return DatabaseFactory.getThreadDatabase(context).getConversationList();
}
}

View File

@ -29,8 +29,8 @@ import android.preference.PreferenceManager;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.SecureSMS;
import org.thoughtcrime.securesms.crypto.MasterSecret;
/**
@ -141,7 +141,7 @@ public class KeyCachingService extends Service {
private void foregroundService() {
Notification notification = new Notification(R.drawable.icon, "TextSecure Passphrase Cached", System.currentTimeMillis());
Intent intent = new Intent(this, SecureSMS.class);
Intent intent = new Intent(this, ConversationListActivity.class);
PendingIntent launchIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
notification.setLatestEventInfo(getApplicationContext(), "TextSecure Cached", "TextSecure Passphrase Cached", launchIntent);

View File

@ -1,6 +1,6 @@
/**
/**
* Copyright (C) 2011 Whisper Systems
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@ -10,26 +10,12 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import java.util.LinkedList;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.SecureSMS;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@ -43,17 +29,31 @@ import android.preference.PreferenceManager;
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.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.LinkedList;
/**
* Handles posting system notifications for new messages.
*
*
*
*
* @author Moxie Marlinspike
*/
public class MessageNotifier {
public static final int NOTIFICATION_ID = 1338;
private static String buildTickerMessage(int count, Recipients recipients) {
Recipient recipient = recipients.getPrimaryRecipient();
StringBuilder builder = new StringBuilder();
@ -61,26 +61,26 @@ public class MessageNotifier {
builder.append(count);
builder.append(')');
builder.append(" New messages");
if (recipient != null) {
builder.append(", most recent from: ");
builder.append(", most recent from: ");
builder.append(recipient.getName() == null ? recipient.getNumber() : recipient.getName());
}
return builder.toString();
}
private static String buildTitleMessage(int count) {
return "(" + count + ") New Messages";
}
private static String buildSubtitleMessage(Recipients recipients) {
Recipient recipient = recipients.getPrimaryRecipient();
if (recipient != null) {
return "Most recent from: " + (recipient.getName() == null ? recipient.getNumber() : recipient.getName());
}
return null;
}
@ -88,60 +88,60 @@ public class MessageNotifier {
String address = c.getString(c.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
return RecipientFactory.getRecipientsFromString(context, address);
}
private static Recipients getMmsRecipient(Context context, Cursor c) throws RecipientFormattingException {
long messageId = c.getLong(c.getColumnIndexOrThrow(MmsDatabase.ID));
String address = DatabaseFactory.getMmsDatabase(context).getMessageRecipient(messageId);
return RecipientFactory.getRecipientsFromString(context, address);
}
private static Recipients getMostRecentRecipients(Context context, Cursor c) {
if (c != null && c.moveToLast()) {
try {
String type = c.getString(c.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
if (type.equals("sms"))
return getSmsRecipient(context, c);
else
return getMmsRecipient(context, c);
String type = c.getString(c.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
if (type.equals("sms"))
return getSmsRecipient(context, c);
else
return getMmsRecipient(context, c);
} catch (RecipientFormattingException e) {
return new Recipients(new LinkedList<Recipient>());
return new Recipients(new LinkedList<Recipient>());
}
}
return null;
}
private static PendingIntent buildPendingIntent(Context context, Cursor c, Recipients recipients) {
Intent intent = new Intent(context, SecureSMS.class);
Intent intent = new Intent(context, ConversationListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Log.w("SMSNotifier", "Building pending intent...");
if (c != null && c.getCount() == 1) {
Log.w("SMSNotifier", "Adding extras...");
c.moveToLast();
long threadId = c.getLong(c.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
Log.w("SmsNotifier", "Adding thread_id to pending intent: " + threadId);
if (recipients.getPrimaryRecipient() != null) {
intent.putExtra("recipients", recipients);
intent.putExtra("thread_id", threadId);
intent.putExtra("recipients", recipients);
intent.putExtra("thread_id", threadId);
}
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
}
return PendingIntent.getActivity(context, 0, intent, 0);
}
private static void sendNotification(Context context, NotificationManager manager, PendingIntent launchIntent,
String ticker, String title, String subtitle, boolean signal)
private static void sendNotification(Context context, NotificationManager manager, PendingIntent launchIntent,
String ticker, String title, String subtitle, boolean signal)
{
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
if (!sp.getBoolean(ApplicationPreferencesActivity.NOTIFICATION_PREF, true)) return;
Notification notification = new Notification(R.drawable.stat_notify_sms, ticker, System.currentTimeMillis());
String ringtone = sp.getString(ApplicationPreferencesActivity.RINGTONE_PREF, null);
boolean vibrate = sp.getBoolean(ApplicationPreferencesActivity.VIBRATE_PREF, true);
@ -149,7 +149,7 @@ public class MessageNotifier {
String ledBlinkPattern = sp.getString(ApplicationPreferencesActivity.LED_BLINK_PREF, "500,2000");
String ledBlinkPatternCustom = sp.getString(ApplicationPreferencesActivity.LED_BLINK_PREF_CUSTOM, "500,2000");
String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
notification.setLatestEventInfo(context, title, subtitle, launchIntent);
notification.sound = TextUtils.isEmpty(ringtone) || !signal ? null : Uri.parse(ringtone);
if (signal && vibrate)
@ -159,43 +159,43 @@ public class MessageNotifier {
notification.ledARGB = Color.parseColor(ledColor);//0xff00ff00;
notification.ledOnMS = Integer.parseInt(blinkPatternArray[0]);
notification.ledOffMS = Integer.parseInt(blinkPatternArray[1]);
manager.notify(NOTIFICATION_ID, notification);
manager.notify(NOTIFICATION_ID, notification);
}
private static void flashNotification(Context context, NotificationManager manager) {
sendNotification(context, manager, buildPendingIntent(context, null, null), "(1) New Messages", "(1) New Messages", null, true);
}
public static void updateNotification(Context context, boolean signal) {
NotificationManager manager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
manager.cancel(NOTIFICATION_ID);
Cursor c = null;
try {
c = DatabaseFactory.getMmsSmsDatabase(context).getUnread();
if ((c == null && signal) || (!c.moveToFirst() && signal)) {flashNotification(context, manager); return;}
else if (c == null || !c.moveToFirst()) return;
Recipients recipients = getMostRecentRecipients(context, c);
String ticker = buildTickerMessage(c.getCount(), recipients);
String title = buildTitleMessage(c.getCount());
String subtitle = buildSubtitleMessage(recipients);
PendingIntent launchIntent = buildPendingIntent(context, c, recipients);
sendNotification(context, manager, launchIntent, ticker, title, subtitle, signal);
} finally {
if (c != null)
c.close();
c.close();
}
}
}
private static String[] parseBlinkPattern(String blinkPattern, String blinkPatternCustom) {
if (blinkPattern.equals("custom"))
blinkPattern = blinkPatternCustom;
return blinkPattern.split(",");
}
}

View File

@ -1,102 +0,0 @@
package org.thoughtcrime.securesms.util;
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.SecureSMS;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Displays an EULA ("End User License Agreement") that the user has to accept before
* using the application. Your application should call {@link Eula#showEula(android.app.Activity)}
* in the onCreate() method of the first activity. If the user accepts the EULA, it will never
* be shown again. If the user refuses, {@link android.app.Activity#finish()} is invoked
* on your activity.
*/
public class Eula {
private static final String PREFERENCE_EULA_ACCEPTED = "eula.accepted";
private static final String PREFERENCES_EULA = "eula";
private static final String PREFERENCE_DISCLAIMER_ACCEPTED = "disclaimer.accepted";
public static boolean seenDisclaimer(final Activity activity) {
return activity.getSharedPreferences(PREFERENCES_EULA, Activity.MODE_PRIVATE).getBoolean(PREFERENCE_DISCLAIMER_ACCEPTED, false);
}
private static void acceptDisclaimer(SecureSMS activity) {
activity.getSharedPreferences(PREFERENCES_EULA, Activity.MODE_PRIVATE).edit().putBoolean(PREFERENCE_DISCLAIMER_ACCEPTED, true).commit();
activity.eulaComplete();
}
public static void showDisclaimer(final SecureSMS activity) {
if (!seenDisclaimer(activity)) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(readFile(activity, R.raw.disclaimer));
builder.setCancelable(true);
builder.setTitle("Please Note");
builder.setPositiveButton("I understand", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
acceptDisclaimer(activity);
}
});
builder.create().show();
} else {
activity.eulaComplete();
}
}
private static CharSequence readFile(Activity activity, int id) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(
activity.getResources().openRawResource(id)));
String line;
StringBuilder buffer = new StringBuilder();
while ((line = in.readLine()) != null) buffer.append(line).append('\n');
return buffer;
} catch (IOException e) {
return "";
} finally {
closeStream(in);
}
}
/**
* Closes the specified stream.
*
* @param stream The stream to close.
*/
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
}
}