TimerReceiver.java revision 06077d25a16684bc4d26c1ee0c48db556983c86c
1/* 2 * Copyright (C) 2012 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 17package com.android.deskclock.timer; 18 19import android.app.AlarmManager; 20import android.app.KeyguardManager; 21import android.app.Notification; 22import android.app.NotificationManager; 23import android.app.PendingIntent; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.SharedPreferences; 28import android.preference.PreferenceManager; 29import android.util.Log; 30 31import com.android.deskclock.AlarmAlertFullScreen; 32import com.android.deskclock.DeskClock; 33import com.android.deskclock.R; 34import com.android.deskclock.TimerRingService; 35 36import java.util.ArrayList; 37import java.util.Iterator; 38 39public class TimerReceiver extends BroadcastReceiver { 40 private static final String TAG = "TimerReceiver"; 41 42 private static final int IN_USE_NOTIFICATION_ID = 2; 43 44 ArrayList<TimerObj> mTimers; 45 46 @Override 47 public void onReceive(final Context context, final Intent intent) { 48 int timer; 49 String actionType = intent.getAction(); 50 51 // Get the updated timers data. 52 if (mTimers == null) { 53 mTimers = new ArrayList<TimerObj> (); 54 } 55 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 56 TimerObj.getTimersFromSharedPrefs(prefs, mTimers); 57 58 59 if (intent.hasExtra(Timers.TIMER_INTENT_EXTRA)) { 60 // Get the alarm out of the Intent 61 timer = intent.getIntExtra(Timers.TIMER_INTENT_EXTRA, -1); 62 if (timer == -1) { 63 Log.d(TAG, " got intent without Timer data: "+actionType); 64 } 65 } else if (Timers.NOTIF_IN_USE_SHOW.equals(actionType)){ 66 showInUseNotification(context); 67 return; 68 } else if (Timers.NOTIF_IN_USE_CANCEL.equals(actionType)) { 69 cancelInUseNotification(context); 70 return; 71 } else { 72 // No data to work with, do nothing 73 Log.d(TAG, " got intent without Timer data"); 74 return; 75 } 76 77 TimerObj t = Timers.findTimer(mTimers, timer); 78 79 if (intent.getBooleanExtra(Timers.UPDATE_NOTIFICATION, false)) { 80 if (Timers.TIMER_STOP.equals(actionType)) { 81 if (t == null) { 82 Log.d(TAG, "timer not found in list - can't stop it."); 83 return; 84 } 85 t.mState = TimerObj.STATE_DONE; 86 t.writeToSharedPref(prefs); 87 SharedPreferences.Editor editor = prefs.edit(); 88 editor.putBoolean(Timers.FROM_NOTIFICATION, true); 89 editor.putLong(Timers.NOTIF_TIME, System.currentTimeMillis()); 90 editor.putInt(Timers.NOTIF_ID, timer); 91 editor.apply(); 92 93 stopRingtoneIfNoTimesup(context); 94 95 Intent activityIntent = new Intent(context, DeskClock.class); 96 activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 97 activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX); 98 context.startActivity(activityIntent); 99 } 100 return; 101 } 102 103 if (Timers.TIMES_UP.equals(actionType)) { 104 // Find the timer (if it doesn't exists, it was probably deleted). 105 if (t == null) { 106 Log.d(TAG, " timer not found in list - do nothing"); 107 return; 108 } 109 110 t.mState = TimerObj.STATE_TIMESUP; 111 t.writeToSharedPref(prefs); 112 // Play ringtone by using TimerRingService service with a default alarm. 113 Log.d(TAG, "playing ringtone"); 114 Intent si = new Intent(); 115 si.setClass(context, TimerRingService.class); 116 context.startService(si); 117 118 // Start notification for buzzing alarm. 119 Intent broadcastIntent = new Intent(); 120 broadcastIntent.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId); 121 broadcastIntent.setAction(Timers.TIMER_STOP); 122 broadcastIntent.putExtra(Timers.UPDATE_NOTIFICATION, true); 123 PendingIntent pendingBroadcastIntent = PendingIntent.getBroadcast( 124 context, 0, broadcastIntent, 0); 125 String label = t.mLabel == "" ? context.getString(R.string.timer_notification_label) : 126 t.mLabel; 127 String contentText = context.getString(R.string.timer_times_up); 128 showCollapsedNotification(context, label, contentText, Notification.PRIORITY_MAX, 129 pendingBroadcastIntent, t.mTimerId, true); 130 cancelInUseNotification(context); 131 132 KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); 133 if (!km.inKeyguardRestrictedInputMode()) { 134 // Start the DeskClock Activity 135 Intent i = new Intent(); 136 i.setClass(context, DeskClock.class); 137 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 138 i.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX); 139 context.startActivity(i); 140 } else { 141 // Start the TimerAlertFullScreen activity. 142 Intent timersAlert = new Intent(context, TimerAlertFullScreen.class); 143 timersAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); 144 context.startActivity(timersAlert); 145 } 146 147 // Cancel the inuse notification if none are inuse. 148 if (getNextRunningTimer(mTimers, false, System.currentTimeMillis()) == null) { 149 // Found no running timers. 150 cancelInUseNotification(context); 151 } 152 153 } else if (Timers.TIMER_RESET.equals(actionType) 154 || Timers.DELETE_TIMER.equals(actionType) 155 || Timers.TIMER_DONE.equals(actionType)) { 156 // Stop Ringtone if all timers are not in timesup status 157 stopRingtoneIfNoTimesup(context); 158 } 159 // Update the next "Times up" alarm 160 updateNextTimesup(context); 161 } 162 163 private void stopRingtoneIfNoTimesup(final Context context) { 164 if (Timers.findExpiredTimer(mTimers) == null) { 165 // Stop ringtone 166 Log.d(TAG, "stopping ringtone"); 167 Intent si = new Intent(); 168 si.setClass(context, TimerRingService.class); 169 context.stopService(si); 170 } 171 } 172 173 // Scan all timers and find the one that will expire next. 174 // Tell AlarmManager to send a "Time's up" message to this receiver when this timer expires. 175 // If no timer exists, clear "time's up" message. 176 private void updateNextTimesup(Context context) { 177 TimerObj t = getNextRunningTimer(mTimers, false, System.currentTimeMillis()); 178 long nextTimesup = (t == null) ? -1 : t.getTimesupTime(); 179 int timerId = (t == null) ? -1 : t.mTimerId; 180 181 Intent intent = new Intent(); 182 intent.setAction(Timers.TIMES_UP); 183 intent.setClass(context, TimerReceiver.class); 184 if (!mTimers.isEmpty()) { 185 intent.putExtra(Timers.TIMER_INTENT_EXTRA, timerId); 186 } 187 AlarmManager mngr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 188 PendingIntent p = PendingIntent.getBroadcast(context, 189 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); 190 if (t != null) { 191 mngr.set(AlarmManager.RTC_WAKEUP, nextTimesup, p); 192 Log.d(TAG,"Setting times up to " + nextTimesup); 193 } else { 194 Log.d(TAG,"canceling times up"); 195 mngr.cancel(p); 196 } 197 } 198 199 private void showInUseNotification(final Context context) { 200 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 201 boolean appOpen = prefs.getBoolean(Timers.NOTIF_APP_OPEN, false); 202 ArrayList<TimerObj> timersInUse = Timers.timersInUse(mTimers); 203 int numTimersInUse = timersInUse.size(); 204 205 if (appOpen || numTimersInUse == 0) { 206 return; 207 } 208 209 String title, contentText; 210 Long nextBroadcastTime = null; 211 long now = System.currentTimeMillis(); 212 if (timersInUse.size() == 1) { 213 TimerObj timer = timersInUse.get(0); 214 String label = timer.mLabel == "" ? context.getString(R.string.timer_notification_label) 215 : timer.mLabel; 216 title = timer.isTicking() ? label : context.getString(R.string.timer_stopped); 217 long timeLeft = timer.isTicking() ? timer.getTimesupTime() - now : timer.mTimeLeft; 218 contentText = buildTimeRemaining(context, timeLeft); 219 if (timeLeft > 60) { 220 nextBroadcastTime = getBroadcastTime(now, timeLeft); 221 } 222 } else { 223 TimerObj timer = getNextRunningTimer(timersInUse, false, now); 224 if (timer == null) { 225 // No running timers. 226 title = String.format( 227 context.getString(R.string.timers_stopped), numTimersInUse); 228 contentText = context.getString(R.string.all_timers_stopped_notif); 229 } else { 230 // We have at least one timer running and other timers stopped. 231 title = String.format( 232 context.getString(R.string.timers_in_use), numTimersInUse); 233 long completionTime = timer.getTimesupTime(); 234 long timeLeft = completionTime - now; 235 contentText = String.format(context.getString(R.string.next_timer_notif), 236 buildTimeRemaining(context, timeLeft)); 237 if (timeLeft <= 60) { 238 TimerObj timerWithUpdate = getNextRunningTimer(timersInUse, true, now); 239 if (timerWithUpdate != null) { 240 completionTime = timerWithUpdate.getTimesupTime(); 241 timeLeft = completionTime - now; 242 nextBroadcastTime = getBroadcastTime(now, timeLeft); 243 } 244 } else { 245 nextBroadcastTime = getBroadcastTime(now, timeLeft); 246 } 247 } 248 } 249 showCollapsedNotificationWithNext(context, title, contentText, nextBroadcastTime); 250 } 251 252 private long getBroadcastTime(long now, long timeUntilBroadcast) { 253 long seconds = timeUntilBroadcast / 1000; 254 seconds = seconds - ( (seconds / 60) * 60 ); 255 return now + (seconds * 1000); 256 } 257 258 private void showCollapsedNotificationWithNext( 259 final Context context, String title, String text, Long nextBroadcastTime) { 260 Intent activityIntent = new Intent(context, DeskClock.class); 261 activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 262 activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX); 263 PendingIntent pendingActivityIntent = PendingIntent.getActivity(context, 0, activityIntent, 264 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); 265 showCollapsedNotification(context, title, text, Notification.PRIORITY_HIGH, 266 pendingActivityIntent, IN_USE_NOTIFICATION_ID, false); 267 268 if (nextBroadcastTime == null) { 269 return; 270 } 271 Intent nextBroadcast = new Intent(); 272 nextBroadcast.setAction(Timers.NOTIF_IN_USE_SHOW); 273 PendingIntent pendingNextBroadcast = 274 PendingIntent.getBroadcast(context, 0, nextBroadcast, 0); 275 AlarmManager alarmManager = 276 (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 277 alarmManager.set(AlarmManager.RTC, nextBroadcastTime, pendingNextBroadcast); 278 } 279 280 private void showCollapsedNotification(final Context context, String title, String text, 281 int priority, PendingIntent pendingIntent, int notificationId, boolean showTicker) { 282 Notification.Builder builder = new Notification.Builder(context) 283 .setAutoCancel(false) 284 .setContentTitle(title) 285 .setContentText(text) 286 .setDeleteIntent(pendingIntent) 287 .setOngoing(true) 288 .setPriority(priority) 289 .setShowWhen(false) 290 .setSmallIcon(R.drawable.stat_notify_timer); 291 if (showTicker) { 292 builder.setTicker(text); 293 } 294 295 Notification notification = builder.build(); 296 notification.contentIntent = pendingIntent; 297 NotificationManager notificationManager = 298 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 299 notificationManager.notify(notificationId, notification); 300 } 301 302 private String buildTimeRemaining(Context context, long timeLeft) { 303 if (timeLeft < 0) { 304 // We should never be here... 305 Log.v(TAG, "Will not show notification for timer already expired."); 306 return null; 307 } 308 309 long hundreds, seconds, minutes, hours; 310 seconds = timeLeft / 1000; 311 minutes = seconds / 60; 312 seconds = seconds - minutes * 60; 313 hours = minutes / 60; 314 minutes = minutes - hours * 60; 315 if (hours > 99) { 316 hours = 0; 317 } 318 319 String hourSeq = (hours == 0) ? "" : 320 ( (hours == 1) ? context.getString(R.string.hour) : 321 context.getString(R.string.hours, Long.toString(hours)) ); 322 String minSeq = (minutes == 0) ? "" : 323 ( (minutes == 1) ? context.getString(R.string.minute) : 324 context.getString(R.string.minutes, Long.toString(minutes)) ); 325 326 boolean dispHour = hours > 0; 327 boolean dispMinute = minutes > 0; 328 int index = (dispHour ? 1 : 0) | (dispMinute ? 2 : 0); 329 String[] formats = context.getResources().getStringArray(R.array.timer_notifications); 330 return String.format(formats[index], hourSeq, minSeq); 331 } 332 333 private TimerObj getNextRunningTimer( 334 ArrayList<TimerObj> timers, boolean requireNextUpdate, long now) { 335 long nextTimesup = Long.MAX_VALUE; 336 boolean nextTimerFound = false; 337 Iterator<TimerObj> i = timers.iterator(); 338 TimerObj t = null; 339 while(i.hasNext()) { 340 TimerObj tmp = i.next(); 341 if (tmp.mState == TimerObj.STATE_RUNNING) { 342 long timesupTime = tmp.getTimesupTime(); 343 long timeLeft = timesupTime - now; 344 if (timesupTime < nextTimesup && (!requireNextUpdate || timeLeft > 60) ) { 345 nextTimesup = timesupTime; 346 nextTimerFound = true; 347 t = tmp; 348 } 349 } 350 } 351 if (nextTimerFound) { 352 return t; 353 } else { 354 return null; 355 } 356 } 357 358 private void cancelInUseNotification(final Context context) { 359 NotificationManager notificationManager = 360 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 361 notificationManager.cancel(IN_USE_NOTIFICATION_ID); 362 } 363}