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
17package android.app.test;
18
19import static org.mockito.Mockito.any;
20import static org.mockito.Mockito.anyInt;
21import static org.mockito.Mockito.anyLong;
22import static org.mockito.Mockito.anyString;
23import static org.mockito.Mockito.doAnswer;
24import static org.mockito.Mockito.mock;
25
26import android.app.AlarmManager;
27import android.app.test.MockAnswerUtil.AnswerWithArguments;
28import android.os.Handler;
29
30import java.util.ArrayList;
31import java.util.Iterator;
32import java.util.List;
33import java.util.Objects;
34
35/**
36 * Creates an AlarmManager whose alarm dispatch can be controlled
37 * Currently only supports alarm listeners
38 *
39 * Alarm listeners will be dispatched to the handler provided or will
40 * be dispatched immediately if they would have been sent to the main
41 * looper (handler was null).
42 */
43public class TestAlarmManager {
44    private final AlarmManager mAlarmManager;
45    private final List<PendingAlarm> mPendingAlarms;
46
47    public TestAlarmManager() throws Exception {
48        mPendingAlarms = new ArrayList<>();
49
50        mAlarmManager = mock(AlarmManager.class);
51        doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
52                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
53        doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
54                anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
55        doAnswer(new CancelListenerAnswer())
56                .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
57    }
58
59    public AlarmManager getAlarmManager() {
60        return mAlarmManager;
61    }
62
63    /**
64     * Dispatch a pending alarm with the given tag
65     * @return if any alarm was dispatched
66     */
67    public boolean dispatch(String tag) {
68        for (int i = 0; i < mPendingAlarms.size(); ++i) {
69            PendingAlarm alarm = mPendingAlarms.get(i);
70            if (Objects.equals(tag, alarm.getTag())) {
71                mPendingAlarms.remove(i);
72                alarm.dispatch();
73                return true;
74            }
75        }
76        return false;
77    }
78
79    /**
80     * @return if an alarm with the given tag is pending
81     */
82    public boolean isPending(String tag) {
83        for (int i = 0; i < mPendingAlarms.size(); ++i) {
84            PendingAlarm alarm = mPendingAlarms.get(i);
85            if (Objects.equals(tag, alarm.getTag())) {
86                return true;
87            }
88        }
89        return false;
90    }
91
92    /**
93     * @return trigger time of an pending alarm with the given tag
94     *         -1 if no pending alarm with the given tag
95     */
96    public long getTriggerTimeMillis(String tag) {
97        for (int i = 0; i < mPendingAlarms.size(); ++i) {
98            PendingAlarm alarm = mPendingAlarms.get(i);
99            if (Objects.equals(tag, alarm.getTag())) {
100                return alarm.getTriggerTimeMillis();
101            }
102        }
103        return -1;
104    }
105
106    private static class PendingAlarm {
107        private final int mType;
108        private final long mTriggerAtMillis;
109        private final String mTag;
110        private final Runnable mCallback;
111
112        public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
113            mType = type;
114            mTriggerAtMillis = triggerAtMillis;
115            mTag = tag;
116            mCallback = callback;
117        }
118
119        public void dispatch() {
120            if (mCallback != null) {
121                mCallback.run();
122            }
123        }
124
125        public Runnable getCallback() {
126            return mCallback;
127        }
128
129        public String getTag() {
130            return mTag;
131        }
132
133        public long getTriggerTimeMillis() {
134            return mTriggerAtMillis;
135        }
136    }
137
138    private class SetListenerAnswer extends AnswerWithArguments {
139        public void answer(int type, long triggerAtMillis, String tag,
140                AlarmManager.OnAlarmListener listener, Handler handler) {
141            mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
142                            new AlarmListenerRunnable(listener, handler)));
143        }
144    }
145
146    private class CancelListenerAnswer extends AnswerWithArguments {
147        public void answer(AlarmManager.OnAlarmListener listener) {
148            Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
149            while (alarmItr.hasNext()) {
150                PendingAlarm alarm = alarmItr.next();
151                if (alarm.getCallback() instanceof AlarmListenerRunnable) {
152                    AlarmListenerRunnable alarmCallback =
153                            (AlarmListenerRunnable) alarm.getCallback();
154                    if (alarmCallback.getListener() == listener) {
155                        alarmItr.remove();
156                    }
157                }
158            }
159        }
160    }
161
162    private static class AlarmListenerRunnable implements Runnable {
163        private final AlarmManager.OnAlarmListener mListener;
164        private final Handler mHandler;
165        public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
166            mListener = listener;
167            mHandler = handler;
168        }
169
170        public AlarmManager.OnAlarmListener getListener() {
171            return mListener;
172        }
173
174        @Override
175        public void run() {
176            if (mHandler != null) {
177                mHandler.post(new Runnable() {
178                        @Override
179                        public void run() {
180                            mListener.onAlarm();
181                        }
182                    });
183            } else { // normally gets dispatched in main looper
184                mListener.onAlarm();
185            }
186        }
187    }
188}
189