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.DeskClock; 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 import com.android.deskclock.uidata.UiDataModel; 30 31 import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS; 32 33 /** 34 * <p>This service exists solely to allow {@link android.app.AlarmManager} and timer notifications 35 * to alter the state of timers without disturbing the notification shade. If an activity were used 36 * instead (even one that is not displayed) the notification manager implicitly closes the 37 * notification shade which clashes with the use case of starting/pausing/resetting timers without 38 * disturbing the notification shade.</p> 39 * 40 * <p>The service has a second benefit. It is used to start heads-up notifications for expired 41 * timers in the foreground. This keeps the entire application in the foreground and thus prevents 42 * the operating system from killing it while expired timers are firing.</p> 43 */ 44 public final class TimerService extends Service { 45 46 private static final String ACTION_PREFIX = "com.android.deskclock.action."; 47 48 /** Shows the tab with timers; scrolls to a specific timer. */ 49 public static final String ACTION_SHOW_TIMER = ACTION_PREFIX + "SHOW_TIMER"; 50 /** Pauses running timers; resets expired timers. */ 51 public static final String ACTION_PAUSE_TIMER = ACTION_PREFIX + "PAUSE_TIMER"; 52 /** Starts the sole timer. */ 53 public static final String ACTION_START_TIMER = ACTION_PREFIX + "START_TIMER"; 54 /** Resets the timer. */ 55 public static final String ACTION_RESET_TIMER = ACTION_PREFIX + "RESET_TIMER"; 56 /** Adds an extra minute to the timer. */ 57 public static final String ACTION_ADD_MINUTE_TIMER = ACTION_PREFIX + "ADD_MINUTE_TIMER"; 58 59 /** Extra for many actions specific to a given timer. */ 60 public static final String EXTRA_TIMER_ID = "com.android.deskclock.extra.TIMER_ID"; 61 62 private static final String ACTION_TIMER_EXPIRED = 63 ACTION_PREFIX + "TIMER_EXPIRED"; 64 private static final String ACTION_UPDATE_NOTIFICATION = 65 ACTION_PREFIX + "UPDATE_NOTIFICATION"; 66 private static final String ACTION_RESET_EXPIRED_TIMERS = 67 ACTION_PREFIX + "RESET_EXPIRED_TIMERS"; 68 private static final String ACTION_RESET_UNEXPIRED_TIMERS = 69 ACTION_PREFIX + "RESET_UNEXPIRED_TIMERS"; 70 private static final String ACTION_RESET_MISSED_TIMERS = 71 ACTION_PREFIX + "RESET_MISSED_TIMERS"; 72 createTimerExpiredIntent(Context context, Timer timer)73 public static Intent createTimerExpiredIntent(Context context, Timer timer) { 74 final int timerId = timer == null ? -1 : timer.getId(); 75 return new Intent(context, TimerService.class) 76 .setAction(ACTION_TIMER_EXPIRED) 77 .putExtra(EXTRA_TIMER_ID, timerId); 78 } 79 createResetExpiredTimersIntent(Context context)80 public static Intent createResetExpiredTimersIntent(Context context) { 81 return new Intent(context, TimerService.class) 82 .setAction(ACTION_RESET_EXPIRED_TIMERS); 83 } 84 createResetUnexpiredTimersIntent(Context context)85 public static Intent createResetUnexpiredTimersIntent(Context context) { 86 return new Intent(context, TimerService.class) 87 .setAction(ACTION_RESET_UNEXPIRED_TIMERS); 88 } 89 createResetMissedTimersIntent(Context context)90 public static Intent createResetMissedTimersIntent(Context context) { 91 return new Intent(context, TimerService.class) 92 .setAction(ACTION_RESET_MISSED_TIMERS); 93 } 94 95 createAddMinuteTimerIntent(Context context, int timerId)96 public static Intent createAddMinuteTimerIntent(Context context, int timerId) { 97 return new Intent(context, TimerService.class) 98 .setAction(ACTION_ADD_MINUTE_TIMER) 99 .putExtra(EXTRA_TIMER_ID, timerId); 100 } 101 createUpdateNotificationIntent(Context context)102 public static Intent createUpdateNotificationIntent(Context context) { 103 return new Intent(context, TimerService.class) 104 .setAction(ACTION_UPDATE_NOTIFICATION); 105 } 106 107 @Override onBind(Intent intent)108 public IBinder onBind(Intent intent) { 109 return null; 110 } 111 112 @Override onStartCommand(Intent intent, int flags, int startId)113 public int onStartCommand(Intent intent, int flags, int startId) { 114 try { 115 final String action = intent.getAction(); 116 final int label = intent.getIntExtra(Events.EXTRA_EVENT_LABEL, R.string.label_intent); 117 switch (action) { 118 case ACTION_UPDATE_NOTIFICATION: { 119 DataModel.getDataModel().updateTimerNotification(); 120 return START_NOT_STICKY; 121 } 122 case ACTION_RESET_EXPIRED_TIMERS: { 123 DataModel.getDataModel().resetOrDeleteExpiredTimers(label); 124 return START_NOT_STICKY; 125 } 126 case ACTION_RESET_UNEXPIRED_TIMERS: { 127 DataModel.getDataModel().resetUnexpiredTimers(label); 128 return START_NOT_STICKY; 129 } 130 case ACTION_RESET_MISSED_TIMERS: { 131 DataModel.getDataModel().resetMissedTimers(label); 132 return START_NOT_STICKY; 133 } 134 } 135 136 // Look up the timer in question. 137 final int timerId = intent.getIntExtra(EXTRA_TIMER_ID, -1); 138 final Timer timer = DataModel.getDataModel().getTimer(timerId); 139 140 // If the timer cannot be located, ignore the action. 141 if (timer == null) { 142 return START_NOT_STICKY; 143 } 144 145 // Perform the action on the timer. 146 switch (action) { 147 case ACTION_SHOW_TIMER: { 148 Events.sendTimerEvent(R.string.action_show, label); 149 150 // Change to the timers tab. 151 UiDataModel.getUiDataModel().setSelectedTab(TIMERS); 152 153 // Open DeskClock which is now positioned on the timers tab and show the timer 154 // in question. 155 final Intent showTimers = new Intent(this, DeskClock.class) 156 .putExtra(EXTRA_TIMER_ID, timerId) 157 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 158 startActivity(showTimers); 159 break; 160 } case ACTION_START_TIMER: { 161 Events.sendTimerEvent(R.string.action_start, label); 162 DataModel.getDataModel().startTimer(this, timer); 163 break; 164 } case ACTION_PAUSE_TIMER: { 165 Events.sendTimerEvent(R.string.action_pause, label); 166 DataModel.getDataModel().pauseTimer(timer); 167 break; 168 } case ACTION_ADD_MINUTE_TIMER: { 169 Events.sendTimerEvent(R.string.action_add_minute, label); 170 DataModel.getDataModel().addTimerMinute(timer); 171 break; 172 } case ACTION_RESET_TIMER: { 173 DataModel.getDataModel().resetOrDeleteTimer(timer, label); 174 break; 175 } case ACTION_TIMER_EXPIRED: { 176 Events.sendTimerEvent(R.string.action_fire, label); 177 DataModel.getDataModel().expireTimer(this, timer); 178 break; 179 } 180 } 181 } finally { 182 // This service is foreground when expired timers exist and stopped when none exist. 183 if (DataModel.getDataModel().getExpiredTimers().isEmpty()) { 184 stopSelf(); 185 } 186 } 187 188 return START_NOT_STICKY; 189 } 190 } 191