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.internal.util;
18 
19 import android.app.AlarmManager;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.os.Message;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26  /**
27  * An AlarmListener that sends the specified message to a Handler and keeps the system awake until
28  * the message is processed.
29  *
30  * This is useful when using the AlarmManager direct callback interface to wake up the system and
31  * request that an object whose API consists of messages (such as a StateMachine) perform some
32  * action.
33  *
34  * In this situation, using AlarmManager.onAlarmListener by itself will wake up the system to send
35  * the message, but does not guarantee that the system will be awake until the target object has
36  * processed it. This is because as soon as the onAlarmListener sends the message and returns, the
37  * AlarmManager releases its wakelock and the system is free to go to sleep again.
38  */
39 public class WakeupMessage implements AlarmManager.OnAlarmListener {
40     private final AlarmManager mAlarmManager;
41 
42     @VisibleForTesting
43     protected final Handler mHandler;
44     @VisibleForTesting
45     protected final String mCmdName;
46     @VisibleForTesting
47     protected final int mCmd, mArg1, mArg2;
48     private boolean mScheduled;
49 
WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1, int arg2)50     public WakeupMessage(Context context, Handler handler,
51             String cmdName, int cmd, int arg1, int arg2) {
52         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
53         mHandler = handler;
54         mCmdName = cmdName;
55         mCmd = cmd;
56         mArg1 = arg1;
57         mArg2 = arg2;
58     }
59 
WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1)60     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
61         this(context, handler, cmdName, cmd, arg1, 0);
62     }
63 
WakeupMessage(Context context, Handler handler, String cmdName, int cmd)64     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
65         this(context, handler, cmdName, cmd, 0, 0);
66     }
67 
68     /**
69      * Schedule the message to be delivered at the time in milliseconds of the
70      * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
71      * the device when it goes off. If schedule is called multiple times without the message being
72      * dispatched then the alarm is rescheduled to the new time.
73      */
schedule(long when)74     public synchronized void schedule(long when) {
75         mAlarmManager.setExact(
76                 AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
77         mScheduled = true;
78     }
79 
80     /**
81      * Cancel all pending messages. This includes alarms that may have been fired, but have not been
82      * run on the handler yet.
83      */
cancel()84     public synchronized void cancel() {
85         if (mScheduled) {
86             mAlarmManager.cancel(this);
87             mScheduled = false;
88         }
89     }
90 
91     @Override
onAlarm()92     public void onAlarm() {
93         // Once this method is called the alarm has already been fired and removed from
94         // AlarmManager (it is still partially tracked, but only for statistics). The alarm can now
95         // be marked as unscheduled so that it can be rescheduled in the message handler.
96         final boolean stillScheduled;
97         synchronized (this) {
98             stillScheduled = mScheduled;
99             mScheduled = false;
100         }
101         if (stillScheduled) {
102             Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2);
103             mHandler.handleMessage(msg);
104             msg.recycle();
105         }
106     }
107 }
108