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