1 /*
2  * Copyright (C) 2013 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.cellbroadcastreceiver;
18 
19 import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
20 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRSRC_CBR;
21 import static com.android.cellbroadcastservice.CellBroadcastMetrics.ERRTYPE_REMINDERINTERVAL;
22 
23 import android.app.AlarmManager;
24 import android.app.PendingIntent;
25 import android.app.Service;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.pm.PackageManager;
30 import android.content.res.Resources;
31 import android.media.AudioAttributes;
32 import android.media.AudioManager;
33 import android.media.Ringtone;
34 import android.media.RingtoneManager;
35 import android.net.Uri;
36 import android.os.IBinder;
37 import android.os.SystemClock;
38 import android.os.VibrationEffect;
39 import android.os.Vibrator;
40 import android.preference.PreferenceManager;
41 import android.telephony.SubscriptionManager;
42 import android.util.Log;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 
46 /**
47  * Manages alert reminder notification.
48  */
49 public class CellBroadcastAlertReminder extends Service {
50     private static final String TAG = "CellBroadcastAlertReminder";
51 
52     /** Action to wake up and play alert reminder sound. */
53     @VisibleForTesting
54     public static final String ACTION_PLAY_ALERT_REMINDER = "ACTION_PLAY_ALERT_REMINDER";
55 
56     /** Extra for alert reminder vibration enabled (from settings). */
57     @VisibleForTesting
58     public static final String ALERT_REMINDER_VIBRATE_EXTRA = "alert_reminder_vibrate_extra";
59 
60     /**
61      * Pending intent for alert reminder. This is static so that we don't have to start the
62      * service in order to cancel any pending reminders when user dismisses the alert dialog.
63      */
64     private static PendingIntent sPlayReminderIntent;
65 
66     /**
67      * Alert reminder for current ringtone being played.
68      */
69     private static Ringtone sPlayReminderRingtone;
70 
71     @Override
onBind(Intent intent)72     public IBinder onBind(Intent intent) {
73         return null;
74     }
75 
76     @Override
onStartCommand(Intent intent, int flags, int startId)77     public int onStartCommand(Intent intent, int flags, int startId) {
78         // No intent or unrecognized action; tell the system not to restart us.
79         if (intent == null || !ACTION_PLAY_ALERT_REMINDER.equals(intent.getAction())) {
80             stopSelf();
81             return START_NOT_STICKY;
82         }
83 
84         log("playing alert reminder");
85         playAlertReminderSound(intent.getBooleanExtra(ALERT_REMINDER_VIBRATE_EXTRA, true));
86 
87         int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
88                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
89         if (queueAlertReminder(this, subId, false)) {
90             return START_STICKY;
91         } else {
92             log("no reminders queued");
93             stopSelf();
94             return START_NOT_STICKY;
95         }
96     }
97 
98     /**
99      * Use the RingtoneManager to play the alert reminder sound.
100      *
101      * @param enableVibration True to enable vibration when the alert reminder tone is playing,
102      *                        otherwise false.
103      */
playAlertReminderSound(boolean enableVibration)104     private void playAlertReminderSound(boolean enableVibration) {
105         Uri notificationUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
106         if (notificationUri == null) {
107             loge("Can't get URI for alert reminder sound");
108             return;
109         }
110         Ringtone r = RingtoneManager.getRingtone(this, notificationUri);
111 
112         if (r != null) {
113             if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
114                 r.setStreamType(AudioManager.STREAM_ALARM);
115             } else {
116                 r.setStreamType(AudioManager.STREAM_NOTIFICATION);
117             }
118             log("playing alert reminder sound");
119             r.play();
120         } else {
121             loge("can't get Ringtone for alert reminder sound");
122         }
123 
124         if (enableVibration) {
125             // Vibrate for 500ms.
126             Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
127             if (vibrator != null) {
128                 AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
129                 attrBuilder.setUsage(AudioAttributes.USAGE_ALARM);
130                 AudioAttributes attr = attrBuilder.build();
131                 vibrator.vibrate(VibrationEffect.createOneShot(500,
132                         VibrationEffect.DEFAULT_AMPLITUDE), attr);
133             } else {
134                 Log.e(TAG, "vibrator is null");
135             }
136         }
137     }
138 
139     /**
140      * Helper method to start the alert reminder service to queue the alert reminder.
141      *
142      * @param context Context.
143      * @param subId Subscription index
144      * @param firstTime True if entering this method for the first time, otherwise false.
145      *
146      * @return true if a pending reminder was set; false if there are no more reminders
147      */
148     @VisibleForTesting
queueAlertReminder(Context context, int subId, boolean firstTime)149     public static boolean queueAlertReminder(Context context, int subId, boolean firstTime) {
150         // Stop any alert reminder sound and cancel any previously queued reminders.
151         cancelAlertReminder();
152 
153         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
154         String prefStr = prefs.getString(CellBroadcastSettings.KEY_ALERT_REMINDER_INTERVAL,
155                 null);
156         int reminderIntervalMinutes;
157 
158         if (prefStr == null) {
159             if (DBG) log("no preference value for alert reminder");
160             return false;
161         }
162         try {
163             reminderIntervalMinutes = Integer.valueOf(prefStr);
164         } catch (NumberFormatException ignored) {
165             CellBroadcastReceiverMetrics.getInstance().logModuleError(
166                     ERRSRC_CBR, ERRTYPE_REMINDERINTERVAL);
167             loge("invalid alert reminder interval preference: " + prefStr);
168             return false;
169         }
170 
171         if (reminderIntervalMinutes == 0) {
172             if (DBG) log("Reminder is turned off.");
173             return false;
174         }
175 
176         // "1" means remind once, so we should do nothing if this is not the first reminder.
177         if (reminderIntervalMinutes == 1 && !firstTime) {
178             if (DBG) log("Not scheduling reminder. Done for now.");
179             return false;
180         }
181 
182         if (firstTime) {
183             Resources res = CellBroadcastSettings.getResourcesByOperator(context, subId,
184                     CellBroadcastReceiver.getRoamingOperatorSupported(context));
185             int interval = res.getInteger(R.integer.first_reminder_interval_in_min);
186             // If there is first reminder interval configured, use it.
187             if (interval != 0) {
188                 reminderIntervalMinutes = interval;
189             } else if (reminderIntervalMinutes == 1) {
190                 reminderIntervalMinutes = 2;   // "1" = one reminder after 2 minutes
191             }
192         }
193 
194         if (DBG) log("queueAlertReminder() in " + reminderIntervalMinutes + " minutes");
195 
196         Intent playIntent = new Intent(context, CellBroadcastAlertReminder.class);
197         playIntent.setAction(ACTION_PLAY_ALERT_REMINDER);
198         playIntent.putExtra(ALERT_REMINDER_VIBRATE_EXTRA,
199                 prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, true));
200         playIntent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
201         sPlayReminderIntent = PendingIntent.getService(context, 0, playIntent,
202                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
203 
204         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
205         if (alarmManager == null) {
206             loge("can't get Alarm Service");
207             return false;
208         }
209 
210         // remind user after 2 minutes or 15 minutes
211         long triggerTime = SystemClock.elapsedRealtime() + (reminderIntervalMinutes * 60000);
212         // We use setExact instead of set because this is for emergency reminder.
213         alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
214                 triggerTime, sPlayReminderIntent);
215         log("Set reminder in " + reminderIntervalMinutes + " minutes");
216         return true;
217     }
218 
219     /**
220      * Stops alert reminder and cancels any queued reminders.
221      */
cancelAlertReminder()222     static void cancelAlertReminder() {
223         if (DBG) log("cancelAlertReminder()");
224         if (sPlayReminderRingtone != null) {
225             if (DBG) log("stopping play reminder ringtone");
226             sPlayReminderRingtone.stop();
227             sPlayReminderRingtone = null;
228         }
229         if (sPlayReminderIntent != null) {
230             if (DBG) log("canceling pending play reminder intent");
231             sPlayReminderIntent.cancel();
232             sPlayReminderIntent = null;
233         }
234     }
235 
log(String msg)236     private static void log(String msg) {
237         Log.d(TAG, msg);
238     }
239 
loge(String msg)240     private static void loge(String msg) {
241         Log.e(TAG, msg);
242     }
243 }
244