Earlier this year, we announced the PanicKit Library for Android and Ripple, our basic app for alerts any compatible app that you are in an emergency situation. Rather than build a solitary, enclosed “panic button” app that only can provide a specific set of functionality, we decided, as we often do, to build a framework, and encourage others to participate. Since then, we’ve had over 10 different apps implement PanicKit responder functionality, including Signal, OpenKeyChain, Umbrella app, StoryMaker and Zom.
It is great to have so many apps implement helpful features for users to react during an emergency situation. This might include sending an emergency message, putting sensitive data behind a password, hiding the app icon, or even wiping data. All of this can be triggered by a simple tap and swipe on the Ripple’s app user interface.
However, we would like to promote PanicKit trigger functionality that goes beyond something a user has to actively do, or at least obviously do. In many emergency scenarios, the user might be unable to actively trigger a panic, because they are unconscious, detained or have had their device taken away. In some cases, the activation may need to be subtle, such typing an incorrect phone number. In others, rapidly pressing a button or shaking the phone, may be safer and easier than unlocking your device and using an app.
a truly panic-inducing situation
PanicKit works by connecting trigger apps with receiver apps. Triggers are what create the alert that there is an emergency or panic situation. Responders receive the alert, and take an appropriate, user configured or default action.
The new PanicKitSamples project demonstrates new possible triggers that could be implemented in an app like Ripple, or any app that wishes to do so. In the “info.guardianproject.fakepanicbutton.triggers” package, you will find the following classes:
BaseTrigger: a base class that handles launching of the “panic intent” from a set of stored preferences to trigger the responders
public static void launchPanicIntent (Context context) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); String email = prefs.getString("email",null); String phone = prefs.getString("phone",null); String subject = prefs.getString("subject","panic message"); String message = prefs.getString("message","i triggered a panic!"); launchIntent(context, email, phone, subject, message); } public static void launchIntent (Context context, String emailAddress, String phoneNumber, String subject, String message) { final PackageManager pm = context.getPackageManager(); final SetreceiverPackageNames = PanicTrigger.getResponderActivities(context); Intent intent = new Intent(Panic.ACTION_TRIGGER);
GeoTrigger: Using the awesome “LOST” open-source geofencing library, this trigger sends a panic if the device moves outside of a pre-defined area (in this sample, it is Times Square NYC)
private void setupGeoFence () { //setup geofence for Times Square area String requestId = "geof1-timesSquare"; double latitude = 40.758896; double longitude = -73.985130; float radius = 0.0001f; Geofence geofence = new Geofence.Builder() .setRequestId(requestId) .setCircularRegion(latitude, longitude, radius) .setExpirationDuration(Geofence.NEVER_EXPIRE) .build(); GeofencingRequest request = new GeofencingRequest.Builder() .addGeofence(geofence) .build();
MediaButtonTrigger: This trigger will notice multiple rapid pushes of a headset mic button or a bluetooth mic call button, and send a trigger.
public class MediaButtonTrigger extends BaseTrigger { private static int mTriggerCount = 0; private final static int TRIGGER_THRESHOLD = 3; private static long mLastTriggerTime = -1; public MediaButtonTrigger(Activity context) { super (context); } @Override public void activateTrigger() { //if a headset button or a bluetooth "call" button is pressed, trigger this IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON); MediaButtonIntentReceiver r = new MediaButtonIntentReceiver(); getContext().registerReceiver(r, filter); } public class MediaButtonIntentReceiver extends BroadcastReceiver { public MediaButtonIntentReceiver() { super(); } @Override public void onReceive(Context context, Intent intent) { KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (event == null) { return; } int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { //check for 3 rapidly pressed key events long triggerTime = new Date().getTime(); //if the trigger is the first one, or happened with a second of the last one, then count it if (mLastTriggerTime == -1 || ((triggerTime - mLastTriggerTime)<1000)) mTriggerCount++; mLastTriggerTime = triggerTime; if (mTriggerCount > TRIGGER_THRESHOLD) { launchPanicIntent(context); mTriggerCount = 0; } } abortBroadcast(); } } }
PhoneNumberTrigger (OutgoingCallReceiver): This trigger monitors phone calls, looking for a pre-defined fake “panic number”.
public class OutgoingCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); if (phoneNumber != null && phoneNumber.equals(PhoneNumberTrigger.PHONE_NUMBER_TRIGGER)) { PhoneNumberTrigger.launchPanicIntent(context); } } }
SuperShakeTrigger: This trigger looks for the phone being rapidly shaken. It could be expanded to wait for a series of shakes within a certain time window to avoid false positives.
//setup shake detection using ShakeDetector library SensorManager sensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); ShakeDetector sd = new ShakeDetector(new ShakeDetector.Listener() { public void hearShake() { //you shook me! launchPanicIntent(getContext()); } }); sd.start(sensorManager);
WifiTrigger: This triggers waits for the user to connect to a specific wifi network (in this sample “Starbucks”). It could also be set to trigger if the devices leaves the wifi network.
NetworkInfo netInfo = intent.getParcelableExtra (WifiManager.EXTRA_NETWORK_INFO); if (ConnectivityManager.TYPE_WIFI == netInfo.getType () && netInfo.isConnected()) { WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); WifiInfo info = wifiManager.getConnectionInfo(); String ssid = info.getSSID(); //Check if I am connected to the "trigger" SSID, and if so send an alert! if (!TextUtils.isEmpty(ssid) && ssid.equals(WIFI_SSID_TRIGGER)) { launchPanicIntent(getContext()); } }
All of these samples are configured to work with the FakePanicButton sample app, which allows you to choose a contact to alert, and set a panic message. That said, these are meant to point in a direction of functionality, and have not been fully debugged or tested on all devices and OS versions.
If you have more ideas on other panic triggers that could be implemented, please share them here. We are also happy to take pull requests or fixes to our sample project, in order to improve on the ideas we have. Finally, we will announce more Panic responder and trigger apps, as they are available in the coming months. We looking forward to the continued growth of our PanicKit ecosystem, though of course, we hope even more for a world where there are less reasons to panic.