summaryrefslogtreecommitdiffstats
path: root/src/com/android/deskclock/timer/TimerService.java
blob: 8671d90dfc7cf07bf6cf0dedbdfbf58b4232ba9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
 * Copyright (C) 2015 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.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;

import com.android.deskclock.DeskClock;
import com.android.deskclock.R;
import com.android.deskclock.data.DataModel;
import com.android.deskclock.data.Timer;
import com.android.deskclock.events.Events;
import com.android.deskclock.uidata.UiDataModel;

import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS;

/**
 * <p>This service exists solely to allow {@link android.app.AlarmManager} and timer notifications
 * to alter the state of timers without disturbing the notification shade. If an activity were used
 * instead (even one that is not displayed) the notification manager implicitly closes the
 * notification shade which clashes with the use case of starting/pausing/resetting timers without
 * disturbing the notification shade.</p>
 *
 * <p>The service has a second benefit. It is used to start heads-up notifications for expired
 * timers in the foreground. This keeps the entire application in the foreground and thus prevents
 * the operating system from killing it while expired timers are firing.</p>
 */
public final class TimerService extends Service {

    private static final String ACTION_PREFIX = "com.android.deskclock.action.";

    /** Shows the tab with timers; scrolls to a specific timer. */
    public static final String ACTION_SHOW_TIMER = ACTION_PREFIX + "SHOW_TIMER";
    /** Pauses running timers; resets expired timers. */
    public static final String ACTION_PAUSE_TIMER = ACTION_PREFIX + "PAUSE_TIMER";
    /** Starts the sole timer. */
    public static final String ACTION_START_TIMER = ACTION_PREFIX + "START_TIMER";
    /** Resets the timer. */
    public static final String ACTION_RESET_TIMER = ACTION_PREFIX + "RESET_TIMER";
    /** Adds an extra minute to the timer. */
    public static final String ACTION_ADD_MINUTE_TIMER = ACTION_PREFIX + "ADD_MINUTE_TIMER";

    /** Extra for many actions specific to a given timer. */
    public static final String EXTRA_TIMER_ID = "com.android.deskclock.extra.TIMER_ID";

    private static final String ACTION_TIMER_EXPIRED =
            ACTION_PREFIX + "TIMER_EXPIRED";
    private static final String ACTION_UPDATE_NOTIFICATION =
            ACTION_PREFIX + "UPDATE_NOTIFICATION";
    private static final String ACTION_RESET_EXPIRED_TIMERS =
            ACTION_PREFIX + "RESET_EXPIRED_TIMERS";
    private static final String ACTION_RESET_UNEXPIRED_TIMERS =
            ACTION_PREFIX + "RESET_UNEXPIRED_TIMERS";
    private static final String ACTION_RESET_MISSED_TIMERS =
            ACTION_PREFIX + "RESET_MISSED_TIMERS";

    public static Intent createTimerExpiredIntent(Context context, Timer timer) {
        final int timerId = timer == null ? -1 : timer.getId();
        return new Intent(context, TimerService.class)
                .setAction(ACTION_TIMER_EXPIRED)
                .putExtra(EXTRA_TIMER_ID, timerId);
    }

    public static Intent createResetExpiredTimersIntent(Context context) {
        return new Intent(context, TimerService.class)
                .setAction(ACTION_RESET_EXPIRED_TIMERS);
    }

    public static Intent createResetUnexpiredTimersIntent(Context context) {
        return new Intent(context, TimerService.class)
                .setAction(ACTION_RESET_UNEXPIRED_TIMERS);
    }

    public static Intent createResetMissedTimersIntent(Context context) {
        return new Intent(context, TimerService.class)
                .setAction(ACTION_RESET_MISSED_TIMERS);
    }


    public static Intent createAddMinuteTimerIntent(Context context, int timerId) {
        return new Intent(context, TimerService.class)
                .setAction(ACTION_ADD_MINUTE_TIMER)
                .putExtra(EXTRA_TIMER_ID, timerId);
    }

    public static Intent createUpdateNotificationIntent(Context context) {
        return new Intent(context, TimerService.class)
                .setAction(ACTION_UPDATE_NOTIFICATION);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        try {
            final String action = intent.getAction();
            final int label = intent.getIntExtra(Events.EXTRA_EVENT_LABEL, R.string.label_intent);
            switch (action) {
                case ACTION_UPDATE_NOTIFICATION: {
                    DataModel.getDataModel().updateTimerNotification();
                    return START_NOT_STICKY;
                }
                case ACTION_RESET_EXPIRED_TIMERS: {
                    DataModel.getDataModel().resetExpiredTimers(label);
                    return START_NOT_STICKY;
                }
                case ACTION_RESET_UNEXPIRED_TIMERS: {
                    DataModel.getDataModel().resetUnexpiredTimers(label);
                    return START_NOT_STICKY;
                }
                case ACTION_RESET_MISSED_TIMERS: {
                    DataModel.getDataModel().resetMissedTimers(label);
                    return START_NOT_STICKY;
                }
            }

            // Look up the timer in question.
            final int timerId = intent.getIntExtra(EXTRA_TIMER_ID, -1);
            final Timer timer = DataModel.getDataModel().getTimer(timerId);

            // If the timer cannot be located, ignore the action.
            if (timer == null) {
                return START_NOT_STICKY;
            }

            // Perform the action on the timer.
            switch (action) {
                case ACTION_SHOW_TIMER: {
                    Events.sendTimerEvent(R.string.action_show, label);

                    // Change to the timers tab.
                    UiDataModel.getUiDataModel().setSelectedTab(TIMERS);

                    // Open DeskClock which is now positioned on the timers tab and show the timer
                    // in question.
                    final Intent showTimers = new Intent(this, DeskClock.class)
                            .putExtra(EXTRA_TIMER_ID, timerId)
                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(showTimers);
                    break;
                } case ACTION_START_TIMER: {
                    Events.sendTimerEvent(R.string.action_start, label);
                    DataModel.getDataModel().startTimer(this, timer);
                    break;
                } case ACTION_PAUSE_TIMER: {
                    Events.sendTimerEvent(R.string.action_pause, label);
                    DataModel.getDataModel().pauseTimer(timer);
                    break;
                } case ACTION_ADD_MINUTE_TIMER: {
                    Events.sendTimerEvent(R.string.action_add_minute, label);
                    DataModel.getDataModel().addTimerMinute(timer);
                    break;
                } case ACTION_RESET_TIMER: {
                    DataModel.getDataModel().resetOrDeleteTimer(timer, label);
                    break;
                } case ACTION_TIMER_EXPIRED: {
                    Events.sendTimerEvent(R.string.action_fire, label);
                    DataModel.getDataModel().expireTimer(this, timer);
                    break;
                }
            }
        } finally {
            // This service is foreground when expired timers exist and stopped when none exist.
            if (DataModel.getDataModel().getExpiredTimers().isEmpty()) {
                stopSelf();
            }
        }

        return START_NOT_STICKY;
    }
}