1 /*
2  * Copyright (C) 2020 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.server.alarm;
18 
19 import static android.app.AlarmManager.ELAPSED_REALTIME;
20 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
21 import static android.app.AlarmManager.RTC;
22 import static android.app.AlarmManager.RTC_WAKEUP;
23 
24 import static com.android.server.alarm.AlarmManagerService.PRIORITY_NORMAL;
25 import static com.android.server.alarm.AlarmManagerService.addClampPositive;
26 
27 import android.app.AlarmManager;
28 import android.app.IAlarmListener;
29 import android.app.PendingIntent;
30 import android.os.Bundle;
31 import android.os.WorkSource;
32 import android.util.IndentingPrintWriter;
33 import android.util.TimeUtils;
34 import android.util.proto.ProtoOutputStream;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.text.SimpleDateFormat;
39 import java.util.Arrays;
40 import java.util.Date;
41 
42 /**
43  * Class to describe an alarm that is used to the set the kernel timer that returns when the timer
44  * expires. The timer will wake up the device if the alarm is a "wakeup" alarm.
45  */
46 class Alarm {
47     @VisibleForTesting
48     public static final int NUM_POLICIES = 4;
49     /**
50      * Index used to store the time the alarm was requested to expire. To be used with
51      * {@link #setPolicyElapsed(int, long)}.
52      */
53     public static final int REQUESTER_POLICY_INDEX = 0;
54     /**
55      * Index used to store the earliest time the alarm can expire based on app-standby policy.
56      * To be used with {@link #setPolicyElapsed(int, long)}.
57      */
58     public static final int APP_STANDBY_POLICY_INDEX = 1;
59     /**
60      * Index used to store the earliest time the alarm can expire based on the device's doze policy.
61      * To be used with {@link #setPolicyElapsed(int, long)}.
62      */
63     public static final int DEVICE_IDLE_POLICY_INDEX = 2;
64 
65     /**
66      * Index used to store the earliest time the alarm can expire based on battery saver policy.
67      * To be used with {@link #setPolicyElapsed(int, long)}.
68      */
69     public static final int BATTERY_SAVER_POLICY_INDEX = 3;
70 
71     /**
72      * Reason to use for inexact alarms.
73      */
74     static final int EXACT_ALLOW_REASON_NOT_APPLICABLE = -1;
75     /**
76      * Caller had SCHEDULE_EXACT_ALARM permission.
77      */
78     static final int EXACT_ALLOW_REASON_PERMISSION = 0;
79     /**
80      * Caller was in the power allow-list.
81      */
82     static final int EXACT_ALLOW_REASON_ALLOW_LIST = 1;
83     /**
84      * Change wasn't enable for the caller due to compat reasons.
85      */
86     static final int EXACT_ALLOW_REASON_COMPAT = 2;
87     /**
88      * Caller had USE_EXACT_ALARM permission.
89      */
90     static final int EXACT_ALLOW_REASON_POLICY_PERMISSION = 3;
91     /**
92      * Caller used a listener alarm, which does not need permission to be exact.
93      */
94     static final int EXACT_ALLOW_REASON_LISTENER = 4;
95     /**
96      * Caller used a prioritized alarm, which does not need permission to be exact.
97      */
98     static final int EXACT_ALLOW_REASON_PRIORITIZED = 5;
99 
100     public final int type;
101     /**
102      * The original trigger time supplied by the caller. This can be in the elapsed or rtc time base
103      * depending on the type of this alarm
104      */
105     public final long origWhen;
106     public final boolean wakeup;
107     public final PendingIntent operation;
108     public final IAlarmListener listener;
109     public final String listenerTag;
110     public final String statsTag;
111     public final WorkSource workSource;
112     public final int flags;
113     public final AlarmManager.AlarmClockInfo alarmClock;
114     public final int uid;
115     public final int creatorUid;
116     public final String packageName;
117     public final String sourcePackage;
118     public final long windowLength;
119     public final long repeatInterval;
120     public int count;
121     /** The earliest time this alarm is eligible to fire according to each policy */
122     private long[] mPolicyWhenElapsed;
123     /** The ultimate delivery time to be used for this alarm */
124     private long mWhenElapsed;
125     private long mMaxWhenElapsed;
126     public int exactAllowReason;
127     @AlarmManagerService.DispatchPriority
128     public int priorityClass;
129     /** Broadcast options to use when delivering this alarm */
130     public Bundle mIdleOptions;
131     public boolean mUsingReserveQuota;
132 
Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval, PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags, AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions, int exactAllowReason)133     Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
134             PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
135             AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions,
136             int exactAllowReason) {
137         this.type = type;
138         origWhen = when;
139         wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
140                 || type == AlarmManager.RTC_WAKEUP;
141         mPolicyWhenElapsed = new long[NUM_POLICIES];
142         mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed;
143         mWhenElapsed = requestedWhenElapsed;
144         this.windowLength = windowLength;
145         mMaxWhenElapsed = addClampPositive(requestedWhenElapsed, windowLength);
146         repeatInterval = interval;
147         operation = op;
148         listener = rec;
149         this.listenerTag = listenerTag;
150         statsTag = makeTag(op, listenerTag, type);
151         workSource = ws;
152         this.flags = flags;
153         alarmClock = info;
154         this.uid = uid;
155         packageName = pkgName;
156         mIdleOptions = idleOptions;
157         this.exactAllowReason = exactAllowReason;
158         sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
159         creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
160         mUsingReserveQuota = false;
161         priorityClass = PRIORITY_NORMAL;
162     }
163 
makeTag(PendingIntent pi, String tag, int type)164     public static String makeTag(PendingIntent pi, String tag, int type) {
165         final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
166                 ? "*walarm*:" : "*alarm*:";
167         return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
168     }
169 
170     // Returns true if either matches
matches(PendingIntent pi, IAlarmListener rec)171     public boolean matches(PendingIntent pi, IAlarmListener rec) {
172         return (operation != null)
173                 ? operation.equals(pi)
174                 : rec != null && listener.asBinder().equals(rec.asBinder());
175     }
176 
matches(String packageName)177     public boolean matches(String packageName) {
178         return packageName.equals(sourcePackage);
179     }
180 
181     /**
182      * Get the earliest time this alarm is allowed to expire based on the given policy.
183      *
184      * @param policyIndex The index of the policy. One of [{@link #REQUESTER_POLICY_INDEX},
185      *                    {@link #APP_STANDBY_POLICY_INDEX}].
186      */
187     @VisibleForTesting
getPolicyElapsed(int policyIndex)188     long getPolicyElapsed(int policyIndex) {
189         return mPolicyWhenElapsed[policyIndex];
190     }
191 
192     /**
193      * @return the time this alarm was requested to go off in the elapsed time base.
194      */
getRequestedElapsed()195     public long getRequestedElapsed() {
196         return mPolicyWhenElapsed[REQUESTER_POLICY_INDEX];
197     }
198 
199     /**
200      * Get the earliest time that this alarm should be delivered to the requesting app.
201      */
getWhenElapsed()202     public long getWhenElapsed() {
203         return mWhenElapsed;
204     }
205 
206     /**
207      * Get the latest time that this alarm should be delivered to the requesting app. Will be equal
208      * to {@link #getWhenElapsed()} in case this is an exact alarm.
209      */
getMaxWhenElapsed()210     public long getMaxWhenElapsed() {
211         return mMaxWhenElapsed;
212     }
213 
214     /**
215      * Set the earliest time this alarm can expire based on the passed policy index.
216      *
217      * @return {@code true} if this change resulted in a change in the ultimate delivery time (or
218      * time window in the case of inexact alarms) of this alarm.
219      * @see #getWhenElapsed()
220      * @see #getMaxWhenElapsed()
221      * @see #getPolicyElapsed(int)
222      */
setPolicyElapsed(int policyIndex, long policyElapsed)223     public boolean setPolicyElapsed(int policyIndex, long policyElapsed) {
224         mPolicyWhenElapsed[policyIndex] = policyElapsed;
225         return updateWhenElapsed();
226     }
227 
228     /**
229      * @return {@code true} if either {@link #mWhenElapsed} or {@link #mMaxWhenElapsed} changes
230      * due to this call.
231      */
updateWhenElapsed()232     private boolean updateWhenElapsed() {
233         final long oldWhenElapsed = mWhenElapsed;
234         mWhenElapsed = 0;
235         for (int i = 0; i < NUM_POLICIES; i++) {
236             mWhenElapsed = Math.max(mWhenElapsed, mPolicyWhenElapsed[i]);
237         }
238 
239         final long oldMaxWhenElapsed = mMaxWhenElapsed;
240         // windowLength should always be >= 0 here.
241         final long maxRequestedElapsed = addClampPositive(
242                 mPolicyWhenElapsed[REQUESTER_POLICY_INDEX], windowLength);
243         mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed);
244 
245         return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed);
246     }
247 
248     @Override
toString()249     public String toString() {
250         StringBuilder sb = new StringBuilder(128);
251         sb.append("Alarm{");
252         sb.append(Integer.toHexString(System.identityHashCode(this)));
253         sb.append(" type ");
254         sb.append(type);
255         sb.append(" origWhen ");
256         sb.append(origWhen);
257         sb.append(" whenElapsed ");
258         sb.append(getWhenElapsed());
259         sb.append(" ");
260         sb.append(sourcePackage);
261         sb.append('}');
262         return sb.toString();
263     }
264 
policyIndexToString(int index)265     static String policyIndexToString(int index) {
266         switch (index) {
267             case REQUESTER_POLICY_INDEX:
268                 return "requester";
269             case APP_STANDBY_POLICY_INDEX:
270                 return "app_standby";
271             case DEVICE_IDLE_POLICY_INDEX:
272                 return "device_idle";
273             case BATTERY_SAVER_POLICY_INDEX:
274                 return "battery_saver";
275             default:
276                 return "--unknown(" + index + ")--";
277         }
278     }
279 
exactReasonToString(int reason)280     private static String exactReasonToString(int reason) {
281         switch (reason) {
282             case EXACT_ALLOW_REASON_ALLOW_LIST:
283                 return "allow-listed";
284             case EXACT_ALLOW_REASON_COMPAT:
285                 return "compat";
286             case EXACT_ALLOW_REASON_PERMISSION:
287                 return "permission";
288             case EXACT_ALLOW_REASON_POLICY_PERMISSION:
289                 return "policy_permission";
290             case EXACT_ALLOW_REASON_LISTENER:
291                 return "listener";
292             case EXACT_ALLOW_REASON_PRIORITIZED:
293                 return "prioritized";
294             case EXACT_ALLOW_REASON_NOT_APPLICABLE:
295                 return "N/A";
296             default:
297                 return "--unknown--";
298         }
299     }
300 
typeToString(int type)301     public static String typeToString(int type) {
302         switch (type) {
303             case RTC:
304                 return "RTC";
305             case RTC_WAKEUP:
306                 return "RTC_WAKEUP";
307             case ELAPSED_REALTIME:
308                 return "ELAPSED";
309             case ELAPSED_REALTIME_WAKEUP:
310                 return "ELAPSED_WAKEUP";
311             default:
312                 return "--unknown--";
313         }
314     }
315 
dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf)316     public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) {
317         final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
318         ipw.print("tag=");
319         ipw.println(statsTag);
320 
321         ipw.print("type=");
322         ipw.print(typeToString(type));
323         ipw.print(" origWhen=");
324         if (isRtc) {
325             ipw.print(sdf.format(new Date(origWhen)));
326         } else {
327             TimeUtils.formatDuration(origWhen, nowELAPSED, ipw);
328         }
329         ipw.print(" window=");
330         TimeUtils.formatDuration(windowLength, ipw);
331         if (exactAllowReason != EXACT_ALLOW_REASON_NOT_APPLICABLE) {
332             ipw.print(" exactAllowReason=");
333             ipw.print(exactReasonToString(exactAllowReason));
334         }
335         ipw.print(" repeatInterval=");
336         ipw.print(repeatInterval);
337         ipw.print(" count=");
338         ipw.print(count);
339         ipw.print(" flags=0x");
340         ipw.println(Integer.toHexString(flags));
341 
342         ipw.print("policyWhenElapsed:");
343         for (int i = 0; i < NUM_POLICIES; i++) {
344             ipw.print(" " + policyIndexToString(i) + "=");
345             TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowELAPSED, ipw);
346         }
347         ipw.println();
348 
349         ipw.print("whenElapsed=");
350         TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw);
351         ipw.print(" maxWhenElapsed=");
352         TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw);
353         if (mUsingReserveQuota) {
354             ipw.print(" usingReserveQuota=true");
355         }
356         ipw.println();
357 
358         if (alarmClock != null) {
359             ipw.println("Alarm clock:");
360 
361             ipw.print("  triggerTime=");
362             ipw.println(sdf.format(new Date(alarmClock.getTriggerTime())));
363 
364             ipw.print("  showIntent=");
365             ipw.println(alarmClock.getShowIntent());
366         }
367         if (operation != null) {
368             ipw.print("operation=");
369             ipw.println(operation);
370         }
371         if (listener != null) {
372             ipw.print("listener=");
373             ipw.println(listener.asBinder());
374         }
375         if (mIdleOptions != null) {
376             ipw.print("idle-options=");
377             ipw.println(mIdleOptions.toString());
378         }
379     }
380 
dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed)381     public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) {
382         final long token = proto.start(fieldId);
383 
384         proto.write(AlarmProto.TAG, statsTag);
385         proto.write(AlarmProto.TYPE, type);
386         proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, getWhenElapsed() - nowElapsed);
387         proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
388         proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
389         proto.write(AlarmProto.COUNT, count);
390         proto.write(AlarmProto.FLAGS, flags);
391         if (alarmClock != null) {
392             alarmClock.dumpDebug(proto, AlarmProto.ALARM_CLOCK);
393         }
394         if (operation != null) {
395             operation.dumpDebug(proto, AlarmProto.OPERATION);
396         }
397         if (listener != null) {
398             proto.write(AlarmProto.LISTENER, listener.asBinder().toString());
399         }
400 
401         proto.end(token);
402     }
403 
404     /**
405      * Stores a snapshot of an alarm at any given time to be used for logging and diagnostics.
406      * This should intentionally avoid holding pointers to objects like {@link Alarm#operation}.
407      */
408     static class Snapshot {
409         final int mType;
410         final String mTag;
411         final long[] mPolicyWhenElapsed;
412 
Snapshot(Alarm a)413         Snapshot(Alarm a) {
414             mType = a.type;
415             mTag = a.statsTag;
416             mPolicyWhenElapsed = Arrays.copyOf(a.mPolicyWhenElapsed, NUM_POLICIES);
417         }
418 
dump(IndentingPrintWriter pw, long nowElapsed)419         void dump(IndentingPrintWriter pw, long nowElapsed) {
420             pw.print("type", typeToString(mType));
421             pw.print("tag", mTag);
422             pw.println();
423             pw.print("policyWhenElapsed:");
424             for (int i = 0; i < NUM_POLICIES; i++) {
425                 pw.print(" " + policyIndexToString(i) + "=");
426                 TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowElapsed, pw);
427             }
428             pw.println();
429         }
430     }
431 }
432