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