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