1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.emergency.action.service; 18 19 import static android.app.NotificationManager.IMPORTANCE_HIGH; 20 21 import android.app.AlarmManager; 22 import android.app.Notification; 23 import android.app.NotificationChannel; 24 import android.app.NotificationManager; 25 import android.app.Service; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.os.IBinder; 30 import android.os.SystemClock; 31 import android.os.VibrationEffect; 32 import android.os.Vibrator; 33 import android.telecom.TelecomManager; 34 import android.util.Log; 35 import android.widget.RemoteViews; 36 37 import com.android.emergency.R; 38 import com.android.emergency.action.broadcast.EmergencyActionBroadcastReceiver; 39 import com.android.emergency.action.sensoryfeedback.EmergencyActionAlarmHelper; 40 import com.android.settingslib.emergencynumber.EmergencyNumberUtils; 41 42 /** 43 * A service that counts down for emergency gesture. 44 */ 45 public class EmergencyActionForegroundService extends Service { 46 private static final String TAG = "EmergencyActionSvc"; 47 /** The notification that current service should be started with. */ 48 private static final String SERVICE_EXTRA_NOTIFICATION = "service.extra.notification"; 49 /** The remaining time in milliseconds before taking emergency action */ 50 private static final String SERVICE_EXTRA_REMAINING_TIME_MS = "service.extra.remaining_time_ms"; 51 /** The alarm volume user setting before triggering this gesture */ 52 private static final String SERVICE_EXTRA_ALARM_VOLUME = "service.extra.alarm_volume"; 53 /** Random unique number for the notification */ 54 private static final int COUNT_DOWN_NOTIFICATION_ID = 0x112; 55 56 private static final long[] TIMINGS = new long[]{200, 20, 20, 20, 20, 100, 20, 600}; 57 private static final int[] AMPLITUDES = new int[]{0, 39, 82, 139, 213, 0, 127, 0}; 58 private static final VibrationEffect VIBRATION_EFFECT = 59 VibrationEffect.createWaveform(TIMINGS, AMPLITUDES, /*repeat=*/ -1); 60 61 private TelecomManager mTelecomManager; 62 private Vibrator mVibrator; 63 private EmergencyNumberUtils mEmergencyNumberUtils; 64 private NotificationManager mNotificationManager; 65 private EmergencyActionAlarmHelper mAlarmHelper; 66 67 68 @Override onCreate()69 public void onCreate() { 70 super.onCreate(); 71 PackageManager pm = getPackageManager(); 72 mVibrator = getSystemService(Vibrator.class); 73 mNotificationManager = getSystemService(NotificationManager.class); 74 if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { 75 mTelecomManager = getSystemService(TelecomManager.class); 76 mEmergencyNumberUtils = new EmergencyNumberUtils(this); 77 mAlarmHelper = new EmergencyActionAlarmHelper(this); 78 } 79 } 80 81 @Override onStartCommand(Intent intent, int flags, int startId)82 public int onStartCommand(Intent intent, int flags, int startId) { 83 Log.d(TAG, "Service started"); 84 if (mTelecomManager == null || mEmergencyNumberUtils == null) { 85 Log.d(TAG, "Device does not have telephony support, nothing to do"); 86 stopSelf(); 87 return START_NOT_STICKY; 88 } 89 long remainingTimeMs = intent.getLongExtra(SERVICE_EXTRA_REMAINING_TIME_MS, -1); 90 if (remainingTimeMs <= 0) { 91 Log.d(TAG, "Invalid remaining countdown time, nothing to do"); 92 stopSelf(); 93 return START_NOT_STICKY; 94 } 95 mNotificationManager.createNotificationChannel(buildNotificationChannel(this)); 96 Notification notification = intent.getParcelableExtra(SERVICE_EXTRA_NOTIFICATION); 97 98 // Immediately show notification And now put the service in foreground mode 99 startForeground(COUNT_DOWN_NOTIFICATION_ID, notification); 100 scheduleEmergencyCallBroadcast(remainingTimeMs); 101 // vibration 102 mVibrator.vibrate(VIBRATION_EFFECT); 103 mAlarmHelper.playWarningSound(); 104 105 return START_NOT_STICKY; 106 } 107 108 @Override onDestroy()109 public void onDestroy() { 110 // Take notification down 111 mNotificationManager.cancel(COUNT_DOWN_NOTIFICATION_ID); 112 // Stop sound 113 mAlarmHelper.stopWarningSound(); 114 // Stop vibrate 115 mVibrator.cancel(); 116 super.onDestroy(); 117 } 118 119 @Override onBind(Intent intent)120 public IBinder onBind(Intent intent) { 121 return null; 122 } 123 124 /** 125 * Build {@link Intent} that launches foreground service for emergency gesture's countdown 126 * action 127 */ newStartCountdownIntent( Context context, long remainingTimeMs, int userSetAlarmVolume)128 public static Intent newStartCountdownIntent( 129 Context context, long remainingTimeMs, int userSetAlarmVolume) { 130 return new Intent(context, EmergencyActionForegroundService.class) 131 .putExtra(SERVICE_EXTRA_REMAINING_TIME_MS, remainingTimeMs) 132 .putExtra(SERVICE_EXTRA_ALARM_VOLUME, userSetAlarmVolume) 133 .putExtra(SERVICE_EXTRA_NOTIFICATION, 134 buildCountDownNotification(context, remainingTimeMs)); 135 } 136 137 /** End all work in this service and remove the foreground notification. */ stopService(Context context)138 public static void stopService(Context context) { 139 context.stopService(new Intent(context, EmergencyActionForegroundService.class)); 140 } 141 142 /** 143 * Creates a {@link NotificationChannel} object for emergency action notifications. 144 * 145 * <p/> Note this does not create notification channel in the system. 146 */ buildNotificationChannel(Context context)147 private static NotificationChannel buildNotificationChannel(Context context) { 148 NotificationChannel channel = new NotificationChannel("EmergencyGesture", 149 context.getString(R.string.emergency_action_title), IMPORTANCE_HIGH); 150 return channel; 151 } 152 buildCountDownNotification(Context context, long remainingTimeMs)153 private static Notification buildCountDownNotification(Context context, long remainingTimeMs) { 154 NotificationChannel channel = buildNotificationChannel(context); 155 EmergencyNumberUtils emergencyNumberUtils = new EmergencyNumberUtils(context); 156 long targetTimeMs = SystemClock.elapsedRealtime() + remainingTimeMs; 157 // TODO(b/172075832): Make UI prettier 158 RemoteViews contentView = 159 new RemoteViews(context.getPackageName(), 160 R.layout.emergency_action_count_down_notification); 161 contentView.setTextViewText(R.id.notification_text, 162 context.getString(R.string.emergency_action_subtitle, 163 emergencyNumberUtils.getPoliceNumber())); 164 contentView.setChronometerCountDown(R.id.chronometer, true); 165 contentView.setChronometer( 166 R.id.chronometer, 167 targetTimeMs, 168 /* format= */ null, 169 /* started= */ true); 170 return new Notification.Builder(context, channel.getId()) 171 .setColor(context.getColor(R.color.emergency_primary)) 172 .setSmallIcon(R.drawable.ic_launcher_settings) 173 .setStyle(new Notification.DecoratedCustomViewStyle()) 174 .setAutoCancel(false) 175 .setOngoing(true) 176 // This is set to make sure that device doesn't vibrate twice when client 177 // attempts to post currently displayed notification again 178 .setOnlyAlertOnce(true) 179 .setCategory(Notification.CATEGORY_ALARM) 180 .setCustomContentView(contentView) 181 .addAction(new Notification.Action.Builder(null, context.getText(R.string.cancel), 182 EmergencyActionBroadcastReceiver.newCancelCountdownPendingIntent( 183 context)).build()) 184 .build(); 185 } 186 scheduleEmergencyCallBroadcast(long remainingTimeMs)187 private void scheduleEmergencyCallBroadcast(long remainingTimeMs) { 188 long alarmTimeMs = System.currentTimeMillis() + remainingTimeMs; 189 AlarmManager alarmManager = getSystemService(AlarmManager.class); 190 alarmManager.setExactAndAllowWhileIdle( 191 AlarmManager.RTC_WAKEUP, alarmTimeMs, 192 EmergencyActionBroadcastReceiver.newCallEmergencyPendingIntent(this)); 193 } 194 195 } 196