TimerReceiver.java revision 4fd80b58da8ad66e0e4a23cbf7c900174727fb4e
1815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson/*
2815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * Copyright (C) 2012 The Android Open Source Project
3815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson *
4815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * Licensed under the Apache License, Version 2.0 (the "License");
5815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * you may not use this file except in compliance with the License.
6815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * You may obtain a copy of the License at
7815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson *
8815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson *      http://www.apache.org/licenses/LICENSE-2.0
9815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson *
10815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * Unless required by applicable law or agreed to in writing, software
11815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * distributed under the License is distributed on an "AS IS" BASIS,
12815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * See the License for the specific language governing permissions and
14815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson * limitations under the License.
15815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson */
16815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
17815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonpackage com.android.deskclock.timer;
18815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
19dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelsonimport android.app.AlarmManager;
20815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonimport android.app.Notification;
21b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzsteinimport android.app.NotificationManager;
22815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonimport android.app.PendingIntent;
23815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonimport android.content.BroadcastReceiver;
24815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonimport android.content.Context;
25815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonimport android.content.Intent;
26dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelsonimport android.content.SharedPreferences;
27dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelsonimport android.preference.PreferenceManager;
28815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonimport android.util.Log;
29815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
30dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelsonimport com.android.deskclock.DeskClock;
31b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzsteinimport com.android.deskclock.R;
325cacdd0b56950cf6a5f28e780ed07625a81bde53Isaac Katzenelsonimport com.android.deskclock.TimerRingService;
337cff0e83debe70133aad4cf22163263b16d4a255Isaac Katzenelsonimport com.android.deskclock.Utils;
34dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
35dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelsonimport java.util.ArrayList;
36dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelsonimport java.util.Iterator;
37dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
38815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelsonpublic class TimerReceiver extends BroadcastReceiver {
39815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson    private static final String TAG = "TimerReceiver";
40815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
4158c329f52d7952597ae7a4e88fc2ec9e15ddd93aRobyn Coultas    // Make this a large number to avoid the alarm ID's which seem to be 1, 2, ...
4258c329f52d7952597ae7a4e88fc2ec9e15ddd93aRobyn Coultas    // Must also be different than StopwatchService.NOTIFICATION_ID
4358c329f52d7952597ae7a4e88fc2ec9e15ddd93aRobyn Coultas    private static final int IN_USE_NOTIFICATION_ID = Integer.MAX_VALUE - 2;
44b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
45dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    ArrayList<TimerObj> mTimers;
46dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
47dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    @Override
48815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson    public void onReceive(final Context context, final Intent intent) {
49b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        int timer;
50b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        String actionType = intent.getAction();
51815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
52b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        // Get the updated timers data.
53b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (mTimers == null) {
54b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            mTimers = new ArrayList<TimerObj> ();
55b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
56b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
57b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        TimerObj.getTimersFromSharedPrefs(prefs, mTimers);
58815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
59815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
60b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (intent.hasExtra(Timers.TIMER_INTENT_EXTRA)) {
61b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            // Get the alarm out of the Intent
62b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            timer = intent.getIntExtra(Timers.TIMER_INTENT_EXTRA, -1);
63b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            if (timer == -1) {
64b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                Log.d(TAG, " got intent without Timer data: "+actionType);
65b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            }
66b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        } else if (Timers.NOTIF_IN_USE_SHOW.equals(actionType)){
67b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            showInUseNotification(context);
68b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return;
69b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        } else if (Timers.NOTIF_IN_USE_CANCEL.equals(actionType)) {
70b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            cancelInUseNotification(context);
71b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return;
72b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        } else {
73b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            // No data to work with, do nothing
74b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            Log.d(TAG, " got intent without Timer data");
75b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return;
76b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
77b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
78b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        TimerObj t = Timers.findTimer(mTimers, timer);
79815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson
80b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (intent.getBooleanExtra(Timers.UPDATE_NOTIFICATION, false)) {
81b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            if (Timers.TIMER_STOP.equals(actionType)) {
82b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                if (t == null) {
83b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    Log.d(TAG, "timer not found in list - can't stop it.");
84b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    return;
85b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                }
86b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                t.mState = TimerObj.STATE_DONE;
87b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                t.writeToSharedPref(prefs);
88b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                SharedPreferences.Editor editor = prefs.edit();
89b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                editor.putBoolean(Timers.FROM_NOTIFICATION, true);
907cff0e83debe70133aad4cf22163263b16d4a255Isaac Katzenelson                editor.putLong(Timers.NOTIF_TIME, Utils.getTimeNow());
91b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                editor.putInt(Timers.NOTIF_ID, timer);
92b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                editor.apply();
93dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
94b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                stopRingtoneIfNoTimesup(context);
95dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
96b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                Intent activityIntent = new Intent(context, DeskClock.class);
97b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
98b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX);
99b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                context.startActivity(activityIntent);
100b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            }
101b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein             return;
102b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
103b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
104b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (Timers.TIMES_UP.equals(actionType)) {
105dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            // Find the timer (if it doesn't exists, it was probably deleted).
106dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            if (t == null) {
107dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson                Log.d(TAG, " timer not found in list - do nothing");
108dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson                return;
109dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            }
110dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
111dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            t.mState = TimerObj.STATE_TIMESUP;
112dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            t.writeToSharedPref(prefs);
113733afe52e37148f2f1d36436c1a1eadcad5b471bRobyn Coultas            // Play ringtone by using TimerRingService service with a default alarm.
114dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            Log.d(TAG, "playing ringtone");
115dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            Intent si = new Intent();
1165cacdd0b56950cf6a5f28e780ed07625a81bde53Isaac Katzenelson            si.setClass(context, TimerRingService.class);
117dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            context.startService(si);
118dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
11998b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            // Update the in-use notification
1207cff0e83debe70133aad4cf22163263b16d4a255Isaac Katzenelson            if (getNextRunningTimer(mTimers, false, Utils.getTimeNow()) == null) {
121b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                // Found no running timers.
122b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                cancelInUseNotification(context);
12398b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            } else {
12498b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas                showInUseNotification(context);
125dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            }
126b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
12798b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            // Start the TimerAlertFullScreen activity.
12898b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            Intent timersAlert = new Intent(context, TimerAlertFullScreen.class);
12998b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            timersAlert.setFlags(
13098b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
13198b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            context.startActivity(timersAlert);
132b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        } else if (Timers.TIMER_RESET.equals(actionType)
133b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                || Timers.DELETE_TIMER.equals(actionType)
134b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                || Timers.TIMER_DONE.equals(actionType)) {
13598b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas            // Stop Ringtone if all timers are not in times-up status
136b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            stopRingtoneIfNoTimesup(context);
137dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        }
138dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        // Update the next "Times up" alarm
139b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        updateNextTimesup(context);
140b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
141b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
142b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private void stopRingtoneIfNoTimesup(final Context context) {
143b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (Timers.findExpiredTimer(mTimers) == null) {
144b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            // Stop ringtone
145b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            Log.d(TAG, "stopping ringtone");
146b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            Intent si = new Intent();
147b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            si.setClass(context, TimerRingService.class);
148b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            context.stopService(si);
149b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
150815e2f7431c590086d5bd4eee5d7bf08108c77ebIsaac Katzenelson    }
151dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
152dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    // Scan all timers and find the one that will expire next.
153dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    // Tell AlarmManager to send a "Time's up" message to this receiver when this timer expires.
154dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    // If no timer exists, clear "time's up" message.
155dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    private void updateNextTimesup(Context context) {
1567cff0e83debe70133aad4cf22163263b16d4a255Isaac Katzenelson        TimerObj t = getNextRunningTimer(mTimers, false, Utils.getTimeNow());
157b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        long nextTimesup = (t == null) ? -1 : t.getTimesupTime();
158b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        int timerId = (t == null) ? -1 : t.mTimerId;
159dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
160dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        Intent intent = new Intent();
161dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        intent.setAction(Timers.TIMES_UP);
162dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        intent.setClass(context, TimerReceiver.class);
163b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (!mTimers.isEmpty()) {
164b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            intent.putExtra(Timers.TIMER_INTENT_EXTRA, timerId);
165dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        }
166dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        AlarmManager mngr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
167dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        PendingIntent p = PendingIntent.getBroadcast(context,
168dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson                0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
169b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (t != null) {
1704fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas            if (Utils.isKeyLimePieOrLater()) {
1714fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas                mngr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTimesup, p);
1724fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas            } else {
1734fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas                mngr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTimesup, p);
1744fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas            }
175dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            Log.d(TAG,"Setting times up to " + nextTimesup);
176dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        } else {
177dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            Log.d(TAG,"canceling times up");
178dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson            mngr.cancel(p);
179dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson        }
180dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson    }
181dfb182f455aa07eb7319d32df6d9a14492884e2fIsaac Katzenelson
182b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private void showInUseNotification(final Context context) {
183b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
184b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        boolean appOpen = prefs.getBoolean(Timers.NOTIF_APP_OPEN, false);
185b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        ArrayList<TimerObj> timersInUse = Timers.timersInUse(mTimers);
186b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        int numTimersInUse = timersInUse.size();
187b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
188b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (appOpen || numTimersInUse == 0) {
189b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return;
190b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
191b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
192b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        String title, contentText;
193b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        Long nextBroadcastTime = null;
1947cff0e83debe70133aad4cf22163263b16d4a255Isaac Katzenelson        long now = Utils.getTimeNow();
195b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (timersInUse.size() == 1) {
196b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            TimerObj timer = timersInUse.get(0);
197d82ec8feb07d6d1f0f9539f75b1b84b3ae1e74d4Sam Blitzstein            boolean timerIsTicking = timer.isTicking();
19872332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein            String label = timer.mLabel.equals("") ?
19972332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein                    context.getString(R.string.timer_notification_label) : timer.mLabel;
200d82ec8feb07d6d1f0f9539f75b1b84b3ae1e74d4Sam Blitzstein            title = timerIsTicking ? label : context.getString(R.string.timer_stopped);
201d82ec8feb07d6d1f0f9539f75b1b84b3ae1e74d4Sam Blitzstein            long timeLeft = timerIsTicking ? timer.getTimesupTime() - now : timer.mTimeLeft;
202b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            contentText = buildTimeRemaining(context, timeLeft);
203d82ec8feb07d6d1f0f9539f75b1b84b3ae1e74d4Sam Blitzstein            if (timerIsTicking && timeLeft > 60 * 1000) {
204b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                nextBroadcastTime = getBroadcastTime(now, timeLeft);
205b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            }
206b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        } else {
207b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            TimerObj timer = getNextRunningTimer(timersInUse, false, now);
208b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            if (timer == null) {
209b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                // No running timers.
210b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                title = String.format(
211b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                        context.getString(R.string.timers_stopped), numTimersInUse);
212b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                contentText = context.getString(R.string.all_timers_stopped_notif);
213b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            } else {
214b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                // We have at least one timer running and other timers stopped.
215b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                title = String.format(
216b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                        context.getString(R.string.timers_in_use), numTimersInUse);
217b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                long completionTime = timer.getTimesupTime();
218b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                long timeLeft = completionTime - now;
219b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                contentText = String.format(context.getString(R.string.next_timer_notif),
220b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                        buildTimeRemaining(context, timeLeft));
221d82ec8feb07d6d1f0f9539f75b1b84b3ae1e74d4Sam Blitzstein                if (timeLeft <= 60 * 1000) {
222b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    TimerObj timerWithUpdate = getNextRunningTimer(timersInUse, true, now);
223b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    if (timerWithUpdate != null) {
224b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                        completionTime = timerWithUpdate.getTimesupTime();
225b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                        timeLeft = completionTime - now;
226b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                        nextBroadcastTime = getBroadcastTime(now, timeLeft);
227b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    }
228b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                } else {
229b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    nextBroadcastTime = getBroadcastTime(now, timeLeft);
230b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                }
231b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            }
232b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
233b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        showCollapsedNotificationWithNext(context, title, contentText, nextBroadcastTime);
234b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
235b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
236b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private long getBroadcastTime(long now, long timeUntilBroadcast) {
237b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        long seconds = timeUntilBroadcast / 1000;
238b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        seconds = seconds - ( (seconds / 60) * 60 );
239b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        return now + (seconds * 1000);
240b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
241b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
24272332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein    /** Public and static to allow timer fragment to update notification with new label. **/
24372332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein    public static void showExpiredAlarmNotification(Context context, TimerObj t) {
24472332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        Intent broadcastIntent = new Intent();
24572332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        broadcastIntent.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId);
24672332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        broadcastIntent.setAction(Timers.TIMER_STOP);
24772332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        broadcastIntent.putExtra(Timers.UPDATE_NOTIFICATION, true);
24872332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        PendingIntent pendingBroadcastIntent = PendingIntent.getBroadcast(
24972332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein                context, 0, broadcastIntent, 0);
25072332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        String label = t.mLabel.equals("") ? context.getString(R.string.timer_notification_label) :
25172332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein            t.mLabel;
25272332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        String contentText = context.getString(R.string.timer_times_up);
25372332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein        showCollapsedNotification(context, label, contentText, Notification.PRIORITY_MAX,
25472332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein                pendingBroadcastIntent, t.mTimerId, true);
25572332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein    }
25672332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein
257b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private void showCollapsedNotificationWithNext(
258b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            final Context context, String title, String text, Long nextBroadcastTime) {
259b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        Intent activityIntent = new Intent(context, DeskClock.class);
260b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
261b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        activityIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX);
262b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        PendingIntent pendingActivityIntent = PendingIntent.getActivity(context, 0, activityIntent,
263b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
264b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        showCollapsedNotification(context, title, text, Notification.PRIORITY_HIGH,
265184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein                pendingActivityIntent, IN_USE_NOTIFICATION_ID, false);
266b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
267b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (nextBroadcastTime == null) {
268b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return;
269b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
270b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        Intent nextBroadcast = new Intent();
271b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        nextBroadcast.setAction(Timers.NOTIF_IN_USE_SHOW);
272b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        PendingIntent pendingNextBroadcast =
273b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                PendingIntent.getBroadcast(context, 0, nextBroadcast, 0);
274b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        AlarmManager alarmManager =
275b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
2764fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas        if (Utils.isKeyLimePieOrLater()) {
2774fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas            alarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextBroadcastTime, pendingNextBroadcast);
2784fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas        } else {
2794fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas            alarmManager.set(AlarmManager.ELAPSED_REALTIME, nextBroadcastTime, pendingNextBroadcast);
2804fd80b58da8ad66e0e4a23cbf7c900174727fb4eRobyn Coultas        }
281b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
282b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
28372332c2290d17c344ece074726f72b35ea469f31Sam Blitzstein    private static void showCollapsedNotification(final Context context, String title, String text,
284184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein            int priority, PendingIntent pendingIntent, int notificationId, boolean showTicker) {
285184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein        Notification.Builder builder = new Notification.Builder(context)
286b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setAutoCancel(false)
287b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setContentTitle(title)
288b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setContentText(text)
289b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setDeleteIntent(pendingIntent)
290b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setOngoing(true)
291b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setPriority(priority)
292b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        .setShowWhen(false)
293184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein        .setSmallIcon(R.drawable.stat_notify_timer);
294184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein        if (showTicker) {
295184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein            builder.setTicker(text);
296184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein        }
297184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein
298184aa695ef173081a692be38d9b23560ad34b789Sam Blitzstein        Notification notification = builder.build();
299b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        notification.contentIntent = pendingIntent;
300b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        NotificationManager notificationManager =
301b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
302b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        notificationManager.notify(notificationId, notification);
303b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
304b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
305b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private String buildTimeRemaining(Context context, long timeLeft) {
306b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (timeLeft < 0) {
307b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            // We should never be here...
308b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            Log.v(TAG, "Will not show notification for timer already expired.");
309b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return null;
310b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
311b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
312b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        long hundreds, seconds, minutes, hours;
313b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        seconds = timeLeft / 1000;
314b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        minutes = seconds / 60;
315b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        seconds = seconds - minutes * 60;
316b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        hours = minutes / 60;
317b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        minutes = minutes - hours * 60;
318b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (hours > 99) {
319b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            hours = 0;
320b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
321b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
322b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        String hourSeq = (hours == 0) ? "" :
323b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            ( (hours == 1) ? context.getString(R.string.hour) :
324b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                context.getString(R.string.hours, Long.toString(hours)) );
325b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        String minSeq = (minutes == 0) ? "" :
326b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            ( (minutes == 1) ? context.getString(R.string.minute) :
327b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                context.getString(R.string.minutes, Long.toString(minutes)) );
328b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
329b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        boolean dispHour = hours > 0;
330b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        boolean dispMinute = minutes > 0;
331b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        int index = (dispHour ? 1 : 0) | (dispMinute ? 2 : 0);
332b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        String[] formats = context.getResources().getStringArray(R.array.timer_notifications);
333b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        return String.format(formats[index], hourSeq, minSeq);
334b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
335b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
336b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private TimerObj getNextRunningTimer(
337b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            ArrayList<TimerObj> timers, boolean requireNextUpdate, long now) {
338b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        long nextTimesup = Long.MAX_VALUE;
339b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        boolean nextTimerFound = false;
340b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        Iterator<TimerObj> i = timers.iterator();
341b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        TimerObj t = null;
342b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        while(i.hasNext()) {
343b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            TimerObj tmp = i.next();
344b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            if (tmp.mState == TimerObj.STATE_RUNNING) {
345b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                long timesupTime = tmp.getTimesupTime();
346b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                long timeLeft = timesupTime - now;
347b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                if (timesupTime < nextTimesup && (!requireNextUpdate || timeLeft > 60) ) {
348b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    nextTimesup = timesupTime;
349b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    nextTimerFound = true;
350b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                    t = tmp;
351b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                }
352b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            }
353b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
354b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        if (nextTimerFound) {
355b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return t;
356b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        } else {
357b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein            return null;
358b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        }
359b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
360b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein
361b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    private void cancelInUseNotification(final Context context) {
362b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        NotificationManager notificationManager =
363b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
364b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein        notificationManager.cancel(IN_USE_NOTIFICATION_ID);
365b6a815a52fd31a96b51e51ef6275a68bed73c2c5Sam Blitzstein    }
36698b2b05e7a0cebcd6e065bdd0b25cb6eb86b497cRobyn Coultas}
367