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 }