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
+