Home | History | Annotate | Download | only in timer
      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 
     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 
     80     public static Intent createResetExpiredTimersIntent(Context context) {
     81         return new Intent(context, TimerService.class)
     82                 .setAction(ACTION_RESET_EXPIRED_TIMERS);
     83     }
     84 
     85     public static Intent createResetUnexpiredTimersIntent(Context context) {
     86         return new Intent(context, TimerService.class)
     87                 .setAction(ACTION_RESET_UNEXPIRED_TIMERS);
     88     }
     89 
     90     public static Intent createResetMissedTimersIntent(Context context) {
     91         return new Intent(context, TimerService.class)
     92                 .setAction(ACTION_RESET_MISSED_TIMERS);
     93     }
     94 
     95 
     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 
    102     public static Intent createUpdateNotificationIntent(Context context) {
    103         return new Intent(context, TimerService.class)
    104                 .setAction(ACTION_UPDATE_NOTIFICATION);
    105     }
    106 
    107     @Override
    108     public IBinder onBind(Intent intent) {
    109         return null;
    110     }
    111 
    112     @Override
    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