| /* |
| * Copyright (C) 2012 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.deskclock.timer; |
| |
| import android.app.AlarmManager; |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.preference.PreferenceManager; |
| import android.util.Log; |
| |
| import com.android.deskclock.DeskClock; |
| import com.android.deskclock.R; |
| import com.android.deskclock.TimerRingService; |
| import com.android.deskclock.Utils; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| |
| public class TimerReceiver extends BroadcastReceiver { |
| private static final String TAG = "TimerReceiver"; |
| |
| // Make this a large number to avoid the alarm ID's which seem to be 1, 2, ... |
| // Must also be different than StopwatchService.NOTIFICATION_ID |
| private static final int IN_USE_NOTIFICATION_ID = Integer.MAX_VALUE - 2; |
| |
| ArrayList<TimerObj> mTimers; |
| |
| @Override |
| public void onReceive(final Context context, final Intent intent) { |
| int timer; |
| String actionType = intent.getAction(); |
| |
| // Get the updated timers data. |
| if (mTimers == null) { |
| mTimers = new ArrayList<TimerObj> (); |
| } |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
| TimerObj.getTimersFromSharedPrefs(prefs, mTimers); |
| |
| |
| if (intent.hasExtra(Timers.TIMER_INTENT_EXTRA)) { |
| // Get the alarm out of the Intent |
| timer = intent.getIntExtra(Timers.TIMER_INTENT_EXTRA, -1); |
| if (timer == -1) { |
| Log.d(TAG, " got intent without Timer data: "+actionType); |
| } |
| } else if (Timers.NOTIF_IN_USE_SHOW.equals(actionType)){ |
| showInUseNotification(context); |
| return; |
| } else if (Timers.NOTIF_IN_USE_CANCEL.equals(actionType)) { |
| cancelInUseNotification(context); |
| return; |
| } else { |
| // No data to work with, do nothing |
| Log.d(TAG, " got intent without Timer data"); |
| return; |
| } |
| |
| TimerObj t = Timers.findTimer(mTimers, timer); |
| |
| if (intent.getBooleanExtra(Timers.UPDATE_NOTIFICATION, false)) { |
| if (Timers.TIMER_STOP.equals(actionType)) { |
| if (t == null) { |
| Log.d(TAG, "timer not found in list - can't stop it."); |
| return; |
| } |
| t.mState = TimerObj.STATE_DONE; |
| t.writeToSharedPref(prefs); |
| SharedPreferences.Editor editor = prefs.edit(); |
| editor.putBoolean(Timers.FROM_NOTIFICATION, true); |
| editor.putLong(Timers.NOTIF_TIME, Utils.getTimeNow()); |
| editor.putInt(Timers.NOTIF_ID, timer); |
| editor.apply(); |
| |
| stopRingtoneIfNoTimesup(context); |
| |
| Intent activityIntent = new Intent(context, DeskClock.class); |
| activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX); |
| context.startActivity(activityIntent); |
| } |
| return; |
| } |
| |
| if (Timers.TIMES_UP.equals(actionType)) { |
| // Find the timer (if it doesn't exists, it was probably deleted). |
| if (t == null) { |
| Log.d(TAG, " timer not found in list - do nothing"); |
| return; |
| } |
| |
| t.mState = TimerObj.STATE_TIMESUP; |
| t.writeToSharedPref(prefs); |
| // Play ringtone by using TimerRingService service with a default alarm. |
| Log.d(TAG, "playing ringtone"); |
| Intent si = new Intent(); |
| si.setClass(context, TimerRingService.class); |
| context.startService(si); |
| |
| // Update the in-use notification |
| if (getNextRunningTimer(mTimers, false, Utils.getTimeNow()) == null) { |
| // Found no running timers. |
| cancelInUseNotification(context); |
| } else { |
| showInUseNotification(context); |
| } |
| |
| // Start the TimerAlertFullScreen activity. |
| Intent timersAlert = new Intent(context, TimerAlertFullScreen.class); |
| timersAlert.setFlags( |
| Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); |
| context.startActivity(timersAlert); |
| } else if (Timers.TIMER_RESET.equals(actionType) |
| || Timers.DELETE_TIMER.equals(actionType) |
| || Timers.TIMER_DONE.equals(actionType)) { |
| // Stop Ringtone if all timers are not in times-up status |
| stopRingtoneIfNoTimesup(context); |
| } |
| // Update the next "Times up" alarm |
| updateNextTimesup(context); |
| } |
| |
| private void stopRingtoneIfNoTimesup(final Context context) { |
| if (Timers.findExpiredTimer(mTimers) == null) { |
| // Stop ringtone |
| Log.d(TAG, "stopping ringtone"); |
| Intent si = new Intent(); |
| si.setClass(context, TimerRingService.class); |
| context.stopService(si); |
| } |
| } |
| |
| // Scan all timers and find the one that will expire next. |
| // Tell AlarmManager to send a "Time's up" message to this receiver when this timer expires. |
| // If no timer exists, clear "time's up" message. |
| private void updateNextTimesup(Context context) { |
| TimerObj t = getNextRunningTimer(mTimers, false, Utils.getTimeNow()); |
| long nextTimesup = (t == null) ? -1 : t.getTimesupTime(); |
| int timerId = (t == null) ? -1 : t.mTimerId; |
| |
| Intent intent = new Intent(); |
| intent.setAction(Timers.TIMES_UP); |
| intent.setClass(context, TimerReceiver.class); |
| if (!mTimers.isEmpty()) { |
| intent.putExtra(Timers.TIMER_INTENT_EXTRA, timerId); |
| } |
| AlarmManager mngr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); |
| PendingIntent p = PendingIntent.getBroadcast(context, |
| 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); |
| if (t != null) { |
| mngr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTimesup, p); |
| Log.d(TAG,"Setting times up to " + nextTimesup); |
| } else { |
| Log.d(TAG,"canceling times up"); |
| mngr.cancel(p); |
| } |
| } |
| |
| private void showInUseNotification(final Context context) { |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
| boolean appOpen = prefs.getBoolean(Timers.NOTIF_APP_OPEN, false); |
| ArrayList<TimerObj> timersInUse = Timers.timersInUse(mTimers); |
| int numTimersInUse = timersInUse.size(); |
| |
| if (appOpen || numTimersInUse == 0) { |
| return; |
| } |
| |
| String title, contentText; |
| Long nextBroadcastTime = null; |
| long now = Utils.getTimeNow(); |
| if (timersInUse.size() == 1) { |
| TimerObj timer = timersInUse.get(0); |
| boolean timerIsTicking = timer.isTicking(); |
| String label = timer.mLabel.equals("") ? |
| context.getString(R.string.timer_notification_label) : timer.mLabel; |
| title = timerIsTicking ? label : context.getString(R.string.timer_stopped); |
| long timeLeft = timerIsTicking ? timer.getTimesupTime() - now : timer.mTimeLeft; |
| contentText = buildTimeRemaining(context, timeLeft); |
| if (timerIsTicking && timeLeft > 60 * 1000) { |
| nextBroadcastTime = getBroadcastTime(now, timeLeft); |
| } |
| } else { |
| TimerObj timer = getNextRunningTimer(timersInUse, false, now); |
| if (timer == null) { |
| // No running timers. |
| title = String.format( |
| context.getString(R.string.timers_stopped), numTimersInUse); |
| contentText = context.getString(R.string.all_timers_stopped_notif); |
| } else { |
| // We have at least one timer running and other timers stopped. |
| title = String.format( |
| context.getString(R.string.timers_in_use), numTimersInUse); |
| long completionTime = timer.getTimesupTime(); |
| long timeLeft = completionTime - now; |
| contentText = String.format(context.getString(R.string.next_timer_notif), |
| buildTimeRemaining(context, timeLeft)); |
| if (timeLeft <= 60 * 1000) { |
| TimerObj timerWithUpdate = getNextRunningTimer(timersInUse, true, now); |
| if (timerWithUpdate != null) { |
| completionTime = timerWithUpdate.getTimesupTime(); |
| timeLeft = completionTime - now; |
| nextBroadcastTime = getBroadcastTime(now, timeLeft); |
| } |
| } else { |
| nextBroadcastTime = getBroadcastTime(now, timeLeft); |
| } |
| } |
| } |
| showCollapsedNotificationWithNext(context, title, contentText, nextBroadcastTime); |
| } |
| |
| private long getBroadcastTime(long now, long timeUntilBroadcast) { |
| long seconds = timeUntilBroadcast / 1000; |
| seconds = seconds - ( (seconds / 60) * 60 ); |
| return now + (seconds * 1000); |
| } |
| |
| /** Public and static to allow timer fragment to update notification with new label. **/ |
| public static void showExpiredAlarmNotification(Context context, TimerObj t) { |
| Intent broadcastIntent = new Intent(); |
| broadcastIntent.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId); |
| broadcastIntent.setAction(Timers.TIMER_STOP); |
| broadcastIntent.putExtra(Timers.UPDATE_NOTIFICATION, true); |
| PendingIntent pendingBroadcastIntent = PendingIntent.getBroadcast( |
| context, 0, broadcastIntent, 0); |
| String label = t.mLabel.equals("") ? context.getString(R.string.timer_notification_label) : |
| t.mLabel; |
| String contentText = context.getString(R.string.timer_times_up); |
| showCollapsedNotification(context, label, contentText, Notification.PRIORITY_MAX, |
| pendingBroadcastIntent, t.mTimerId, true); |
| } |
| |
| private void showCollapsedNotificationWithNext( |
| final Context context, String title, String text, Long nextBroadcastTime) { |
| Intent activityIntent = new Intent(context, DeskClock.class); |
| activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX); |
| PendingIntent pendingActivityIntent = PendingIntent.getActivity(context, 0, activityIntent, |
| PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); |
| showCollapsedNotification(context, title, text, Notification.PRIORITY_HIGH, |
| pendingActivityIntent, IN_USE_NOTIFICATION_ID, false); |
| |
| if (nextBroadcastTime == null) { |
| return; |
| } |
| Intent nextBroadcast = new Intent(); |
| nextBroadcast.setAction(Timers.NOTIF_IN_USE_SHOW); |
| PendingIntent pendingNextBroadcast = |
| PendingIntent.getBroadcast(context, 0, nextBroadcast, 0); |
| AlarmManager alarmManager = |
| (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
| alarmManager.set(AlarmManager.ELAPSED_REALTIME, nextBroadcastTime, pendingNextBroadcast); |
| } |
| |
| private static void showCollapsedNotification(final Context context, String title, String text, |
| int priority, PendingIntent pendingIntent, int notificationId, boolean showTicker) { |
| Notification.Builder builder = new Notification.Builder(context) |
| .setAutoCancel(false) |
| .setContentTitle(title) |
| .setContentText(text) |
| .setDeleteIntent(pendingIntent) |
| .setOngoing(true) |
| .setPriority(priority) |
| .setShowWhen(false) |
| .setSmallIcon(R.drawable.stat_notify_timer); |
| if (showTicker) { |
| builder.setTicker(text); |
| } |
| |
| Notification notification = builder.build(); |
| notification.contentIntent = pendingIntent; |
| NotificationManager notificationManager = |
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
| notificationManager.notify(notificationId, notification); |
| } |
| |
| private String buildTimeRemaining(Context context, long timeLeft) { |
| if (timeLeft < 0) { |
| // We should never be here... |
| Log.v(TAG, "Will not show notification for timer already expired."); |
| return null; |
| } |
| |
| long hundreds, seconds, minutes, hours; |
| seconds = timeLeft / 1000; |
| minutes = seconds / 60; |
| seconds = seconds - minutes * 60; |
| hours = minutes / 60; |
| minutes = minutes - hours * 60; |
| if (hours > 99) { |
| hours = 0; |
| } |
| |
| String hourSeq = (hours == 0) ? "" : |
| ( (hours == 1) ? context.getString(R.string.hour) : |
| context.getString(R.string.hours, Long.toString(hours)) ); |
| String minSeq = (minutes == 0) ? "" : |
| ( (minutes == 1) ? context.getString(R.string.minute) : |
| context.getString(R.string.minutes, Long.toString(minutes)) ); |
| |
| boolean dispHour = hours > 0; |
| boolean dispMinute = minutes > 0; |
| int index = (dispHour ? 1 : 0) | (dispMinute ? 2 : 0); |
| String[] formats = context.getResources().getStringArray(R.array.timer_notifications); |
| return String.format(formats[index], hourSeq, minSeq); |
| } |
| |
| private TimerObj getNextRunningTimer( |
| ArrayList<TimerObj> timers, boolean requireNextUpdate, long now) { |
| long nextTimesup = Long.MAX_VALUE; |
| boolean nextTimerFound = false; |
| Iterator<TimerObj> i = timers.iterator(); |
| TimerObj t = null; |
| while(i.hasNext()) { |
| TimerObj tmp = i.next(); |
| if (tmp.mState == TimerObj.STATE_RUNNING) { |
| long timesupTime = tmp.getTimesupTime(); |
| long timeLeft = timesupTime - now; |
| if (timesupTime < nextTimesup && (!requireNextUpdate || timeLeft > 60) ) { |
| nextTimesup = timesupTime; |
| nextTimerFound = true; |
| t = tmp; |
| } |
| } |
| } |
| if (nextTimerFound) { |
| return t; |
| } else { |
| return null; |
| } |
| } |
| |
| private void cancelInUseNotification(final Context context) { |
| NotificationManager notificationManager = |
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
| notificationManager.cancel(IN_USE_NOTIFICATION_ID); |
| } |
| } |