blob: 4394428b597cc65cea3e8981babb19183faaf2be [file] [log] [blame]
/*
* 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.Notification.FOREGROUND_SERVICE_IMMEDIATE;
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.action.R;
import com.android.emergency.action.broadcast.EmergencyActionBroadcastReceiver;
import com.android.emergency.action.sensoryfeedback.EmergencyActionAlarmHelper;
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";
/** The alarm volume user setting before triggering this gesture */
private static final String SERVICE_EXTRA_ALARM_VOLUME = "service.extra.alarm_volume";
/** Random unique number for the notification */
private static final int COUNT_DOWN_NOTIFICATION_ID = 0x112;
private static final long[] TIMINGS = new long[]{200, 20, 20, 20, 20, 100, 20, 600};
private static final int[] AMPLITUDES = new int[]{0, 39, 82, 139, 213, 0, 127, 0};
private static final VibrationEffect VIBRATION_EFFECT =
VibrationEffect.createWaveform(TIMINGS, AMPLITUDES, /*repeat=*/ -1);
private TelecomManager mTelecomManager;
private Vibrator mVibrator;
private EmergencyNumberUtils mEmergencyNumberUtils;
private NotificationManager mNotificationManager;
private EmergencyActionAlarmHelper mAlarmHelper;
@Override
public void onCreate() {
super.onCreate();
PackageManager pm = getPackageManager();
mVibrator = getSystemService(Vibrator.class);
mNotificationManager = getSystemService(NotificationManager.class);
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
mTelecomManager = getSystemService(TelecomManager.class);
mEmergencyNumberUtils = new EmergencyNumberUtils(this);
mAlarmHelper = new EmergencyActionAlarmHelper(this);
}
}
@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,
Notification.class);
// Immediately show notification And now put the service in foreground mode
startForeground(COUNT_DOWN_NOTIFICATION_ID, notification);
scheduleEmergencyCallBroadcast(remainingTimeMs);
// vibration
mVibrator.vibrate(VIBRATION_EFFECT);
mAlarmHelper.playWarningSound();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
// Take notification down
mNotificationManager.cancel(COUNT_DOWN_NOTIFICATION_ID);
// Stop sound
mAlarmHelper.stopWarningSound();
// 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, int userSetAlarmVolume) {
return new Intent(context, EmergencyActionForegroundService.class)
.putExtra(SERVICE_EXTRA_REMAINING_TIME_MS, remainingTimeMs)
.putExtra(SERVICE_EXTRA_ALARM_VOLUME, userSetAlarmVolume)
.putExtra(SERVICE_EXTRA_NOTIFICATION,
buildCountDownNotification(context, remainingTimeMs));
}
/** End all work in this service and remove the foreground notification. */
public static void stopService(Context context) {
// Cancel previously scheduled eCall broadcast
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
alarmManager.cancel(
EmergencyActionBroadcastReceiver.newCallEmergencyPendingIntent(context));
// Stop service
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;
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())
.setColor(context.getColor(R.color.emergency_primary))
.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)
.setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE)
.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));
}
}