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