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