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 }