summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFan Zhang <zhfan@google.com>2020-12-14 22:47:44 -0800
committerFan Zhang <zhfan@google.com>2020-12-15 16:20:17 -0800
commit99183fe7d4a5d4329f50744f04faa70feec9088d (patch)
tree52e355d3520ede8fe80481c95f3ea56f443e90ed /src
parent7468455aa5fdf640faab38847f2ed033cfa18af9 (diff)
downloadEmergencyInfo-99183fe7d4a5d4329f50744f04faa70feec9088d.tar.gz
Add service to keep countdown when ui is backgrounded
This change introduced a few new components to make countdown work in the background. - EmergencyActionForegroundService: posts a notification with remaining seconds. * It also starts/stops sound associated with the notification - EmergencyActionBroadcastReceiver: handles 2 action * calling emergency number and * cancelling the count down notification When UI is closed, it start a foreground service to take over the count down. Then the service sends an notification immediately, and schedules a broadcast when countdown reaches 0. This broadcast will trigger emergency calling. The notification has a cancel button, which also is a broadcast to cancel ongoing foreground service. When foreground service stops, the vibration, sound and notification all stops with it. Bug: 172075832 Test: manual Change-Id: I04f33848b9532534f1551f20320683331207aa7e
Diffstat (limited to 'src')
-rw-r--r--src/com/android/emergency/action/EmergencyActionFragment.java9
-rw-r--r--src/com/android/emergency/action/broadcast/EmergencyActionBroadcastReceiver.java95
-rw-r--r--src/com/android/emergency/action/service/EmergencyActionForegroundService.java184
3 files changed, 287 insertions, 1 deletions
diff --git a/src/com/android/emergency/action/EmergencyActionFragment.java b/src/com/android/emergency/action/EmergencyActionFragment.java
index f0b36d4e..e8ea31d8 100644
--- a/src/com/android/emergency/action/EmergencyActionFragment.java
+++ b/src/com/android/emergency/action/EmergencyActionFragment.java
@@ -42,6 +42,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.emergency.R;
+import com.android.emergency.action.service.EmergencyActionForegroundService;
import com.android.emergency.widgets.countdown.CountDownAnimationView;
import com.android.emergency.widgets.slider.OnSlideCompleteListener;
import com.android.emergency.widgets.slider.SliderView;
@@ -68,6 +69,7 @@ public class EmergencyActionFragment extends Fragment implements OnSlideComplete
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ EmergencyActionForegroundService.stopService(context);
mAudioManager = context.getSystemService(AudioManager.class);
mEmergencyNumberUtils = new EmergencyNumberUtils(context);
mTelecomManager = context.getSystemService(TelecomManager.class);
@@ -134,13 +136,18 @@ public class EmergencyActionFragment extends Fragment implements OnSlideComplete
Log.d(TAG,
"Emergency countdown UI dismissed without being cancelled/finished, "
+ "continuing countdown in background");
- // TODO(b/172075832): Continue countdown in a foreground service.
+
+ Context context = getContext();
+ context.startService(
+ EmergencyActionForegroundService.newStartCountdownIntent(context,
+ mCountDownMillisLeft));
}
}
@Override
public void onSlideComplete() {
mCountdownCancelled = true;
+ EmergencyActionForegroundService.stopService(getActivity());
getActivity().finish();
}
diff --git a/src/com/android/emergency/action/broadcast/EmergencyActionBroadcastReceiver.java b/src/com/android/emergency/action/broadcast/EmergencyActionBroadcastReceiver.java
new file mode 100644
index 00000000..6c1c1b27
--- /dev/null
+++ b/src/com/android/emergency/action/broadcast/EmergencyActionBroadcastReceiver.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.emergency.action.broadcast;
+
+import static android.telecom.TelecomManager.EXTRA_CALL_SOURCE;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+import com.android.emergency.action.service.EmergencyActionForegroundService;
+import com.android.settingslib.emergencynumber.EmergencyNumberUtils;
+
+/**
+ * Broadcast receiver to handle actions for emergency gesture foreground service and notification
+ */
+public class EmergencyActionBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "EmergencyActionRcvr";
+
+
+ private static final String ACTION_START_EMERGENCY_CALL =
+ "com.android.emergency.broadcast.MAKE_EMERGENCY_CALL";
+ private static final String ACTION_CANCEL_COUNTDOWN =
+ "com.android.emergency.broadcast.CANCEL_EMERGENCY_COUNTDOWN";
+
+ public static PendingIntent newCallEmergencyPendingIntent(Context context) {
+ return PendingIntent.getBroadcast(context, 0,
+ new Intent(ACTION_START_EMERGENCY_CALL).setClass(context,
+ EmergencyActionBroadcastReceiver.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ public static PendingIntent newCancelCountdownPendingIntent(Context context) {
+ return PendingIntent.getBroadcast(context, 0,
+ new Intent(ACTION_CANCEL_COUNTDOWN).setClass(context,
+ EmergencyActionBroadcastReceiver.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
+ String action = intent.getAction();
+ switch (action) {
+ case ACTION_START_EMERGENCY_CALL:
+ Log.i(TAG, "Starting to call emergency number");
+ placeEmergencyCall(context);
+ // Intentionally fall through to cancel service
+ case ACTION_CANCEL_COUNTDOWN:
+ Log.i(TAG, "Cancelling scheduled emergency calls and foreground service");
+ alarmManager.cancel(newCallEmergencyPendingIntent(context));
+ EmergencyActionForegroundService.stopService(context);
+ break;
+ default:
+ Log.w(TAG, "Unknown action received, skipping: " + action);
+ }
+ }
+
+ private void placeEmergencyCall(Context context) {
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ Log.i(TAG, "Telephony is not supported, skipping.");
+ return;
+ }
+ Bundle extras = new Bundle();
+ extras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, true);
+ extras.putInt(EXTRA_CALL_SOURCE, TelecomManager.CALL_SOURCE_EMERGENCY_SHORTCUT);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ EmergencyNumberUtils emergencyNumberUtils = new EmergencyNumberUtils(context);
+ telecomManager.placeCall(
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, emergencyNumberUtils.getPoliceNumber(),
+ /* fragment= */ null), extras);
+ }
+}
diff --git a/src/com/android/emergency/action/service/EmergencyActionForegroundService.java b/src/com/android/emergency/action/service/EmergencyActionForegroundService.java
new file mode 100644
index 00000000..dac8550e
--- /dev/null
+++ b/src/com/android/emergency/action/service/EmergencyActionForegroundService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.emergency.action.service;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.emergency.R;
+import com.android.emergency.action.broadcast.EmergencyActionBroadcastReceiver;
+import com.android.settingslib.emergencynumber.EmergencyNumberUtils;
+
+/**
+ * A service that counts down for emergency gesture.
+ */
+public class EmergencyActionForegroundService extends Service {
+ private static final String TAG = "EmergencyActionSvc";
+ /** The notification that current service should be started with. */
+ private static final String SERVICE_EXTRA_NOTIFICATION = "service.extra.notification";
+ /** The remaining time in milliseconds before taking emergency action */
+ private static final String SERVICE_EXTRA_REMAINING_TIME_MS = "service.extra.remaining_time_ms";
+ /** Random unique number for the notification */
+ private static final int COUNT_DOWN_NOTIFICATION_ID = 0x112;
+
+ private TelecomManager mTelecomManager;
+ private Vibrator mVibrator;
+ private EmergencyNumberUtils mEmergencyNumberUtils;
+ private NotificationManager mNotificationManager;
+
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ PackageManager pm = getPackageManager();
+ mVibrator = getSystemService(Vibrator.class);
+ if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mTelecomManager = getSystemService(TelecomManager.class);
+ mEmergencyNumberUtils = new EmergencyNumberUtils(this);
+ }
+ mNotificationManager = getSystemService(NotificationManager.class);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "Service started");
+ if (mTelecomManager == null || mEmergencyNumberUtils == null) {
+ Log.d(TAG, "Device does not have telephony support, nothing to do");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ long remainingTimeMs = intent.getLongExtra(SERVICE_EXTRA_REMAINING_TIME_MS, -1);
+ if (remainingTimeMs <= 0) {
+ Log.d(TAG, "Invalid remaining countdown time, nothing to do");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ mNotificationManager.createNotificationChannel(buildNotificationChannel(this));
+ Notification notification = intent.getParcelableExtra(SERVICE_EXTRA_NOTIFICATION);
+
+ // Immediately show notification And now put the service in foreground mode
+ startForeground(COUNT_DOWN_NOTIFICATION_ID, notification);
+ scheduleEmergencyCallBroadcast(remainingTimeMs);
+ // vibration
+ // TODO(b/175401642): Use correct vibrate pattern
+ mVibrator.vibrate(
+ VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
+ // TODO(b/172075832): sound
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ // Take notification down
+ mNotificationManager.cancel(COUNT_DOWN_NOTIFICATION_ID);
+ // TODO(b/172075832): Stop sound
+ // Stop vibrate
+ mVibrator.cancel();
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Build {@link Intent} that launches foreground service for emergency gesture's countdown
+ * action
+ */
+ public static Intent newStartCountdownIntent(
+ Context context, long remainingTimeMs) {
+ return new Intent(context, EmergencyActionForegroundService.class)
+ .putExtra(SERVICE_EXTRA_REMAINING_TIME_MS, remainingTimeMs)
+ .putExtra(SERVICE_EXTRA_NOTIFICATION,
+ buildCountDownNotification(context, remainingTimeMs));
+ }
+
+ /** End all work in this service and remove the foreground notification. */
+ public static void stopService(Context context) {
+ context.stopService(new Intent(context, EmergencyActionForegroundService.class));
+ }
+
+ /**
+ * Creates a {@link NotificationChannel} object for emergency action notifications.
+ *
+ * <p/> Note this does not create notification channel in the system.
+ */
+ private static NotificationChannel buildNotificationChannel(Context context) {
+ NotificationChannel channel = new NotificationChannel("EmergencyGesture",
+ context.getString(R.string.emergency_action_title), IMPORTANCE_HIGH);
+ return channel;
+ }
+
+ private static Notification buildCountDownNotification(Context context, long remainingTimeMs) {
+ NotificationChannel channel = buildNotificationChannel(context);
+ EmergencyNumberUtils emergencyNumberUtils = new EmergencyNumberUtils(context);
+ long targetTimeMs = SystemClock.elapsedRealtime() + remainingTimeMs;
+ // TODO(b/172075832): Make UI prettier
+ RemoteViews contentView =
+ new RemoteViews(context.getPackageName(),
+ R.layout.emergency_action_count_down_notification);
+ contentView.setTextViewText(R.id.notification_text,
+ context.getString(R.string.emergency_action_subtitle,
+ emergencyNumberUtils.getPoliceNumber()));
+ contentView.setChronometerCountDown(R.id.chronometer, true);
+ contentView.setChronometer(
+ R.id.chronometer,
+ targetTimeMs,
+ /* format= */ null,
+ /* started= */ true);
+ return new Notification.Builder(context, channel.getId())
+ .setSmallIcon(R.drawable.ic_launcher_settings)
+ .setStyle(new Notification.DecoratedCustomViewStyle())
+ .setAutoCancel(false)
+ .setOngoing(true)
+ // This is set to make sure that device doesn't vibrate twice when client
+ // attempts to post currently displayed notification again
+ .setOnlyAlertOnce(true)
+ .setCategory(Notification.CATEGORY_ALARM)
+ .setCustomContentView(contentView)
+ .addAction(new Notification.Action.Builder(null, context.getText(R.string.cancel),
+ EmergencyActionBroadcastReceiver.newCancelCountdownPendingIntent(
+ context)).build())
+ .build();
+ }
+
+ private void scheduleEmergencyCallBroadcast(long remainingTimeMs) {
+ long alarmTimeMs = System.currentTimeMillis() + remainingTimeMs;
+ AlarmManager alarmManager = getSystemService(AlarmManager.class);
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP, alarmTimeMs,
+ EmergencyActionBroadcastReceiver.newCallEmergencyPendingIntent(this));
+ }
+
+}