diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index ac262a3c2067cc6abebc478b0e2b751b87305546..e5e660ddd4700c84bbe043260c7aa7478f124280 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -194,6 +194,11 @@ <activity android:name=".android.panic.PanicPreferencesActivity" android:label="@string/panic_setting" > + <intent-filter> + <action android:name="info.guardianproject.panic.action.CONNECT" /> + <action android:name="info.guardianproject.panic.action.DISCONNECT" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> </activity> <activity android:name=".android.panic.PanicResponderActivity" diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 4c3835102e719748bff27bcce6c47a1eab04dc03..b52c0c52177a6818226ee7be439efe4575698443 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -17,6 +17,7 @@ dependencies { compile "com.android.support:preference-v14:23.1.1" compile "com.android.support:design:23.1.1" compile "info.guardianproject.panic:panic:0.5" + compile "info.guardianproject.trustedintents:trustedintents:0.2" } dependencyVerification { @@ -29,6 +30,7 @@ dependencyVerification { 'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82', 'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd', 'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2', + 'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e', ] } diff --git a/briar-android/res/xml/panic_preferences.xml b/briar-android/res/xml/panic_preferences.xml index 3b40f0e3dfed14616a4015f1374e60ae436493a0..af9bbeca501b78bac1abd2115e0a3279ebd089d3 100644 --- a/briar-android/res/xml/panic_preferences.xml +++ b/briar-android/res/xml/panic_preferences.xml @@ -8,4 +8,22 @@ android:summary="@string/lock_setting_summary" android:defaultValue="true"/> + <PreferenceCategory + android:title="Destructive Actions"> + + <ListPreference + android:key="pref_key_panic_app" + android:title="@string/panic_app_setting_title" + android:summary="@string/panic_app_setting_summary" + android:icon="@android:drawable/ic_menu_close_clear_cancel"/> + + <CheckBoxPreference + android:key="pref_key_purge" + android:title="@string/purge_setting_title" + android:summary="@string/purge_setting_summary" + android:enabled="false" + android:defaultValue="false"/> + + </PreferenceCategory> + </PreferenceScreen> \ 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 7fd096213c379dc4f1efeb28502a46d746092da0..3ee8915c25d976609029bbdc189ffa64eea406c3 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<CharSequence> entries = new ArrayList<CharSequence>(); + ArrayList<CharSequence> entryValues = new ArrayList<CharSequence>(); + 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 df758e295e1065ce0bd3ae2566a1c13e04a6ccd5..1643767af005cd0d3332e7fb5898c02b93375e64 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 = 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); + } - Intent intent = getIntent(); - if (intent != null && 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