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