diff --git a/app/build.gradle b/app/build.gradle
index 5fdd539f..5a7dd079 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,6 +27,7 @@ dependencies {
compile 'com.google.code.gson:gson:2.3'
compile 'com.android.support:support-v4:23.1.+'
compile 'com.android.support:preference-v7:23.1.0'
+ compile 'info.guardianproject.panic:panic:0.5'
testCompile "org.robolectric:robolectric:3.0"
testCompile "org.robolectric:shadows-support-v4:3.0"
testCompile 'junit:junit:4.12'
@@ -47,6 +48,7 @@ dependencyVerification {
'com.android.support:appcompat-v7:b5783b390d1440769c9b8a7b42290523a4ff058ef2d3fb90c983973934ca115b',
'com.android.support:recyclerview-v7:464ec6e5004400a4fe0310f35343da9e5a2912386a3606db3585f7cf987c444a',
'com.crashlytics.sdk.android:answers:5af101ef6b58a26dd32cfc13f53c63c33fb2fdcdf6990241eca22e7a8c842847',
+ 'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2',
'io.fabric.sdk.android:fabric:15465f60ee6a2bb53dcf84e8dd939ce92dffbffc73bfdd3432b63fa6ee2f7bae',
'com.crashlytics.sdk.android:crashlytics-core:2f35df62420723d4a8e58f58a376fb640d11e1660656a9aa2959bb041b5b1d15',
'com.crashlytics.sdk.android:beta:6d7dce749fd70fa20adcf089aaed5d52f9e8e2a08f6666336e90a7555cee9718',
diff --git a/app/src/main/assets/mobile_en.json b/app/src/main/assets/mobile_en.json
index 3384501a..ffa62dc9 100644
--- a/app/src/main/assets/mobile_en.json
+++ b/app/src/main/assets/mobile_en.json
@@ -432,6 +432,14 @@
],
"content": "
Security Tips
- Think about who can practically assist you - there is no point someone knowing you are in trouble if they are not in a position to help you
- Make a plan with your chosen contacts so that they are prepared to act fast in an emergencyā€¯.
- Be aware of the risks - how might your chosen contact be put at risk?
"
},
+ {
+ "id": "settings-panic-responders",
+ "lang": "en",
+ "type": "interactive",
+ "title": "Panic Responders",
+ "introduction": "When you trigger Panic Button, your chosen apps react. Tap EDIT to see and change that app's behaviors.",
+ "component": "panic-responders"
+ },
{
"id": "settings-code",
"lang": "en",
@@ -506,6 +514,10 @@
"title": "Pin Settings",
"link": "settings-code"
},
+ {
+ "title": "Panic Responders",
+ "link": "settings-panic-responders"
+ },
{
"title": "Advanced Settings",
"link": "settings-advanced"
@@ -896,4 +908,4 @@
}
]
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/iilab/pb/MainActivity.java b/app/src/main/java/org/iilab/pb/MainActivity.java
index 7021af59..28c33479 100644
--- a/app/src/main/java/org/iilab/pb/MainActivity.java
+++ b/app/src/main/java/org/iilab/pb/MainActivity.java
@@ -20,6 +20,7 @@
import org.iilab.pb.fragment.AdvancedSettingsSubScreenFragment;
import org.iilab.pb.fragment.LanguageSettingsFragment;
import org.iilab.pb.fragment.MainSetupAlertFragment;
+import org.iilab.pb.fragment.PanicRespondersFragment;
import org.iilab.pb.fragment.SetupCodeFragment;
import org.iilab.pb.fragment.SetupContactsFragment;
import org.iilab.pb.fragment.SetupMessageFragment;
@@ -36,6 +37,7 @@
import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_CONTACTS;
import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_LANGUAGE;
import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_MESSAGE;
+import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_PANIC_RESPONDERS;
import static org.iilab.pb.common.AppConstants.PAGE_FROM_NOT_IMPLEMENTED;
import static org.iilab.pb.common.AppConstants.PAGE_HOME_NOT_CONFIGURED;
import static org.iilab.pb.common.AppConstants.PAGE_HOME_READY;
@@ -141,6 +143,8 @@ public void onCreate(Bundle savedInstanceState) {
} else {
if (currentPage.getComponent().equals(PAGE_COMPONENT_CONTACTS))
fragment = new SetupContactsFragment().newInstance(pageId, FROM_MAIN_ACTIVITY);
+ else if (currentPage.getComponent().equals(PAGE_COMPONENT_PANIC_RESPONDERS))
+ fragment = new PanicRespondersFragment().newInstance(pageId, FROM_MAIN_ACTIVITY);
else if (currentPage.getComponent().equals(PAGE_COMPONENT_MESSAGE))
fragment = new SetupMessageFragment().newInstance(pageId, FROM_MAIN_ACTIVITY);
else if (currentPage.getComponent().equals(PAGE_COMPONENT_CODE))
diff --git a/app/src/main/java/org/iilab/pb/WizardActivity.java b/app/src/main/java/org/iilab/pb/WizardActivity.java
index b37174db..ecc1022e 100644
--- a/app/src/main/java/org/iilab/pb/WizardActivity.java
+++ b/app/src/main/java/org/iilab/pb/WizardActivity.java
@@ -21,6 +21,7 @@
import org.iilab.pb.common.MyTagHandler;
import org.iilab.pb.data.PBDatabase;
import org.iilab.pb.fragment.LanguageSettingsFragment;
+import org.iilab.pb.fragment.PanicRespondersFragment;
import org.iilab.pb.fragment.SetupCodeFragment;
import org.iilab.pb.fragment.SetupContactsFragment;
import org.iilab.pb.fragment.SetupMessageFragment;
@@ -45,6 +46,7 @@
import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_DISGUISE_TEST_UNLOCK;
import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_LANGUAGE;
import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_MESSAGE;
+import static org.iilab.pb.common.AppConstants.PAGE_COMPONENT_PANIC_RESPONDERS;
import static org.iilab.pb.common.AppConstants.PAGE_FROM_NOT_IMPLEMENTED;
import static org.iilab.pb.common.AppConstants.PAGE_HOME_NOT_CONFIGURED;
import static org.iilab.pb.common.AppConstants.PAGE_HOME_NOT_CONFIGURED_ALARM;
@@ -147,6 +149,8 @@ public void onCreate(Bundle savedInstanceState) {
} else { // type = interactive
if (currentPage.getComponent().equals(PAGE_COMPONENT_CONTACTS))
fragment = new SetupContactsFragment().newInstance(pageId, FROM_WIZARD_ACTIVITY);
+ else if (currentPage.getComponent().equals(PAGE_COMPONENT_PANIC_RESPONDERS))
+ fragment = new PanicRespondersFragment().newInstance(pageId, FROM_WIZARD_ACTIVITY);
else if (currentPage.getComponent().equals(PAGE_COMPONENT_MESSAGE))
fragment = new SetupMessageFragment().newInstance(pageId, FROM_WIZARD_ACTIVITY);
else if (currentPage.getComponent().equals(PAGE_COMPONENT_CODE))
diff --git a/app/src/main/java/org/iilab/pb/alert/PanicAlert.java b/app/src/main/java/org/iilab/pb/alert/PanicAlert.java
index beca46d0..508e6286 100644
--- a/app/src/main/java/org/iilab/pb/alert/PanicAlert.java
+++ b/app/src/main/java/org/iilab/pb/alert/PanicAlert.java
@@ -14,6 +14,8 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import info.guardianproject.panic.PanicTrigger;
+
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
import static org.iilab.pb.common.AppConstants.GPS_MIN_DISTANCE;
@@ -75,6 +77,8 @@ public void run() {
private void activateAlert() {
setAlertActive(context, true);
+ // send panic triggers to responder apps that support it
+ PanicTrigger.sendTrigger(context);
sendFirstAlert();
registerLocationUpdate();
scheduleFutureAlert();
diff --git a/app/src/main/java/org/iilab/pb/common/AppConstants.java b/app/src/main/java/org/iilab/pb/common/AppConstants.java
index ef5f4241..746c67b2 100644
--- a/app/src/main/java/org/iilab/pb/common/AppConstants.java
+++ b/app/src/main/java/org/iilab/pb/common/AppConstants.java
@@ -103,6 +103,7 @@ public class AppConstants {
public static final String PAGE_COMPONENT_CODE = "code";
public static final String PAGE_COMPONENT_ALERT = "alert";
public static final String PAGE_COMPONENT_LANGUAGE = "language";
+ public static final String PAGE_COMPONENT_PANIC_RESPONDERS = "panic-responders";
public static final String PAGE_COMPONENT_ADVANCED_SETTINGS = "advanced";
public static final String PAGE_COMPONENT_ALARM_TEST_HARDWARE = "alarm-test-hardware";
public static final String PAGE_COMPONENT_ALARM_TEST_DISGUISE = "alarm-test-disguise";
diff --git a/app/src/main/java/org/iilab/pb/fragment/PanicRespondersFragment.java b/app/src/main/java/org/iilab/pb/fragment/PanicRespondersFragment.java
new file mode 100644
index 00000000..d9facee2
--- /dev/null
+++ b/app/src/main/java/org/iilab/pb/fragment/PanicRespondersFragment.java
@@ -0,0 +1,262 @@
+package org.iilab.pb.fragment;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import org.iilab.pb.R;
+import org.iilab.pb.adapter.PageItemAdapter;
+import org.iilab.pb.common.ApplicationSettings;
+import org.iilab.pb.data.PBDatabase;
+import org.iilab.pb.model.Page;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import info.guardianproject.panic.Panic;
+import info.guardianproject.panic.PanicTrigger;
+
+public class PanicRespondersFragment extends Fragment {
+ public static final String TAG = "PanicRespondersFragment";
+
+ private static final String PAGE_ID = "page_id";
+ private static final String PARENT_ACTIVITY = "parent_activity";
+ private Activity activity;
+ DisplayMetrics metrics;
+ TextView tvTitle, tvIntro;
+ Page currentPage;
+ PageItemAdapter pageItemAdapter;
+
+ private static final int CONNECT_RESULT = 0x01;
+
+ private String responders[];
+ private Set enabledResponders;
+ private Set respondersThatCanConnect;
+ private ArrayList appLabelList;
+ private ArrayList iconList;
+
+ private String requestPackageName;
+
+ public static PanicRespondersFragment newInstance(String pageId, int parentActivity) {
+ PanicRespondersFragment f = new PanicRespondersFragment();
+ Bundle args = new Bundle();
+ args.putString(PAGE_ID, pageId);
+ args.putInt(PARENT_ACTIVITY, parentActivity);
+ f.setArguments(args);
+ return (f);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_type_interactive_panic_responders, container, false);
+
+ tvTitle = (TextView) view.findViewById(R.id.fragment_title);
+ tvIntro = (TextView) view.findViewById(R.id.fragment_intro);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ enabledResponders = PanicTrigger.getEnabledResponders(activity);
+ respondersThatCanConnect = PanicTrigger.getRespondersThatCanConnect(activity);
+
+ // sort enabled first, then disabled
+ LinkedHashSet a = new LinkedHashSet(enabledResponders);
+ LinkedHashSet b = new LinkedHashSet(PanicTrigger.getAllResponders(activity));
+ b.removeAll(enabledResponders);
+ a.addAll(b);
+ responders = a.toArray(new String[a.size()]);
+
+ PackageManager pm = getActivity().getPackageManager();
+ appLabelList = new ArrayList(responders.length);
+ iconList = new ArrayList(responders.length);
+ for (String packageName : responders) {
+ try {
+ appLabelList.add(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
+ iconList.add(pm.getApplicationIcon(packageName));
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recycler_view);
+ recyclerView.addItemDecoration(new SimpleDividerItemDecoration(activity));
+ recyclerView.setHasFixedSize(true); // does not change, except in onResume()
+ recyclerView.setLayoutManager(new LinearLayoutManager(activity));
+ recyclerView.setAdapter(new RecyclerView.Adapter() {
+ @Override
+ public AppRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ Context context = parent.getContext();
+ LayoutInflater layoutInflater = LayoutInflater.from(context);
+ View view = layoutInflater.inflate(R.layout.responder_row, parent, false);
+ return new AppRowHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(AppRowHolder holder, int position) {
+ String packageName = responders[position];
+ boolean canConnect = respondersThatCanConnect.contains(packageName);
+ holder.setupForApp(
+ packageName,
+ iconList.get(position),
+ appLabelList.get(position),
+ canConnect);
+ }
+
+ @Override
+ public int getItemCount() {
+ return appLabelList.size();
+ }
+ });
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ activity = getActivity();
+ if (activity != null) {
+ metrics = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ String pageId = getArguments().getString(PAGE_ID);
+ String selectedLang = ApplicationSettings.getSelectedLanguage(activity);
+
+ PBDatabase dbInstance = new PBDatabase(activity);
+ dbInstance.open();
+ currentPage = dbInstance.retrievePage(pageId, selectedLang);
+ dbInstance.close();
+
+ tvTitle.setText(currentPage.getTitle());
+
+ if (currentPage.getIntroduction() == null) {
+ tvIntro.setVisibility(View.GONE);
+ } else {
+ tvIntro.setText(currentPage.getIntroduction());
+ }
+
+ pageItemAdapter = new PageItemAdapter(activity, null);
+ pageItemAdapter.setData(currentPage.getItems());
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ List fragments = getChildFragmentManager().getFragments();
+ if (fragments != null) {
+ for (Fragment fragment : fragments) {
+ fragment.onActivityResult(requestCode, resultCode, intent);
+ }
+ }
+ }
+
+ class AppRowHolder extends RecyclerView.ViewHolder {
+
+ private final View.OnClickListener onClickListener;
+ private final Switch onSwitch;
+ private final TextView editableLabel;
+ private final ImageView iconView;
+ private final TextView appLabelView;
+ private String rowPackageName;
+
+ AppRowHolder(final View row) {
+ super(row);
+
+ iconView = (ImageView) row.findViewById(R.id.iconView);
+ appLabelView = (TextView) row.findViewById(R.id.appLabel);
+ editableLabel = (TextView) row.findViewById(R.id.editableLabel);
+ onSwitch = (Switch) row.findViewById(R.id.on_switch);
+ onClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ requestPackageName = rowPackageName;
+ Intent intent = new Intent(Panic.ACTION_CONNECT);
+ intent.setPackage(requestPackageName);
+ startActivityForResult(intent, CONNECT_RESULT);
+ }
+ };
+
+ onSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean enabled) {
+ setEnabled(enabled);
+ if (enabled) {
+ PanicTrigger.enableResponder(activity, rowPackageName);
+ } else {
+ PanicTrigger.disableResponder(activity, rowPackageName);
+ }
+ }
+ });
+ }
+
+ void setEnabled(boolean enabled) {
+ if (enabled) {
+ editableLabel.setVisibility(View.VISIBLE);
+ appLabelView.setEnabled(true);
+ iconView.setEnabled(true);
+ iconView.setColorFilter(null);
+ } else {
+ editableLabel.setVisibility(View.GONE);
+ appLabelView.setEnabled(false);
+ iconView.setEnabled(false);
+ // grey out app icon when disabled
+ ColorMatrix matrix = new ColorMatrix();
+ matrix.setSaturation(0);
+ ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
+ iconView.setColorFilter(filter);
+ }
+ }
+
+ void setupForApp(String packageName, Drawable icon, CharSequence appLabel, boolean editable) {
+ this.rowPackageName = packageName;
+ iconView.setImageDrawable(icon);
+ appLabelView.setText(appLabel);
+ if (editable) {
+ iconView.setOnClickListener(onClickListener);
+ appLabelView.setOnClickListener(onClickListener);
+ editableLabel.setOnClickListener(onClickListener);
+ editableLabel.setText(R.string.edit);
+ editableLabel.setTypeface(null, Typeface.BOLD);
+ if (Build.VERSION.SDK_INT >= 14)
+ editableLabel.setAllCaps(true);
+ } else {
+ iconView.setOnClickListener(null);
+ appLabelView.setOnClickListener(null);
+ editableLabel.setOnClickListener(null);
+ editableLabel.setText(R.string.app_hides);
+ editableLabel.setTypeface(null, Typeface.NORMAL);
+ if (Build.VERSION.SDK_INT >= 14)
+ editableLabel.setAllCaps(false);
+ }
+ boolean enabled = enabledResponders.contains(packageName);
+ if (Build.VERSION.SDK_INT >= 14)
+ onSwitch.setChecked(enabled);
+ setEnabled(enabled);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/iilab/pb/fragment/SimpleDividerItemDecoration.java b/app/src/main/java/org/iilab/pb/fragment/SimpleDividerItemDecoration.java
new file mode 100644
index 00000000..1a153a7f
--- /dev/null
+++ b/app/src/main/java/org/iilab/pb/fragment/SimpleDividerItemDecoration.java
@@ -0,0 +1,36 @@
+package org.iilab.pb.fragment;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import org.iilab.pb.R;
+
+public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {
+ private final Drawable mDivider;
+
+ public SimpleDividerItemDecoration(Context context) {
+ mDivider = context.getResources().getDrawable(R.drawable.responder_line_divider);
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ int left = parent.getPaddingLeft();
+ int right = parent.getWidth() - parent.getPaddingRight();
+
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+ int top = child.getBottom() + params.bottomMargin;
+ int bottom = top + mDivider.getIntrinsicHeight();
+
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/responder_line_divider.xml b/app/src/main/res/drawable/responder_line_divider.xml
new file mode 100644
index 00000000..c4183430
--- /dev/null
+++ b/app/src/main/res/drawable/responder_line_divider.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_type_interactive_panic_responders.xml b/app/src/main/res/layout/fragment_type_interactive_panic_responders.xml
new file mode 100644
index 00000000..9f568b99
--- /dev/null
+++ b/app/src/main/res/layout/fragment_type_interactive_panic_responders.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/responder_row.xml b/app/src/main/res/layout/responder_row.xml
new file mode 100644
index 00000000..e98e2d42
--- /dev/null
+++ b/app/src/main/res/layout/responder_row.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 367a20bb..ab519a79 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,4 +3,5 @@
#fc2d2d
#fcc542
#ff0000ff
+ #ffdddddd
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 093962e6..ae1a6811 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -145,4 +145,7 @@
and then press for confirmation within 3 seconds
<img src="/media/mobile/panic_button_activation_5press_confirm_en.gif" >
<img src="/media/mobile/panic_button_activation.png" >
+
+ Edit
+ App hides when triggered