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
18 
19 import android.app.Activity
20 import android.content.ContentResolver
21 import android.content.Context
22 import android.content.Intent
23 import android.provider.AlarmClock
24 
25 import com.android.deskclock.alarms.AlarmStateManager
26 import com.android.deskclock.controller.Controller
27 import com.android.deskclock.provider.Alarm
28 import com.android.deskclock.provider.AlarmInstance
29 import com.android.deskclock.provider.ClockContract.AlarmsColumns
30 import com.android.deskclock.provider.ClockContract.InstancesColumns
31 
32 import java.text.DateFormatSymbols
33 import java.util.Calendar
34 
35 /**
36  * Returns a list of alarms that are specified by the intent
37  * processed by HandleDeskClockApiCalls
38  * if there are more than 1 matching alarms and the SEARCH_MODE is not ALL
39  * we show a picker UI dialog
40  */
41 internal class FetchMatchingAlarmsAction(
42     private val mContext: Context,
43     private val mAlarms: List<Alarm>,
44     private val mIntent: Intent,
45     private val mActivity: Activity
46 ) : Runnable {
47     private val mMatchingAlarms: MutableList<Alarm> = ArrayList()
48 
runnull49     override fun run() {
50         Utils.enforceNotMainLooper()
51 
52         val searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE)
53         // if search mode isn't specified show all alarms in the UI picker
54         if (searchMode == null) {
55             mMatchingAlarms.addAll(mAlarms)
56             return
57         }
58 
59         val cr = mContext.contentResolver
60         when (searchMode) {
61             AlarmClock.ALARM_SEARCH_MODE_TIME -> {
62                 // at least one of these has to be specified in this search mode.
63                 val hour = mIntent.getIntExtra(AlarmClock.EXTRA_HOUR, -1)
64                 // if minutes weren't specified default to 0
65                 val minutes = mIntent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0)
66                 val isPm = mIntent.extras!![AlarmClock.EXTRA_IS_PM] as Boolean?
67                 var badInput = isPm != null && hour > 12 && isPm
68                 badInput = badInput or (hour < 0 || hour > 23)
69                 badInput = badInput or (minutes < 0 || minutes > 59)
70 
71                 if (badInput) {
72                     val ampm = DateFormatSymbols().amPmStrings
73                     val amPm = if (isPm == null) "" else (if (isPm) ampm[1] else ampm[0])
74                     val reason = mContext.getString(R.string.invalid_time, hour, minutes, amPm)
75                     notifyFailureAndLog(reason, mActivity)
76                     return
77                 }
78 
79                 val hour24 = if (java.lang.Boolean.TRUE == isPm && hour < 12) hour + 12 else hour
80 
81                 // there might me multiple alarms at the same time
82                 for (alarm in mAlarms) {
83                     if (alarm.hour == hour24 && alarm.minutes == minutes) {
84                         mMatchingAlarms.add(alarm)
85                     }
86                 }
87                 if (mMatchingAlarms.isEmpty()) {
88                     val reason = mContext.getString(R.string.no_alarm_at, hour24, minutes)
89                     notifyFailureAndLog(reason, mActivity)
90                     return
91                 }
92             }
93             AlarmClock.ALARM_SEARCH_MODE_NEXT -> {
94                 // Match currently firing alarms before scheduled alarms.
95                 for (alarm in mAlarms) {
96                     val alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId(cr, alarm.id)
97                     if (alarmInstance != null &&
98                             alarmInstance.mAlarmState == InstancesColumns.FIRED_STATE) {
99                         mMatchingAlarms.add(alarm)
100                     }
101                 }
102                 if (mMatchingAlarms.isNotEmpty()) {
103                     // return the matched firing alarms
104                     return
105                 }
106                 val nextAlarm = AlarmStateManager.getNextFiringAlarm(mContext)
107                 if (nextAlarm == null) {
108                     val reason = mContext.getString(R.string.no_scheduled_alarms)
109                     notifyFailureAndLog(reason, mActivity)
110                     return
111                 }
112 
113                 // get time from nextAlarm and see if there are any other alarms matching this time
114                 val nextTime: Calendar = nextAlarm.alarmTime
115                 val alarmsFiringAtSameTime = getAlarmsByHourMinutes(
116                         nextTime[Calendar.HOUR_OF_DAY], nextTime[Calendar.MINUTE], cr)
117                 // there might me multiple alarms firing next
118                 mMatchingAlarms.addAll(alarmsFiringAtSameTime)
119             }
120             AlarmClock.ALARM_SEARCH_MODE_ALL -> mMatchingAlarms.addAll(mAlarms)
121             AlarmClock.ALARM_SEARCH_MODE_LABEL -> {
122                 // EXTRA_MESSAGE has to be set in this mode
123                 val label = mIntent.getStringExtra(AlarmClock.EXTRA_MESSAGE)
124                 if (label == null) {
125                     val reason = mContext.getString(R.string.no_label_specified)
126                     notifyFailureAndLog(reason, mActivity)
127                     return
128                 }
129 
130                 // there might me multiple alarms with this label
131                 for (alarm in mAlarms) {
132                     if (alarm.label!!.contains(label)) {
133                         mMatchingAlarms.add(alarm)
134                     }
135                 }
136 
137                 if (mMatchingAlarms.isEmpty()) {
138                     val reason = mContext.getString(R.string.no_alarms_with_label)
139                     notifyFailureAndLog(reason, mActivity)
140                     return
141                 }
142             }
143         }
144     }
145 
getAlarmsByHourMinutesnull146     private fun getAlarmsByHourMinutes(
147         hour24: Int,
148         minutes: Int,
149         cr: ContentResolver
150     ): List<Alarm> {
151         // if we want to dismiss we should only add enabled alarms
152         val selection = String.format("%s=? AND %s=? AND %s=?",
153                 AlarmsColumns.HOUR, AlarmsColumns.MINUTES, AlarmsColumns.ENABLED)
154         val args = arrayOf(hour24.toString(), minutes.toString(), "1")
155         return Alarm.getAlarms(cr, selection, *args)
156     }
157 
158     val matchingAlarms: List<Alarm>
159         get() = mMatchingAlarms
160 
notifyFailureAndLognull161     private fun notifyFailureAndLog(reason: String, activity: Activity) {
162         LogUtils.e(reason)
163         Controller.getController().notifyVoiceFailure(activity, reason)
164     }
165 }