163211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson/*
263211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * Copyright (C) 2012 The Android Open Source Project
363211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson *
463211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * Licensed under the Apache License, Version 2.0 (the "License");
563211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * you may not use this file except in compliance with the License.
663211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * You may obtain a copy of the License at
763211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson *
863211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson *      http://www.apache.org/licenses/LICENSE-2.0
963211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson *
1063211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * Unless required by applicable law or agreed to in writing, software
1163211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * distributed under the License is distributed on an "AS IS" BASIS,
1263211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1363211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * See the License for the specific language governing permissions and
1463211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson * limitations under the License.
1563211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson */
1663211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson
1763211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonpackage com.android.deskclock.timer;
1863211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson
19871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelsonimport android.app.AlarmManager;
2063211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonimport android.app.Notification;
211ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelsonimport android.app.NotificationManager;
2263211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonimport android.app.PendingIntent;
2363211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonimport android.content.BroadcastReceiver;
2463211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonimport android.content.Context;
2563211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonimport android.content.Intent;
26871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelsonimport android.content.SharedPreferences;
27871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelsonimport android.preference.PreferenceManager;
2863211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonimport android.util.Log;
2963211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson
30871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelsonimport com.android.deskclock.DeskClock;
311ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelsonimport com.android.deskclock.R;
323f99104b4dc4df9a774ff7433084b8e483d88744Isaac Katzenelsonimport com.android.deskclock.TimerRingService;
33a65d3eb00d543735305221f86ce630c8650e65e2Isaac Katzenelsonimport com.android.deskclock.Utils;
34871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
35871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelsonimport java.util.ArrayList;
36871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelsonimport java.util.Iterator;
37871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
3863211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelsonpublic class TimerReceiver extends BroadcastReceiver {
3963211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson    private static final String TAG = "TimerReceiver";
4063211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson
419e33fe399ce41766dc0a2d6e8f3e2fef0d30f6c1Robyn Coultas    // Make this a large number to avoid the alarm ID's which seem to be 1, 2, ...
429e33fe399ce41766dc0a2d6e8f3e2fef0d30f6c1Robyn Coultas    // Must also be different than StopwatchService.NOTIFICATION_ID
439e33fe399ce41766dc0a2d6e8f3e2fef0d30f6c1Robyn Coultas    private static final int IN_USE_NOTIFICATION_ID = Integer.MAX_VALUE - 2;
441ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
45871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    ArrayList<TimerObj> mTimers;
46871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
47871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    @Override
4863211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson    public void onReceive(final Context context, final Intent intent) {
49dd97056fa889b35ce850fd0107e2f59b925bae49Isaac Katzenelson        if (Timers.LOGGING) {
50dd97056fa889b35ce850fd0107e2f59b925bae49Isaac Katzenelson            Log.v(TAG, "Received intent " + intent.toString());
51dd97056fa889b35ce850fd0107e2f59b925bae49Isaac Katzenelson        }
521ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        String actionType = intent.getAction();
53d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // This action does not need the timers data
54d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        if (Timers.NOTIF_IN_USE_CANCEL.equals(actionType)) {
55d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            cancelInUseNotification(context);
56d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            return;
57d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        }
58d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
591ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        // Get the updated timers data.
601ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (mTimers == null) {
611ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            mTimers = new ArrayList<TimerObj> ();
621ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
631ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
641ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        TimerObj.getTimersFromSharedPrefs(prefs, mTimers);
6563211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson
66d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // These actions do not provide a timer ID, but do use the timers data
67d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        if (Timers.NOTIF_IN_USE_SHOW.equals(actionType)) {
681ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            showInUseNotification(context);
691ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return;
70d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        } else if (Timers.NOTIF_TIMES_UP_SHOW.equals(actionType)) {
71d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            showTimesUpNotification(context);
721ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return;
73d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        } else if (Timers.NOTIF_TIMES_UP_CANCEL.equals(actionType)) {
74d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            cancelTimesUpNotification(context);
751ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return;
761ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
771ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
78d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Remaining actions provide a timer Id
79d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        if (!intent.hasExtra(Timers.TIMER_INTENT_EXTRA)) {
80d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // No data to work with, do nothing
81dd97056fa889b35ce850fd0107e2f59b925bae49Isaac Katzenelson            Log.e(TAG, "got intent without Timer data");
82d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            return;
83d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        }
8463211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson
85d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Get the timer out of the Intent
86d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        int timerId = intent.getIntExtra(Timers.TIMER_INTENT_EXTRA, -1);
87d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        if (timerId == -1) {
88d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            Log.d(TAG, "OnReceive:intent without Timer data for " + actionType);
891ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
90dd97056fa889b35ce850fd0107e2f59b925bae49Isaac Katzenelson
91d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        TimerObj t = Timers.findTimer(mTimers, timerId);
921ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
931ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (Timers.TIMES_UP.equals(actionType)) {
94871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            // Find the timer (if it doesn't exists, it was probably deleted).
95871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            if (t == null) {
96871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson                Log.d(TAG, " timer not found in list - do nothing");
97871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson                return;
98871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            }
99871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
100871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            t.mState = TimerObj.STATE_TIMESUP;
101871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            t.writeToSharedPref(prefs);
102647353dd437f8c075aa96f3b98efb84442e8b7bfRobyn Coultas            // Play ringtone by using TimerRingService service with a default alarm.
103871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            Log.d(TAG, "playing ringtone");
104871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            Intent si = new Intent();
1053f99104b4dc4df9a774ff7433084b8e483d88744Isaac Katzenelson            si.setClass(context, TimerRingService.class);
106871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            context.startService(si);
107871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
108feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            // Update the in-use notification
109a65d3eb00d543735305221f86ce630c8650e65e2Isaac Katzenelson            if (getNextRunningTimer(mTimers, false, Utils.getTimeNow()) == null) {
1101ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                // Found no running timers.
1111ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                cancelInUseNotification(context);
112feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            } else {
113feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas                showInUseNotification(context);
114871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            }
1151ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
116feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            // Start the TimerAlertFullScreen activity.
117feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            Intent timersAlert = new Intent(context, TimerAlertFullScreen.class);
118feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            timersAlert.setFlags(
119feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
120feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            context.startActivity(timersAlert);
1211ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        } else if (Timers.TIMER_RESET.equals(actionType)
1221ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                || Timers.DELETE_TIMER.equals(actionType)
1231ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                || Timers.TIMER_DONE.equals(actionType)) {
124feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas            // Stop Ringtone if all timers are not in times-up status
1251ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            stopRingtoneIfNoTimesup(context);
126d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        } else if (Timers.NOTIF_TIMES_UP_STOP.equals(actionType)) {
127d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Find the timer (if it doesn't exists, it was probably deleted).
128d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (t == null) {
129d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                Log.d(TAG, "timer to stop not found in list - do nothing");
130d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                return;
131d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            } else if (t.mState != TimerObj.STATE_TIMESUP) {
132d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                Log.d(TAG, "action to stop but timer not in times-up state - do nothing");
133d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                return;
134d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
135d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
136d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Update timer state
137d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.mState = t.getDeleteAfterUse() ? TimerObj.STATE_DELETED : TimerObj.STATE_DONE;
138d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.mTimeLeft = t.mOriginalLength - (Utils.getTimeNow() - t.mStartTime);
139d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.writeToSharedPref(prefs);
140d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
141d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Flag to tell DeskClock to re-sync with the database
142d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            prefs.edit().putBoolean(Timers.FROM_NOTIFICATION, true).apply();
143d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
144d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            cancelTimesUpNotification(context, t);
145d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
146d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Done with timer - delete from data base
147d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (t.getDeleteAfterUse()) {
148d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                t.deleteFromSharedPref(prefs);
149d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
150d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
151d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Stop Ringtone if no timers are in times-up status
152d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            stopRingtoneIfNoTimesup(context);
153d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        } else if (Timers.NOTIF_TIMES_UP_PLUS_ONE.equals(actionType)) {
154d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Find the timer (if it doesn't exists, it was probably deleted).
155d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (t == null) {
156d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                Log.d(TAG, "timer to +1m not found in list - do nothing");
157d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                return;
158d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            } else if (t.mState != TimerObj.STATE_TIMESUP) {
159d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                Log.d(TAG, "action to +1m but timer not in times up state - do nothing");
160d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                return;
161d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
162d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
163d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Restarting the timer with 1 minute left.
164d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.mState = TimerObj.STATE_RUNNING;
165d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.mStartTime = Utils.getTimeNow();
166d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.mTimeLeft = t. mOriginalLength = TimerObj.MINUTE_IN_MILLIS;
167d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            t.writeToSharedPref(prefs);
168d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
169d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Flag to tell DeskClock to re-sync with the database
170d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            prefs.edit().putBoolean(Timers.FROM_NOTIFICATION, true).apply();
171d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
172d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            cancelTimesUpNotification(context, t);
173d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
174d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // If the app is not open, refresh the in-use notification
175d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (!prefs.getBoolean(Timers.NOTIF_APP_OPEN, false)) {
176d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                showInUseNotification(context);
177d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
178d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
179d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Stop Ringtone if no timers are in times-up status
180d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            stopRingtoneIfNoTimesup(context);
181d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        } else if (Timers.TIMER_UPDATE.equals(actionType)) {
182d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            // Refresh buzzing notification
183d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (t.mState == TimerObj.STATE_TIMESUP) {
184d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                // Must cancel the previous notification to get all updates displayed correctly
185d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                cancelTimesUpNotification(context, t);
186d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                showTimesUpNotification(context, t);
187d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
188871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        }
189871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        // Update the next "Times up" alarm
1901ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        updateNextTimesup(context);
1911ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
1921ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
1931ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private void stopRingtoneIfNoTimesup(final Context context) {
1941ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (Timers.findExpiredTimer(mTimers) == null) {
1951ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            // Stop ringtone
1961ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            Log.d(TAG, "stopping ringtone");
1971ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            Intent si = new Intent();
1981ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            si.setClass(context, TimerRingService.class);
1991ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            context.stopService(si);
2001ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
20163211efa1565b5dc171938740ba183302d0e46f3Isaac Katzenelson    }
202871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
203871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    // Scan all timers and find the one that will expire next.
204871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    // Tell AlarmManager to send a "Time's up" message to this receiver when this timer expires.
205871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    // If no timer exists, clear "time's up" message.
206871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    private void updateNextTimesup(Context context) {
207a65d3eb00d543735305221f86ce630c8650e65e2Isaac Katzenelson        TimerObj t = getNextRunningTimer(mTimers, false, Utils.getTimeNow());
2081ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        long nextTimesup = (t == null) ? -1 : t.getTimesupTime();
2091ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        int timerId = (t == null) ? -1 : t.mTimerId;
210871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
211871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        Intent intent = new Intent();
212871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        intent.setAction(Timers.TIMES_UP);
213871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        intent.setClass(context, TimerReceiver.class);
2141ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (!mTimers.isEmpty()) {
2151ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            intent.putExtra(Timers.TIMER_INTENT_EXTRA, timerId);
216871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        }
217871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        AlarmManager mngr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
218871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        PendingIntent p = PendingIntent.getBroadcast(context,
219871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson                0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
2201ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (t != null) {
2216ff2962d9a2726bacffa20ed09e3c3346fe7491dIsaac Katzenelson            if (Utils.isKitKatOrLater()) {
222203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas                mngr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTimesup, p);
223203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas            } else {
224203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas                mngr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTimesup, p);
225203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas            }
226d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (Timers.LOGGING) {
227d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                Log.d(TAG, "Setting times up to " + nextTimesup);
228d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
229871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        } else {
230871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson            mngr.cancel(p);
231d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            if (Timers.LOGGING) {
232d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                Log.v(TAG, "no next times up");
233d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            }
234871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson        }
235871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson    }
236871d7d00b561dc0e100c5ac7f79542d20ae9578dIsaac Katzenelson
2371ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private void showInUseNotification(final Context context) {
2381ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2391ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        boolean appOpen = prefs.getBoolean(Timers.NOTIF_APP_OPEN, false);
2401ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        ArrayList<TimerObj> timersInUse = Timers.timersInUse(mTimers);
2411ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        int numTimersInUse = timersInUse.size();
2421ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
2431ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (appOpen || numTimersInUse == 0) {
2441ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return;
2451ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
2461ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
2471ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        String title, contentText;
2481ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        Long nextBroadcastTime = null;
249a65d3eb00d543735305221f86ce630c8650e65e2Isaac Katzenelson        long now = Utils.getTimeNow();
2501ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (timersInUse.size() == 1) {
2511ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            TimerObj timer = timersInUse.get(0);
252f6017873e1056b0d9ec0932eb49af77f8ce8814cSam Blitzstein            boolean timerIsTicking = timer.isTicking();
253d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            String label = timer.getLabelOrDefault(context);
254f6017873e1056b0d9ec0932eb49af77f8ce8814cSam Blitzstein            title = timerIsTicking ? label : context.getString(R.string.timer_stopped);
255f6017873e1056b0d9ec0932eb49af77f8ce8814cSam Blitzstein            long timeLeft = timerIsTicking ? timer.getTimesupTime() - now : timer.mTimeLeft;
2561ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            contentText = buildTimeRemaining(context, timeLeft);
257b2e25c2811320be6f719d3dfe9931cdc06212759Isaac Katzenelson            if (timerIsTicking && timeLeft > TimerObj.MINUTE_IN_MILLIS) {
2581ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                nextBroadcastTime = getBroadcastTime(now, timeLeft);
2591ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            }
2601ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        } else {
2611ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            TimerObj timer = getNextRunningTimer(timersInUse, false, now);
2621ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            if (timer == null) {
2631ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                // No running timers.
2641ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                title = String.format(
2651ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                        context.getString(R.string.timers_stopped), numTimersInUse);
2661ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                contentText = context.getString(R.string.all_timers_stopped_notif);
2671ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            } else {
2681ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                // We have at least one timer running and other timers stopped.
2691ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                title = String.format(
2701ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                        context.getString(R.string.timers_in_use), numTimersInUse);
2711ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                long completionTime = timer.getTimesupTime();
2721ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                long timeLeft = completionTime - now;
2731ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                contentText = String.format(context.getString(R.string.next_timer_notif),
2741ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                        buildTimeRemaining(context, timeLeft));
275b2e25c2811320be6f719d3dfe9931cdc06212759Isaac Katzenelson                if (timeLeft <= TimerObj.MINUTE_IN_MILLIS) {
2761ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    TimerObj timerWithUpdate = getNextRunningTimer(timersInUse, true, now);
2771ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    if (timerWithUpdate != null) {
2781ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                        completionTime = timerWithUpdate.getTimesupTime();
2791ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                        timeLeft = completionTime - now;
2801ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                        nextBroadcastTime = getBroadcastTime(now, timeLeft);
2811ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    }
2821ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                } else {
2831ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    nextBroadcastTime = getBroadcastTime(now, timeLeft);
2841ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                }
2851ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            }
2861ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
2871ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        showCollapsedNotificationWithNext(context, title, contentText, nextBroadcastTime);
2881ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
2891ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
2901ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private long getBroadcastTime(long now, long timeUntilBroadcast) {
2911ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        long seconds = timeUntilBroadcast / 1000;
2921ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        seconds = seconds - ( (seconds / 60) * 60 );
2931ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        return now + (seconds * 1000);
2941ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
2951ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
2961ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private void showCollapsedNotificationWithNext(
2971ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            final Context context, String title, String text, Long nextBroadcastTime) {
2981ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        Intent activityIntent = new Intent(context, DeskClock.class);
2991ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3001ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX);
3011ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        PendingIntent pendingActivityIntent = PendingIntent.getActivity(context, 0, activityIntent,
3021ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
3031ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        showCollapsedNotification(context, title, text, Notification.PRIORITY_HIGH,
30406077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein                pendingActivityIntent, IN_USE_NOTIFICATION_ID, false);
3051ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3061ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (nextBroadcastTime == null) {
3071ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return;
3081ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
3091ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        Intent nextBroadcast = new Intent();
3101ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        nextBroadcast.setAction(Timers.NOTIF_IN_USE_SHOW);
3111ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        PendingIntent pendingNextBroadcast =
3121ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                PendingIntent.getBroadcast(context, 0, nextBroadcast, 0);
3131ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        AlarmManager alarmManager =
3141ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
3156ff2962d9a2726bacffa20ed09e3c3346fe7491dIsaac Katzenelson        if (Utils.isKitKatOrLater()) {
316203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas            alarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextBroadcastTime, pendingNextBroadcast);
317203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas        } else {
318203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas            alarmManager.set(AlarmManager.ELAPSED_REALTIME, nextBroadcastTime, pendingNextBroadcast);
319203bf487b2648df2edc74787d6bf2ab34d24191fRobyn Coultas        }
3201ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
3211ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3220f6e3350fed144e3909ba4e45f3006f042c0187cSam Blitzstein    private static void showCollapsedNotification(final Context context, String title, String text,
32306077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein            int priority, PendingIntent pendingIntent, int notificationId, boolean showTicker) {
32406077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein        Notification.Builder builder = new Notification.Builder(context)
3251ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setAutoCancel(false)
3261ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setContentTitle(title)
3271ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setContentText(text)
3281ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setDeleteIntent(pendingIntent)
3291ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setOngoing(true)
3301ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setPriority(priority)
3311ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        .setShowWhen(false)
33206077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein        .setSmallIcon(R.drawable.stat_notify_timer);
33306077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein        if (showTicker) {
33406077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein            builder.setTicker(text);
33506077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein        }
33606077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein
33706077d25a16684bc4d26c1ee0c48db556983c86cSam Blitzstein        Notification notification = builder.build();
3381ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        notification.contentIntent = pendingIntent;
3391ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        NotificationManager notificationManager =
3401ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
3411ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        notificationManager.notify(notificationId, notification);
3421ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
3431ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3441ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private String buildTimeRemaining(Context context, long timeLeft) {
3451ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (timeLeft < 0) {
3461ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            // We should never be here...
3471ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            Log.v(TAG, "Will not show notification for timer already expired.");
3481ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return null;
3491ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
3501ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3511ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        long hundreds, seconds, minutes, hours;
3521ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        seconds = timeLeft / 1000;
3531ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        minutes = seconds / 60;
3541ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        seconds = seconds - minutes * 60;
3551ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        hours = minutes / 60;
3561ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        minutes = minutes - hours * 60;
3571ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (hours > 99) {
3581ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            hours = 0;
3591ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
3601ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3611ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        String hourSeq = (hours == 0) ? "" :
3621ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            ( (hours == 1) ? context.getString(R.string.hour) :
3631ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                context.getString(R.string.hours, Long.toString(hours)) );
3641ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        String minSeq = (minutes == 0) ? "" :
3651ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            ( (minutes == 1) ? context.getString(R.string.minute) :
3661ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                context.getString(R.string.minutes, Long.toString(minutes)) );
3671ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3681ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        boolean dispHour = hours > 0;
3691ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        boolean dispMinute = minutes > 0;
3701ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        int index = (dispHour ? 1 : 0) | (dispMinute ? 2 : 0);
3711ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        String[] formats = context.getResources().getStringArray(R.array.timer_notifications);
3721ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        return String.format(formats[index], hourSeq, minSeq);
3731ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
3741ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
3751ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private TimerObj getNextRunningTimer(
3761ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            ArrayList<TimerObj> timers, boolean requireNextUpdate, long now) {
3771ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        long nextTimesup = Long.MAX_VALUE;
3781ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        boolean nextTimerFound = false;
3791ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        Iterator<TimerObj> i = timers.iterator();
3801ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        TimerObj t = null;
3811ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        while(i.hasNext()) {
3821ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            TimerObj tmp = i.next();
3831ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            if (tmp.mState == TimerObj.STATE_RUNNING) {
3841ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                long timesupTime = tmp.getTimesupTime();
3851ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                long timeLeft = timesupTime - now;
3861ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                if (timesupTime < nextTimesup && (!requireNextUpdate || timeLeft > 60) ) {
3871ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    nextTimesup = timesupTime;
3881ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    nextTimerFound = true;
3891ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                    t = tmp;
3901ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                }
3911ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            }
3921ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
3931ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        if (nextTimerFound) {
3941ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return t;
3951ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        } else {
3961ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson            return null;
3971ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        }
3981ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
3991ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson
4001ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    private void cancelInUseNotification(final Context context) {
4011ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        NotificationManager notificationManager =
4021ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
4031ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson        notificationManager.cancel(IN_USE_NOTIFICATION_ID);
4041ce69abc1f8bea2d7903f24ee32fec42cee83634Itzhak Katzenelson    }
405d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
406d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    private void showTimesUpNotification(final Context context) {
407d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        for (TimerObj timerObj : Timers.timersInTimesUp(mTimers) ) {
408d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            showTimesUpNotification(context, timerObj);
409d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        }
410d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    }
411d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
412d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    private void showTimesUpNotification(final Context context, TimerObj timerObj) {
413d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Content Intent. When clicked will show the timer full screen
414d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        PendingIntent contentIntent = PendingIntent.getActivity(context, timerObj.mTimerId,
415d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                new Intent(context, TimerAlertFullScreen.class).putExtra(
416d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        Timers.TIMER_INTENT_EXTRA, timerObj.mTimerId),
417d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                PendingIntent.FLAG_UPDATE_CURRENT);
418d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
419d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Add one minute action button
420d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        PendingIntent addOneMinuteAction = PendingIntent.getBroadcast(context, timerObj.mTimerId,
421d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                new Intent(Timers.NOTIF_TIMES_UP_PLUS_ONE)
422d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        .putExtra(Timers.TIMER_INTENT_EXTRA, timerObj.mTimerId),
423d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                PendingIntent.FLAG_UPDATE_CURRENT);
424d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
425d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Add stop/done action button
426d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        PendingIntent stopAction = PendingIntent.getBroadcast(context, timerObj.mTimerId,
427d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                new Intent(Timers.NOTIF_TIMES_UP_STOP)
428d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        .putExtra(Timers.TIMER_INTENT_EXTRA, timerObj.mTimerId),
429d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                PendingIntent.FLAG_UPDATE_CURRENT);
430d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
431d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Notification creation
432d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        Notification notification = new Notification.Builder(context)
433d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setContentIntent(contentIntent)
434d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .addAction(R.drawable.ic_menu_add,
435d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        context.getResources().getString(R.string.timer_plus_1_min),
436d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        addOneMinuteAction)
437d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .addAction(
438d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        timerObj.getDeleteAfterUse()
439d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                                ? android.R.drawable.ic_menu_close_clear_cancel
440d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                                : R.drawable.ic_stop_normal,
441d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        timerObj.getDeleteAfterUse()
442d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                                ? context.getResources().getString(R.string.timer_done)
443d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                                : context.getResources().getString(R.string.timer_stop),
444d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                        stopAction)
445d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setContentTitle(timerObj.getLabelOrDefault(context))
446d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setContentText(context.getResources().getString(R.string.timer_times_up))
447d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setSmallIcon(R.drawable.stat_notify_timer)
448d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setOngoing(true)
449d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setAutoCancel(false)
450d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setPriority(Notification.PRIORITY_MAX)
451d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setDefaults(Notification.DEFAULT_LIGHTS)
452d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .setWhen(0)
453d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                .build();
454d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
455d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // Send the notification using the timer's id to identify the
456d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        // correct notification
457d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(
458d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                timerObj.mTimerId, notification);
459d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        if (Timers.LOGGING) {
460d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            Log.v(TAG, "Setting times-up notification for "
461d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                    + timerObj.getLabelOrDefault(context) + " #" + timerObj.mTimerId);
462d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        }
463d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    }
464d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
465d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    private void cancelTimesUpNotification(final Context context) {
466d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        for (TimerObj timerObj : Timers.timersInTimesUp(mTimers) ) {
467d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            cancelTimesUpNotification(context, timerObj);
468d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        }
469d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    }
470d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas
471d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    private void cancelTimesUpNotification(final Context context, TimerObj timerObj) {
472d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        NotificationManager notificationManager =
473d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
474d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        notificationManager.cancel(timerObj.mTimerId);
475d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        if (Timers.LOGGING) {
476d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas            Log.v(TAG, "Canceling times-up notification for "
477d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas                    + timerObj.getLabelOrDefault(context) + " #" + timerObj.mTimerId);
478d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas        }
479d5202b46aaf96b1d217cedba645f28aa1fee04c6Robyn Coultas    }
480feabb7a335fb8c7644783d2dca53b864009054d8Robyn Coultas}
481