1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.deskclock.timer;
18 
19 import android.app.Service;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.IBinder;
23 
24 import com.android.deskclock.HandleDeskClockApiCalls;
25 import com.android.deskclock.R;
26 import com.android.deskclock.data.DataModel;
27 import com.android.deskclock.data.Timer;
28 import com.android.deskclock.events.Events;
29 
30 /**
31  * <p>This service exists solely to allow {@link android.app.AlarmManager} and timer notifications
32  * to alter the state of timers without disturbing the notification shade. If an activity were used
33  * instead (even one that is not displayed) the notification manager implicitly closes the
34  * notification shade which clashes with the use case of starting/pausing/resetting timers without
35  * disturbing the notification shade.</p>
36  *
37  * <p>The service has a second benefit. It is used to start heads-up notifications for expired
38  * timers in the foreground. This keeps the entire application in the foreground and thus prevents
39  * the operating system from killing it while expired timers are firing.</p>
40  */
41 public final class TimerService extends Service {
42 
43     private static final String ACTION_PREFIX = "com.android.deskclock.action.";
44 
45     private static final String ACTION_TIMER_EXPIRED = ACTION_PREFIX + "TIMER_EXPIRED";
46     private static final String ACTION_UPDATE_NOTIFICATION = ACTION_PREFIX + "UPDATE_NOTIFICATION";
47     private static final String ACTION_RESET_EXPIRED_TIMERS = ACTION_PREFIX +
48             "RESET_EXPIRED_TIMERS";
49     private static final String ACTION_RESET_UNEXPIRED_TIMERS = ACTION_PREFIX +
50             "RESET_UNEXPIRED_TIMERS";
51 
createTimerExpiredIntent(Context context, Timer timer)52     public static Intent createTimerExpiredIntent(Context context, Timer timer) {
53         final int timerId = timer == null ? -1 : timer.getId();
54         return new Intent(context, TimerService.class)
55                 .setAction(ACTION_TIMER_EXPIRED)
56                 .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timerId);
57     }
58 
createResetExpiredTimersIntent(Context context)59     public static Intent createResetExpiredTimersIntent(Context context) {
60         return new Intent(context, TimerService.class)
61                 .setAction(ACTION_RESET_EXPIRED_TIMERS);
62     }
63 
createResetUnexpiredTimersIntent(Context context)64     public static Intent createResetUnexpiredTimersIntent(Context context) {
65         return new Intent(context, TimerService.class)
66                 .setAction(ACTION_RESET_UNEXPIRED_TIMERS);
67     }
68 
createAddMinuteTimerIntent(Context context, int timerId)69     public static Intent createAddMinuteTimerIntent(Context context, int timerId) {
70         return new Intent(context, TimerService.class)
71                 .setAction(HandleDeskClockApiCalls.ACTION_ADD_MINUTE_TIMER)
72                 .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timerId);
73     }
74 
createUpdateNotificationIntent(Context context)75     public static Intent createUpdateNotificationIntent(Context context) {
76         return new Intent(context, TimerService.class)
77                 .setAction(ACTION_UPDATE_NOTIFICATION);
78     }
79 
80     @Override
onBind(Intent intent)81     public IBinder onBind(Intent intent) {
82         return null;
83     }
84 
85     @Override
onStartCommand(Intent intent, int flags, int startId)86     public int onStartCommand(Intent intent, int flags, int startId) {
87         try {
88             switch (intent.getAction()) {
89                 case ACTION_UPDATE_NOTIFICATION: {
90                     DataModel.getDataModel().updateTimerNotification();
91                     return START_NOT_STICKY;
92                 }
93                 case ACTION_RESET_EXPIRED_TIMERS: {
94                     DataModel.getDataModel().resetExpiredTimers(R.string.label_notification);
95                     return START_NOT_STICKY;
96                 }
97                 case ACTION_RESET_UNEXPIRED_TIMERS: {
98                     DataModel.getDataModel().resetUnexpiredTimers(R.string.label_notification);
99                     return START_NOT_STICKY;
100                 }
101             }
102 
103             // Look up the timer in question.
104             final int timerId = intent.getIntExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, -1);
105             final Timer timer = DataModel.getDataModel().getTimer(timerId);
106 
107             // If the timer cannot be located, ignore the action.
108             if (timer == null) {
109                 return START_NOT_STICKY;
110             }
111 
112             // Perform the action on the timer.
113             switch (intent.getAction()) {
114                 case HandleDeskClockApiCalls.ACTION_START_TIMER:
115                     DataModel.getDataModel().startTimer(timer);
116                     Events.sendTimerEvent(R.string.action_start, R.string.label_notification);
117                     break;
118                 case HandleDeskClockApiCalls.ACTION_PAUSE_TIMER:
119                     DataModel.getDataModel().pauseTimer(timer);
120                     Events.sendTimerEvent(R.string.action_pause, R.string.label_notification);
121                     break;
122                 case HandleDeskClockApiCalls.ACTION_ADD_MINUTE_TIMER:
123                     DataModel.getDataModel().addTimerMinute(timer);
124                     Events.sendTimerEvent(R.string.action_add_minute, R.string.label_notification);
125                     break;
126                 case HandleDeskClockApiCalls.ACTION_RESET_TIMER:
127                     DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_notification);
128                     break;
129                 case ACTION_TIMER_EXPIRED:
130                     DataModel.getDataModel().expireTimer(this, timer);
131                     Events.sendTimerEvent(R.string.action_fire, R.string.label_intent);
132                     break;
133             }
134         } finally {
135             // This service is foreground when expired timers exist and stopped when none exist.
136             if (DataModel.getDataModel().getExpiredTimers().isEmpty()) {
137                 stopSelf();
138             }
139         }
140 
141         return START_NOT_STICKY;
142     }
143 }