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