diff --git a/app/build.gradle b/app/build.gradle index 81c8ef64b..c453ff178 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,7 @@ dependencies { compile 'eu.chainfire:libsuperuser:1.0.0.201602271131' compile 'cc.mvdan.accesspoint:library:0.2.0' compile 'info.guardianproject.netcipher:netcipher:2.0.0-alpha1' + compile "info.guardianproject.panic:panic:0.5" compile 'commons-io:commons-io:2.5' compile 'commons-net:commons-net:3.5' compile 'org.openhab.jmdns:jmdns:3.4.2' @@ -142,6 +143,7 @@ if (!hasProperty('sourceDeps')) { 'com.nostra13.universalimageloader:universal-image-loader:dbd5197ffec3a8317533190870a7c00ff3750dd6a31241448c6a5522d51b65b4', 'eu.chainfire:libsuperuser:018344ff19ee94d252c14b4a503ee8b519184db473a5af83513f5837c413b128', 'info.guardianproject.netcipher:netcipher:eeeb5d0d95ccfe176b4296cbd71a9a24c6efb0bab5c4025a8c6bc36abdddfc75', + 'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2', 'io.reactivex:rxandroid:35c1a90f8c1f499db3c1f3d608e1f191ac8afddb10c02dd91ef04c03a0a4bcda', 'io.reactivex:rxjava:2c162afd78eba217cdfee78b60e85d3bfb667db61e12bc95e3cf2ddc5beeadf6', 'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce', diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c8cc54d30..76086e59d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -532,6 +532,33 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 921464760..fa859665e 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -2,6 +2,7 @@ package org.fdroid.fdroid; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; import android.os.Build; import android.preference.PreferenceManager; import android.util.Log; @@ -38,8 +39,9 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh .putString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName()) .apply(); } - defaultPreventScreenshots = - context.getResources().getBoolean(R.bool.defaultPreventScreenshots); + Resources res = context.getResources(); + defaultPreventScreenshots = res.getBoolean(R.bool.defaultPreventScreenshots); + defaultPanicExit = res.getBoolean(R.bool.defaultPanicExit); } public static final String PREF_UPD_INTERVAL = "updateInterval"; @@ -69,6 +71,8 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh public static final String PREF_POST_PRIVILEGED_INSTALL = "postPrivilegedInstall"; public static final String PREF_TRIED_EMPTY_UPDATE = "triedEmptyUpdate"; public static final String PREF_PREVENT_SCREENSHOTS = "preventScreenshots"; + public static final String PREF_PANIC_EXIT = "pref_panic_exit"; + public static final String PREF_PANIC_HIDE = "pref_panic_hide"; private static final boolean DEFAULT_ROOTED = true; private static final boolean DEFAULT_HIDE_ANTI_FEATURE_APPS = false; @@ -89,6 +93,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh private static final boolean DEFAULT_FORCE_OLD_INDEX = false; private static final boolean DEFAULT_POST_PRIVILEGED_INSTALL = false; private final boolean defaultPreventScreenshots; + private final boolean defaultPanicExit; public enum Theme { light, @@ -322,6 +327,14 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh return preferences.getBoolean(PREF_PREVENT_SCREENSHOTS, defaultPreventScreenshots); } + public boolean panicExit() { + return preferences.getBoolean(PREF_PANIC_EXIT, defaultPanicExit); + } + + public boolean panicHide() { + return preferences.getBoolean(PREF_PANIC_HIDE, false); + } + /** * This is cached as it is called several times inside app list adapters. * Providing it here means the shared preferences file only needs to be diff --git a/app/src/main/java/org/fdroid/fdroid/views/panic/ExitActivity.java b/app/src/main/java/org/fdroid/fdroid/views/panic/ExitActivity.java new file mode 100644 index 000000000..0822aec03 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/panic/ExitActivity.java @@ -0,0 +1,41 @@ +package org.fdroid.fdroid.views.panic; + +import android.support.v7.app.AppCompatActivity; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +public class ExitActivity extends AppCompatActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + + System.exit(0); + } + + public static void exitAndRemoveFromRecentApps(final AppCompatActivity activity) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Intent intent = new Intent(activity, ExitActivity.class); + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NO_ANIMATION); + + activity.startActivity(intent); + } + }); + + } + +} diff --git a/app/src/main/java/org/fdroid/fdroid/views/panic/PanicPreferencesActivity.java b/app/src/main/java/org/fdroid/fdroid/views/panic/PanicPreferencesActivity.java new file mode 100644 index 000000000..b8016263e --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/panic/PanicPreferencesActivity.java @@ -0,0 +1,38 @@ +package org.fdroid.fdroid.views.panic; + +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; + +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.R; + +public class PanicPreferencesActivity extends AppCompatActivity { + + @Override + public void onCreate(Bundle bundle) { + ((FDroidApp) getApplication()).applyTheme(this); + super.onCreate(bundle); + setContentView(R.layout.activity_panic_settings); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayShowHomeEnabled(true); + ab.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/org/fdroid/fdroid/views/panic/PanicPreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/panic/PanicPreferencesFragment.java new file mode 100644 index 000000000..9b69258dd --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/panic/PanicPreferencesFragment.java @@ -0,0 +1,220 @@ +package org.fdroid.fdroid.views.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.Build; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.support.annotation.Nullable; +import android.support.v4.preference.PreferenceFragment; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; + +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.R; + +import java.util.ArrayList; + +import info.guardianproject.panic.Panic; +import info.guardianproject.panic.PanicResponder; + +public class PanicPreferencesFragment extends PreferenceFragment implements SharedPreferences + .OnSharedPreferenceChangeListener { + + private static final String PREF_EXIT = Preferences.PREF_PANIC_EXIT; + private static final String PREF_APP = "pref_panic_app"; + private static final String PREF_HIDE = Preferences.PREF_PANIC_HIDE; + + private PackageManager pm; + private ListPreference prefApp; + private CheckBoxPreference prefExit; + private CheckBoxPreference prefHide; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences_panic); + + pm = getActivity().getPackageManager(); + prefExit = (CheckBoxPreference) findPreference(PREF_EXIT); + prefApp = (ListPreference) findPreference(PREF_APP); + prefHide = (CheckBoxPreference) findPreference(PREF_HIDE); + prefHide.setTitle(getString(R.string.panic_hide_title, getString(R.string.app_name))); + + if (PanicResponder.checkForDisconnectIntent(getActivity())) { + // the necessary action should have been performed by the check already + getActivity().finish(); + return; + } + String connectIntentSender = PanicResponder.getConnectIntentSender(getActivity()); + // if there's a connecting app and it is not the old one + if (!TextUtils.isEmpty(connectIntentSender) && !TextUtils.equals(connectIntentSender, PanicResponder + .getTriggerPackageName(getActivity()))) { + // Show dialog allowing the user to opt-in + showOptInDialog(); + } + + prefApp.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String packageName = (String) newValue; + PanicResponder.setTriggerPackageName(getActivity(), packageName); + if (packageName.equals(Panic.PACKAGE_NAME_NONE)) { + prefHide.setChecked(false); + prefHide.setEnabled(false); + getActivity().setResult(Activity.RESULT_CANCELED); + } else { + prefHide.setEnabled(true); + } + showPanicApp(packageName); + return true; + } + }); + + // TODO implement app hiding + PreferenceCategory category = (PreferenceCategory) findPreference("pref_panic_destructive_actions"); + category.removePreference(prefHide); + } + + @Override + public void onStart() { + super.onStart(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + showPanicApp(PanicResponder.getTriggerPackageName(getActivity())); + } + + @Override + public void onStop() { + super.onStop(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + // enable "exit" if "hiding" gets enabled + if (key.equals(PREF_HIDE) && sharedPreferences.getBoolean(PREF_HIDE, false)) { + prefExit.setChecked(true); + } + // disable "hiding" if "exit" gets disabled + if (key.equals(PREF_EXIT) && !sharedPreferences.getBoolean(PREF_EXIT, true)) { + prefHide.setChecked(false); + } + } + + private void showPanicApp(String packageName) { + // Fill list of available panic apps + 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); + } + + prefApp.setEntries(entries.toArray(new CharSequence[entries.size()])); + prefApp.setEntryValues(entryValues.toArray(new CharSequence[entryValues.size()])); + prefApp.setDefaultValue(Panic.PACKAGE_NAME_NONE); + + if (entries.size() <= 1) { + // bring the user to Ripple if no other panic apps are available + prefApp.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")); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(intent); + } + return true; + } + }); + } + + if (TextUtils.isEmpty(packageName) || packageName.equals(Panic.PACKAGE_NAME_NONE)) { + // no panic app set + prefApp.setValue(Panic.PACKAGE_NAME_NONE); + prefApp.setSummary(getString(R.string.panic_app_setting_summary)); + if (Build.VERSION.SDK_INT >= 11) { + prefApp.setIcon(null); // otherwise re-setting view resource doesn't work + prefApp.setIcon(R.drawable.ic_cancel); + } + // disable destructive panic actions + prefHide.setEnabled(false); + } else { + // try to display connected panic app + try { + prefApp.setValue(packageName); + prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0))); + if (Build.VERSION.SDK_INT >= 11) { + prefApp.setIcon(pm.getApplicationIcon(packageName)); + } + prefHide.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 dialogInterface, int i) { + PanicResponder.setTriggerPackageName(getActivity()); + showPanicApp(PanicResponder.getTriggerPackageName(getActivity())); + getActivity().setResult(Activity.RESULT_OK); + } + }; + DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(getString(R.string.panic_app_dialog_title)); + + CharSequence app = getString(R.string.panic_app_unknown_app); + String packageName = getCallingPackageName(); + if (packageName != null) { + try { + app = pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + } + + String text = String.format(getString(R.string.panic_app_dialog_message), app); + builder.setMessage(text); + builder.setNegativeButton(R.string.allow, okListener); + builder.setPositiveButton(R.string.cancel, cancelListener); + builder.show(); + } + + @Nullable + private String getCallingPackageName() { + ComponentName componentName = getActivity().getCallingActivity(); + String packageName = null; + if (componentName != null) { + packageName = componentName.getPackageName(); + } + return packageName; + } + +} diff --git a/app/src/main/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java b/app/src/main/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java new file mode 100644 index 000000000..b2275fdca --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java @@ -0,0 +1,53 @@ +package org.fdroid.fdroid.views.panic; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import org.fdroid.fdroid.Preferences; + +import info.guardianproject.panic.Panic; +import info.guardianproject.panic.PanicResponder; + +public class PanicResponderActivity extends AppCompatActivity { + + private static final String TAG = PanicResponderActivity.class.getSimpleName(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent == null || !Panic.isTriggerIntent(intent)) { + finish(); + return; + } + + // received intent from panic app + Log.i(TAG, "Received Panic Trigger..."); + + Preferences preferences = Preferences.get(); + + if (PanicResponder.receivedTriggerFromConnectedApp(this)) { + Log.i(TAG, "Panic Trigger came from connected app"); + + // Performing destructive panic responses + if (preferences.panicHide()) { + Log.i(TAG, "Hiding app..."); + // TODO + } + } + + // exit and clear, if not deactivated + if (preferences.panicExit()) { + ExitActivity.exitAndRemoveFromRecentApps(this); + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } + } + finish(); + } + +} diff --git a/app/src/main/res/layout/activity_panic_settings.xml b/app/src/main/res/layout/activity_panic_settings.xml new file mode 100644 index 000000000..3469eea44 --- /dev/null +++ b/app/src/main/res/layout/activity_panic_settings.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 16849e588..e24ed6d35 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -16,5 +16,6 @@ false + true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1f93bc56d..3f380a2e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -244,6 +244,22 @@ This often occurs with apps installed via Google Play or other sources, if they Prevent Screenshots Blocks screenshots from being taken and hides app content from recent apps screen + Panic Button App + an unknown app + No app has been set + None + Confirm Panic App + Are you sure that you want to allow %1$s to trigger destructive panic button actions? + Allow + + Panic Button Settings + Actions to be taken in case of emergency + Exit App + App will be closed + Destructive Actions + Hide %s + App will hide itself +