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.alarms 18 19 import android.app.Service 20 import android.content.BroadcastReceiver 21 import android.content.ContentResolver 22 import android.content.Context 23 import android.content.Intent 24 import android.content.IntentFilter 25 import android.os.Binder 26 import android.os.IBinder 27 import android.telephony.PhoneStateListener 28 import android.telephony.TelephonyManager 29 30 import com.android.deskclock.AlarmAlertWakeLock 31 import com.android.deskclock.LogUtils 32 import com.android.deskclock.R 33 import com.android.deskclock.events.Events 34 import com.android.deskclock.provider.AlarmInstance 35 import com.android.deskclock.provider.ClockContract.InstancesColumns 36 37 /** 38 * This service is in charge of starting/stopping the alarm. It will bring up and manage the 39 * [AlarmActivity] as well as [AlarmKlaxon]. 40 * 41 * Registers a broadcast receiver to listen for snooze/dismiss intents. The broadcast receiver 42 * exits early if AlarmActivity is bound to prevent double-processing of the snooze/dismiss intents. 43 */ 44 class AlarmService : Service() { 45 /** Binder given to AlarmActivity. */ 46 private val mBinder: IBinder = Binder() 47 48 /** Whether the service is currently bound to AlarmActivity */ 49 private var mIsBound = false 50 51 /** Listener for changes in phone state. */ 52 private val mPhoneStateListener = PhoneStateChangeListener() 53 54 /** Whether the receiver is currently registered */ 55 private var mIsRegistered = false 56 onBindnull57 override fun onBind(intent: Intent?): IBinder { 58 mIsBound = true 59 return mBinder 60 } 61 onUnbindnull62 override fun onUnbind(intent: Intent?): Boolean { 63 mIsBound = false 64 return super.onUnbind(intent) 65 } 66 67 private lateinit var mTelephonyManager: TelephonyManager 68 private var mCurrentAlarm: AlarmInstance? = null 69 startAlarmnull70 private fun startAlarm(instance: AlarmInstance) { 71 LogUtils.v("AlarmService.start with instance: " + instance.mId) 72 if (mCurrentAlarm != null) { 73 AlarmStateManager.setMissedState(this, mCurrentAlarm!!) 74 stopCurrentAlarm() 75 } 76 77 AlarmAlertWakeLock.acquireCpuWakeLock(this) 78 79 mCurrentAlarm = instance 80 AlarmNotifications.showAlarmNotification(this, mCurrentAlarm!!) 81 mTelephonyManager.listen(mPhoneStateListener.init(), PhoneStateListener.LISTEN_CALL_STATE) 82 AlarmKlaxon.start(this, mCurrentAlarm!!) 83 sendBroadcast(Intent(ALARM_ALERT_ACTION)) 84 } 85 stopCurrentAlarmnull86 private fun stopCurrentAlarm() { 87 if (mCurrentAlarm == null) { 88 LogUtils.v("There is no current alarm to stop") 89 return 90 } 91 92 val instanceId = mCurrentAlarm!!.mId 93 LogUtils.v("AlarmService.stop with instance: %s", instanceId) 94 95 AlarmKlaxon.stop(this) 96 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE) 97 sendBroadcast(Intent(ALARM_DONE_ACTION)) 98 99 stopForeground(true /* removeNotification */) 100 101 mCurrentAlarm = null 102 AlarmAlertWakeLock.releaseCpuLock() 103 } 104 105 private val mActionsReceiver: BroadcastReceiver = object : BroadcastReceiver() { onReceivenull106 override fun onReceive(context: Context, intent: Intent) { 107 val action: String? = intent.getAction() 108 LogUtils.i("AlarmService received intent %s", action) 109 if (mCurrentAlarm == null || 110 mCurrentAlarm!!.mAlarmState != InstancesColumns.FIRED_STATE) { 111 LogUtils.i("No valid firing alarm") 112 return 113 } 114 115 if (mIsBound) { 116 LogUtils.i("AlarmActivity bound; AlarmService no-op") 117 return 118 } 119 120 when (action) { 121 ALARM_SNOOZE_ACTION -> { 122 // Set the alarm state to snoozed. 123 // If this broadcast receiver is handling the snooze intent then AlarmActivity 124 // must not be showing, so always show snooze toast. 125 AlarmStateManager.setSnoozeState(context, mCurrentAlarm!!, true /* showToast */) 126 Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent) 127 } 128 ALARM_DISMISS_ACTION -> { 129 // Set the alarm state to dismissed. 130 AlarmStateManager.deleteInstanceAndUpdateParent(context, mCurrentAlarm!!) 131 Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent) 132 } 133 } 134 } 135 } 136 onCreatenull137 override fun onCreate() { 138 super.onCreate() 139 mTelephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager 140 141 // Register the broadcast receiver 142 val filter = IntentFilter(ALARM_SNOOZE_ACTION) 143 filter.addAction(ALARM_DISMISS_ACTION) 144 registerReceiver(mActionsReceiver, filter) 145 mIsRegistered = true 146 } 147 onStartCommandnull148 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 149 LogUtils.v("AlarmService.onStartCommand() with %s", intent) 150 if (intent == null) { 151 return Service.START_NOT_STICKY 152 } 153 154 val instanceId = AlarmInstance.getId(intent.getData()!!) 155 when (intent.getAction()) { 156 AlarmStateManager.CHANGE_STATE_ACTION -> { 157 AlarmStateManager.handleIntent(this, intent) 158 159 // If state is changed to firing, actually fire the alarm! 160 val alarmState: Int = intent.getIntExtra(AlarmStateManager.ALARM_STATE_EXTRA, -1) 161 if (alarmState == InstancesColumns.FIRED_STATE) { 162 val cr: ContentResolver = this.getContentResolver() 163 val instance: AlarmInstance? = AlarmInstance.getInstance(cr, instanceId) 164 if (instance == null) { 165 LogUtils.e("No instance found to start alarm: %d", instanceId) 166 if (mCurrentAlarm != null) { 167 // Only release lock if we are not firing alarm 168 AlarmAlertWakeLock.releaseCpuLock() 169 } 170 } else if (mCurrentAlarm != null && mCurrentAlarm!!.mId == instanceId) { 171 LogUtils.e("Alarm already started for instance: %d", instanceId) 172 } else { 173 startAlarm(instance) 174 } 175 } 176 } 177 STOP_ALARM_ACTION -> { 178 if (mCurrentAlarm != null && mCurrentAlarm!!.mId != instanceId) { 179 LogUtils.e("Can't stop alarm for instance: %d because current alarm is: %d", 180 instanceId, mCurrentAlarm!!.mId) 181 } else { 182 stopCurrentAlarm() 183 stopSelf() 184 } 185 } 186 } 187 188 return Service.START_NOT_STICKY 189 } 190 onDestroynull191 override fun onDestroy() { 192 LogUtils.v("AlarmService.onDestroy() called") 193 super.onDestroy() 194 if (mCurrentAlarm != null) { 195 stopCurrentAlarm() 196 } 197 198 if (mIsRegistered) { 199 unregisterReceiver(mActionsReceiver) 200 mIsRegistered = false 201 } 202 } 203 204 private inner class PhoneStateChangeListener : PhoneStateListener() { 205 private var mPhoneCallState = 0 206 initnull207 fun init(): PhoneStateChangeListener { 208 mPhoneCallState = -1 209 return this 210 } 211 onCallStateChangednull212 override fun onCallStateChanged(state: Int, ignored: String?) { 213 if (mPhoneCallState == -1) { 214 mPhoneCallState = state 215 } 216 217 if (state != TelephonyManager.CALL_STATE_IDLE && state != mPhoneCallState) { 218 startService(AlarmStateManager.createStateChangeIntent(this@AlarmService, 219 "AlarmService", mCurrentAlarm!!, InstancesColumns.MISSED_STATE)) 220 } 221 } 222 } 223 224 companion object { 225 /** 226 * AlarmActivity and AlarmService (when unbound) listen for this broadcast intent 227 * so that other applications can snooze the alarm (after ALARM_ALERT_ACTION and before 228 * ALARM_DONE_ACTION). 229 */ 230 const val ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE" 231 232 /** 233 * AlarmActivity and AlarmService listen for this broadcast intent so that other 234 * applications can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION). 235 */ 236 const val ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS" 237 238 /** A public action sent by AlarmService when the alarm has started. */ 239 const val ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT" 240 241 /** A public action sent by AlarmService when the alarm has stopped for any reason. */ 242 const val ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE" 243 244 /** Private action used to stop an alarm with this service. */ 245 const val STOP_ALARM_ACTION = "STOP_ALARM" 246 247 /** 248 * Utility method to help stop an alarm properly. Nothing will happen, if alarm is not firing 249 * or using a different instance. 250 * 251 * @param context application context 252 * @param instance you are trying to stop 253 */ 254 @JvmStatic stopAlarmnull255 fun stopAlarm(context: Context, instance: AlarmInstance) { 256 val intent: Intent = 257 AlarmInstance.createIntent(context, AlarmService::class.java, instance.mId) 258 .setAction(STOP_ALARM_ACTION) 259 260 // We don't need a wake lock here, since we are trying to kill an alarm 261 context.startService(intent) 262 } 263 } 264 }