diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index ac262a3c2..e5e660ddd 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -194,6 +194,11 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java b/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java index 7fd096213..3ee8915c2 100644 --- a/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java +++ b/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java @@ -1,14 +1,240 @@ package org.briarproject.android.panic; +import android.app.Activity; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.support.v7.preference.CheckBoxPreference; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceFragmentCompat; +import android.text.TextUtils; import org.briarproject.R; -public class PanicPreferencesFragment extends PreferenceFragmentCompat { +import java.util.ArrayList; +import java.util.logging.Logger; + +import info.guardianproject.panic.Panic; +import info.guardianproject.panic.PanicResponder; + +public class PanicPreferencesFragment extends PreferenceFragmentCompat + implements SharedPreferences.OnSharedPreferenceChangeListener { + + private static final Logger LOG = + Logger.getLogger(PanicPreferencesFragment.class.getName()); + + private PackageManager pm; + private CheckBoxPreference lockPref; + private ListPreference panicAppPref; + private CheckBoxPreference purgePref; @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.panic_preferences); + + pm = getActivity().getPackageManager(); + + lockPref = (CheckBoxPreference) findPreference("pref_key_lock"); + panicAppPref = (ListPreference) findPreference("pref_key_panic_app"); + purgePref = (CheckBoxPreference) findPreference("pref_key_purge"); + + // check for connect/disconnect intents from panic trigger apps + if (PanicResponder.checkForDisconnectIntent(getActivity())) { + LOG.info("Received DISCONNECT intent from Panic Trigger App."); + // the necessary action should have been performed by the check + getActivity().finish(); + } else { + // check if we got a connect intent from a not yet connected app + String packageName = + PanicResponder.getConnectIntentSender(getActivity()); + if (!TextUtils.isEmpty((packageName)) && + !TextUtils.equals(packageName, + PanicResponder + .getTriggerPackageName(getActivity()))) { + + // A new panic trigger app asks us to connect + LOG.info("Received CONNECT intent from new Panic Trigger App."); + + // Show dialog allowing the user to opt-in + showOptInDialog(); + } + } + + ArrayList entries = new ArrayList(); + ArrayList entryValues = new ArrayList(); + entries.add(0, getString(R.string.panic_app_setting_none)); + entryValues.add(0, Panic.PACKAGE_NAME_NONE); + + for (ResolveInfo resolveInfo : PanicResponder.resolveTriggerApps(pm)) { + if (resolveInfo.activityInfo == null) + continue; + entries.add(resolveInfo.activityInfo.loadLabel(pm)); + entryValues.add(resolveInfo.activityInfo.packageName); + } + + panicAppPref.setEntries( + entries.toArray(new CharSequence[entries.size()])); + panicAppPref.setEntryValues( + entryValues.toArray(new CharSequence[entryValues.size()])); + panicAppPref.setDefaultValue(Panic.PACKAGE_NAME_NONE); + + panicAppPref.setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, + Object newValue) { + String packageName = (String) newValue; + PanicResponder.setTriggerPackageName(getActivity(), + packageName); + showPanicApp(packageName); + + if (packageName.equals(Panic.PACKAGE_NAME_NONE)) { + purgePref.setChecked(false); + purgePref.setEnabled(false); + getActivity().setResult(Activity.RESULT_CANCELED); + } else { + purgePref.setEnabled(true); + } + + return true; + } + }); + + if (entries.size() <= 1) { + panicAppPref.setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick( + Preference preference) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse( + "market://details?id=info.guardianproject.ripple")); + getActivity().startActivity(intent); + return true; + } + }); + } } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + showPanicApp(PanicResponder.getTriggerPackageName(getActivity())); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + // enable locking if purging gets enabled + if (key.equals("pref_key_purge") + && sharedPreferences.getBoolean("pref_key_purge", false)) { + lockPref.setChecked(true); + } + // disable purging if locking gets disabled + if (key.equals("pref_key_lock") + && !sharedPreferences.getBoolean("pref_key_lock", true) + && sharedPreferences.getBoolean("pref_key_purge", false)) { + purgePref.setChecked(false); + } + } + + private void showPanicApp(String triggerPackageName) { + if (TextUtils.isEmpty(triggerPackageName) + || triggerPackageName.equals(Panic.PACKAGE_NAME_NONE)) { + // no panic app set + panicAppPref.setValue(Panic.PACKAGE_NAME_NONE); + panicAppPref + .setSummary(getString(R.string.panic_app_setting_summary)); + panicAppPref.setIcon( + android.R.drawable.ic_menu_close_clear_cancel); + purgePref.setEnabled(false); + } else { + // display connected panic app + try { + panicAppPref.setValue(triggerPackageName); + panicAppPref.setSummary(pm.getApplicationLabel( + pm.getApplicationInfo(triggerPackageName, 0))); + panicAppPref.setIcon( + pm.getApplicationIcon(triggerPackageName)); + purgePref.setEnabled(true); + } catch (PackageManager.NameNotFoundException e) { + // revert back to no app, just to be safe + PanicResponder.setTriggerPackageName(getActivity(), + Panic.PACKAGE_NAME_NONE); + showPanicApp(Panic.PACKAGE_NAME_NONE); + } + } + } + + private void showOptInDialog() { + DialogInterface.OnClickListener okListener = + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + PanicResponder.setTriggerPackageName(getActivity()); + showPanicApp(PanicResponder + .getTriggerPackageName(getActivity())); + getActivity().setResult(Activity.RESULT_OK); + } + }; + DialogInterface.OnClickListener cancelListener = + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } + }; + + AlertDialog.Builder builder = + new AlertDialog.Builder(getContext()); + builder.setTitle( + getString(R.string.dialog_title_connect_panic_app)); + + CharSequence app = getString(R.string.unknown_app); + String packageName = getCallingPackageName(); + if (packageName != null) { + try { + app = pm.getApplicationLabel( + pm.getApplicationInfo(packageName, 0)); + } catch (PackageManager.NameNotFoundException e) { + LOG.warning(e.toString()); + } + } + + String text = String.format( + getString(R.string.dialog_message_connect_panic_app), app); + builder.setMessage(text); + builder.setPositiveButton(android.R.string.ok, okListener); + builder.setNegativeButton(android.R.string.cancel, cancelListener); + builder.show(); + } + + private String getCallingPackageName() { + ComponentName componentName = getActivity().getCallingActivity(); + String packageName = null; + if (componentName != null) { + packageName = componentName.getPackageName(); + } + return packageName; + } + } diff --git a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java index df758e295..1643767af 100644 --- a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java +++ b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java @@ -7,25 +7,78 @@ import android.os.Bundle; import android.support.v7.preference.PreferenceManager; import org.briarproject.android.BriarActivity; +import org.briarproject.api.db.DatabaseConfig; +import org.briarproject.util.FileUtils; +import org.iilab.IilabEngineeringRSA2048Pin; import java.util.logging.Logger; +import javax.inject.Inject; + +import info.guardianproject.GuardianProjectRSA4096; +import info.guardianproject.panic.Panic; +import info.guardianproject.panic.PanicResponder; +import info.guardianproject.trustedintents.TrustedIntents; + public class PanicResponderActivity extends BriarActivity { private static final Logger LOG = Logger.getLogger(PanicResponderActivity.class.getName()); + @Inject private DatabaseConfig databaseConfig; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - SharedPreferences sharedPref = PreferenceManager - .getDefaultSharedPreferences(this); + TrustedIntents trustedIntents = TrustedIntents.get(this); + // Guardian Project Ripple + trustedIntents.addTrustedSigner(GuardianProjectRSA4096.class); + // Amnesty International's Panic Button, made by iilab.org + trustedIntents.addTrustedSigner(IilabEngineeringRSA2048Pin.class); - Intent intent = getIntent(); - if (intent != null && sharedPref.getBoolean("pref_key_lock", true)) { - LOG.info("Signing out..."); - signOut(true); + Intent intent = trustedIntents.getIntentFromTrustedSender(this); + if (intent != null) { + // received intent from trusted app + if (Panic.isTriggerIntent(intent)) { + SharedPreferences sharedPref = PreferenceManager + .getDefaultSharedPreferences(this); + + LOG.info("Received Panic Trigger..."); + + if (PanicResponder.receivedTriggerFromConnectedApp(this)) { + LOG.info("Panic Trigger came from connected app."); + LOG.info("Performing destructive responses..."); + + // Performing destructive panic responses + if (sharedPref.getBoolean("pref_key_purge", false)) { + LOG.info("Purging all data..."); + deleteAllData(); + } + // still sign out if enabled + else if (sharedPref.getBoolean("pref_key_lock", true)) { + LOG.info("Signing out..."); + signOut(true); + } + + // TODO add other panic behavior such as: + // * send a pre-defined message to certain contacts (#212) + // * uninstall the app (#211) + + } + // Performing non-destructive default panic response + else if (sharedPref.getBoolean("pref_key_lock", true)) { + LOG.info("Signing out..."); + signOut(true); + } + } + } + // received intent from non-trusted app + else { + intent = getIntent(); + if (intent != null && Panic.isTriggerIntent(intent)) { + LOG.info("Signing out..."); + signOut(true); + } } if (Build.VERSION.SDK_INT >= 21) { @@ -34,4 +87,23 @@ public class PanicResponderActivity extends BriarActivity { finish(); } } + + private void deleteAllData() { + runOnDbThread(new Runnable() { + @Override + public void run() { + // TODO somehow delete/shred the database more thoroughly + FileUtils + .deleteFileOrDir( + databaseConfig.getDatabaseDirectory()); + clearSharedPrefs(); + PanicResponder.deleteAllAppData(PanicResponderActivity.this); + + // nothing left to do after everything is deleted, + // so still sign out + LOG.info("Signing out..."); + signOut(true); + } + }); + } } \ No newline at end of file