149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova/* 249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * Copyright (C) 2015 The Android Open Source Project 349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * 449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * Licensed under the Apache License, Version 2.0 (the "License"); 549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * you may not use this file except in compliance with the License. 649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * You may obtain a copy of the License at 749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * 849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * http://www.apache.org/licenses/LICENSE-2.0 949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * 1049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * Unless required by applicable law or agreed to in writing, software 1149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * distributed under the License is distributed on an "AS IS" BASIS, 1249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * See the License for the specific language governing permissions and 1449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * limitations under the License. 1549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova */ 1649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 17972fca903a5272631c2cfbe5f111adc8edeace51Daria Evdokimovapackage com.android.deskclock; 1849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 1997149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimovaimport android.app.Activity; 2049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport android.content.ContentResolver; 2149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport android.content.Context; 2249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport android.content.Intent; 2349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport android.os.Looper; 2449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport android.provider.AlarmClock; 2549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 2649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport com.android.deskclock.alarms.AlarmStateManager; 27bd250dfa5bdcd618c5dfd7f48b662cf4b51c31a2James Lemieuximport com.android.deskclock.controller.Controller; 2849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport com.android.deskclock.provider.Alarm; 2949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport com.android.deskclock.provider.AlarmInstance; 3049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 311ffcbb8702c494ab8f77d7e95253f66fac4bcd6eDaria Evdokimovaimport java.text.DateFormatSymbols; 3249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport java.util.ArrayList; 3349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport java.util.Calendar; 3449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaimport java.util.List; 3549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 3649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova/** 3749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * Returns a list of alarms that are specified by the intent 38972fca903a5272631c2cfbe5f111adc8edeace51Daria Evdokimova * processed by HandleDeskClockApiCalls 3949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * if there are more than 1 matching alarms and the SEARCH_MODE is not ALL 4049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova * we show a picker UI dialog 4149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova */ 4249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimovaclass FetchMatchingAlarmsAction implements Runnable { 4349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 4449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova private final Context mContext; 4549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova private final List<Alarm> mAlarms; 4649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova private final Intent mIntent; 4749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova private final List<Alarm> mMatchingAlarms = new ArrayList<>(); 4897149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova private final Activity mActivity; 4949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 5097149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova public FetchMatchingAlarmsAction(Context context, List<Alarm> alarms, Intent intent, 5197149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova Activity activity) { 5249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova mContext = context; 5349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // only enabled alarms are passed 5449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova mAlarms = alarms; 5549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova mIntent = intent; 5697149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova mActivity = activity; 5749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 5849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 5949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova @Override 6049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova public void run() { 6150f499e72a0915950d7114e198217c8900743efdJames Lemieux Utils.enforceNotMainLooper(); 6249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 6349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final String searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE); 6449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // if search mode isn't specified show all alarms in the UI picker 6549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova if (searchMode == null) { 6649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova mMatchingAlarms.addAll(mAlarms); 6749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova return; 6849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 6949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 7049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final ContentResolver cr = mContext.getContentResolver(); 7149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova switch (searchMode) { 7249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova case AlarmClock.ALARM_SEARCH_MODE_TIME: 7349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // at least one of these has to be specified in this search mode. 7449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final int hour = mIntent.getIntExtra(AlarmClock.EXTRA_HOUR, -1); 7549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // if minutes weren't specified default to 0 7649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final int minutes = mIntent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0); 7749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final Boolean isPm = (Boolean) mIntent.getExtras().get(AlarmClock.EXTRA_IS_PM); 785b8f332d8a65a7b405306d65c075fcc8e09c6e62James Lemieux boolean badInput = isPm != null && hour > 12 && isPm; 795b8f332d8a65a7b405306d65c075fcc8e09c6e62James Lemieux badInput |= hour < 0 || hour > 23; 805b8f332d8a65a7b405306d65c075fcc8e09c6e62James Lemieux badInput |= minutes < 0 || minutes > 59; 8149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 825b8f332d8a65a7b405306d65c075fcc8e09c6e62James Lemieux if (badInput) { 831ffcbb8702c494ab8f77d7e95253f66fac4bcd6eDaria Evdokimova final String[] ampm = new DateFormatSymbols().getAmPmStrings(); 841ffcbb8702c494ab8f77d7e95253f66fac4bcd6eDaria Evdokimova final String amPm = isPm == null ? "" : (isPm ? ampm[1] : ampm[0]); 8597149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova final String reason = mContext.getString(R.string.invalid_time, hour, minutes, 8697149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova amPm); 8797149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova notifyFailureAndLog(reason, mActivity); 8849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova return; 8949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 9049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 91452b68b61244673df53ae5dc7004a46f73d92dffDaria Evdokimova final int hour24 = Boolean.TRUE.equals(isPm) && hour < 12 ? (hour + 12) : hour; 9249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 93da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova // there might me multiple alarms at the same time 9449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova for (Alarm alarm : mAlarms) { 9549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova if (alarm.hour == hour24 && alarm.minutes == minutes) { 96da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova mMatchingAlarms.add(alarm); 9749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 9849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 99da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova if (mMatchingAlarms.isEmpty()) { 10097149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova final String reason = mContext.getString(R.string.no_alarm_at, hour24, minutes); 10197149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova notifyFailureAndLog(reason, mActivity); 10249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova return; 10349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 10449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova break; 10549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova case AlarmClock.ALARM_SEARCH_MODE_NEXT: 106dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen // Match currently firing alarms before scheduled alarms. 107dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen for (Alarm alarm : mAlarms) { 108dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen final AlarmInstance alarmInstance = 109dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen AlarmInstance.getNextUpcomingInstanceByAlarmId(cr, alarm.id); 110dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen if (alarmInstance != null 111dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen && alarmInstance.mAlarmState == AlarmInstance.FIRED_STATE) { 112dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen mMatchingAlarms.add(alarm); 113dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen } 114dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen } 115dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen if (!mMatchingAlarms.isEmpty()) { 116dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen // return the matched firing alarms 117dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen return; 118dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen } 119dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen 12049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final AlarmInstance nextAlarm = AlarmStateManager.getNextFiringAlarm(mContext); 12149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova if (nextAlarm == null) { 12297149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova final String reason = mContext.getString(R.string.no_scheduled_alarms); 12397149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova notifyFailureAndLog(reason, mActivity); 12449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova return; 12549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 12649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 12749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // get time from nextAlarm and see if there are any other alarms matching this time 12849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final Calendar nextTime = nextAlarm.getAlarmTime(); 12949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final List<Alarm> alarmsFiringAtSameTime = getAlarmsByHourMinutes( 13049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova nextTime.get(Calendar.HOUR_OF_DAY), nextTime.get(Calendar.MINUTE), cr); 13149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // there might me multiple alarms firing next 13249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova mMatchingAlarms.addAll(alarmsFiringAtSameTime); 13349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova break; 13449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova case AlarmClock.ALARM_SEARCH_MODE_ALL: 13549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova mMatchingAlarms.addAll(mAlarms); 13649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova break; 137da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova case AlarmClock.ALARM_SEARCH_MODE_LABEL: 138da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova // EXTRA_MESSAGE has to be set in this mode 139da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova final String label = mIntent.getStringExtra(AlarmClock.EXTRA_MESSAGE); 140da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova if (label == null) { 141da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova final String reason = mContext.getString(R.string.no_label_specified); 142da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova notifyFailureAndLog(reason, mActivity); 143da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova return; 144da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova } 145da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova 146da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova // there might me multiple alarms with this label 147da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova for (Alarm alarm : mAlarms) { 148da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova if (alarm.label.contains(label)) { 149da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova mMatchingAlarms.add(alarm); 150da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova } 151da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova } 152da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova 153da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova if (mMatchingAlarms.isEmpty()) { 154da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova final String reason = mContext.getString(R.string.no_alarms_with_label); 155da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova notifyFailureAndLog(reason, mActivity); 156da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova return; 157da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova } 158da784fb58e49e3f02d4ca6ef9246c87c85ce96c3Daria Evdokimova break; 15949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 16049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 16149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 16249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova private List<Alarm> getAlarmsByHourMinutes(int hour24, int minutes, ContentResolver cr) { 16349b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova // if we want to dismiss we should only add enabled alarms 16449b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final String selection = String.format("%s=? AND %s=? AND %s=?", 16549b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova Alarm.HOUR, Alarm.MINUTES, Alarm.ENABLED); 16649b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova final String[] args = { String.valueOf(hour24), String.valueOf(minutes), "1" }; 16749b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova return Alarm.getAlarms(cr, selection, args); 16849b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 16949b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova 17049b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova public List<Alarm> getMatchingAlarms() { 17149b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova return mMatchingAlarms; 17249b6cba9468fe3ccc91ec0efad26810085a8cae7Daria Evdokimova } 17397149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova 17497149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova private void notifyFailureAndLog(String reason, Activity activity) { 17597149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova LogUtils.e(reason); 176bd250dfa5bdcd618c5dfd7f48b662cf4b51c31a2James Lemieux Controller.getController().notifyVoiceFailure(activity, reason); 17797149ea89a5c0e057f25b89b87eef6224b82a777Daria Evdokimova } 178dd3f9e7cab60aa26def027abd02053afdcc6c75aJustin Klaassen} 179