• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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