| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.alarm; |
| |
| import static android.app.AlarmManager.ELAPSED_REALTIME; |
| import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; |
| import static android.app.AlarmManager.RTC; |
| import static android.app.AlarmManager.RTC_WAKEUP; |
| |
| import static com.android.server.alarm.AlarmManagerService.clampPositive; |
| |
| import android.app.AlarmManager; |
| import android.app.IAlarmListener; |
| import android.app.PendingIntent; |
| import android.os.Bundle; |
| import android.os.WorkSource; |
| import android.util.IndentingPrintWriter; |
| import android.util.TimeUtils; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| |
| /** |
| * Class to describe an alarm that is used to the set the kernel timer that returns when the timer |
| * expires. The timer will wake up the device if the alarm is a "wakeup" alarm. |
| */ |
| class Alarm { |
| @VisibleForTesting |
| public static final int NUM_POLICIES = 4; |
| /** |
| * Index used to store the time the alarm was requested to expire. To be used with |
| * {@link #setPolicyElapsed(int, long)}. |
| */ |
| public static final int REQUESTER_POLICY_INDEX = 0; |
| /** |
| * Index used to store the earliest time the alarm can expire based on app-standby policy. |
| * To be used with {@link #setPolicyElapsed(int, long)}. |
| */ |
| public static final int APP_STANDBY_POLICY_INDEX = 1; |
| /** |
| * Index used to store the earliest time the alarm can expire based on the device's doze policy. |
| * To be used with {@link #setPolicyElapsed(int, long)}. |
| */ |
| public static final int DEVICE_IDLE_POLICY_INDEX = 2; |
| |
| /** |
| * Index used to store the earliest time the alarm can expire based on battery saver policy. |
| * To be used with {@link #setPolicyElapsed(int, long)}. |
| */ |
| public static final int BATTERY_SAVER_POLICY_INDEX = 3; |
| |
| /** |
| * Reason to use for inexact alarms. |
| */ |
| static final int EXACT_ALLOW_REASON_NOT_APPLICABLE = -1; |
| /** |
| * Caller had SCHEDULE_EXACT_ALARM permission. |
| */ |
| static final int EXACT_ALLOW_REASON_PERMISSION = 0; |
| /** |
| * Caller was in the power allow-list. |
| */ |
| static final int EXACT_ALLOW_REASON_ALLOW_LIST = 1; |
| /** |
| * Change wasn't enable for the caller due to compat reasons. |
| */ |
| static final int EXACT_ALLOW_REASON_COMPAT = 2; |
| |
| public final int type; |
| /** |
| * The original trigger time supplied by the caller. This can be in the elapsed or rtc time base |
| * depending on the type of this alarm |
| */ |
| public final long origWhen; |
| public final boolean wakeup; |
| public final PendingIntent operation; |
| public final IAlarmListener listener; |
| public final String listenerTag; |
| public final String statsTag; |
| public final WorkSource workSource; |
| public final int flags; |
| public final AlarmManager.AlarmClockInfo alarmClock; |
| public final int uid; |
| public final int creatorUid; |
| public final String packageName; |
| public final String sourcePackage; |
| public final long windowLength; |
| public final long repeatInterval; |
| public int count; |
| /** The earliest time this alarm is eligible to fire according to each policy */ |
| private long[] mPolicyWhenElapsed; |
| /** The ultimate delivery time to be used for this alarm */ |
| private long mWhenElapsed; |
| private long mMaxWhenElapsed; |
| public int mExactAllowReason; |
| public AlarmManagerService.PriorityClass priorityClass; |
| /** Broadcast options to use when delivering this alarm */ |
| public Bundle mIdleOptions; |
| |
| 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) { |
| this.type = type; |
| origWhen = when; |
| wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP |
| || type == AlarmManager.RTC_WAKEUP; |
| mPolicyWhenElapsed = new long[NUM_POLICIES]; |
| mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed; |
| mWhenElapsed = requestedWhenElapsed; |
| this.windowLength = windowLength; |
| mMaxWhenElapsed = clampPositive(requestedWhenElapsed + windowLength); |
| repeatInterval = interval; |
| operation = op; |
| listener = rec; |
| this.listenerTag = listenerTag; |
| statsTag = makeTag(op, listenerTag, type); |
| workSource = ws; |
| this.flags = flags; |
| alarmClock = info; |
| this.uid = uid; |
| packageName = pkgName; |
| mIdleOptions = idleOptions; |
| mExactAllowReason = exactAllowReason; |
| sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; |
| creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid; |
| } |
| |
| public static String makeTag(PendingIntent pi, String tag, int type) { |
| final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP |
| ? "*walarm*:" : "*alarm*:"; |
| return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag); |
| } |
| |
| // Returns true if either matches |
| public boolean matches(PendingIntent pi, IAlarmListener rec) { |
| return (operation != null) |
| ? operation.equals(pi) |
| : rec != null && listener.asBinder().equals(rec.asBinder()); |
| } |
| |
| public boolean matches(String packageName) { |
| return packageName.equals(sourcePackage); |
| } |
| |
| /** |
| * Get the earliest time this alarm is allowed to expire based on the given policy. |
| * |
| * @param policyIndex The index of the policy. One of [{@link #REQUESTER_POLICY_INDEX}, |
| * {@link #APP_STANDBY_POLICY_INDEX}]. |
| */ |
| @VisibleForTesting |
| long getPolicyElapsed(int policyIndex) { |
| return mPolicyWhenElapsed[policyIndex]; |
| } |
| |
| /** |
| * @return the time this alarm was requested to go off in the elapsed time base. |
| */ |
| public long getRequestedElapsed() { |
| return mPolicyWhenElapsed[REQUESTER_POLICY_INDEX]; |
| } |
| |
| /** |
| * Get the earliest time that this alarm should be delivered to the requesting app. |
| */ |
| public long getWhenElapsed() { |
| return mWhenElapsed; |
| } |
| |
| /** |
| * Get the latest time that this alarm should be delivered to the requesting app. Will be equal |
| * to {@link #getWhenElapsed()} in case this is an exact alarm. |
| */ |
| public long getMaxWhenElapsed() { |
| return mMaxWhenElapsed; |
| } |
| |
| /** |
| * Set the earliest time this alarm can expire based on the passed policy index. |
| * |
| * @return {@code true} if this change resulted in a change in the ultimate delivery time (or |
| * time window in the case of inexact alarms) of this alarm. |
| * @see #getWhenElapsed() |
| * @see #getMaxWhenElapsed() |
| * @see #getPolicyElapsed(int) |
| */ |
| public boolean setPolicyElapsed(int policyIndex, long policyElapsed) { |
| mPolicyWhenElapsed[policyIndex] = policyElapsed; |
| return updateWhenElapsed(); |
| } |
| |
| /** |
| * @return {@code true} if either {@link #mWhenElapsed} or {@link #mMaxWhenElapsed} changes |
| * due to this call. |
| */ |
| private boolean updateWhenElapsed() { |
| final long oldWhenElapsed = mWhenElapsed; |
| mWhenElapsed = 0; |
| for (int i = 0; i < NUM_POLICIES; i++) { |
| mWhenElapsed = Math.max(mWhenElapsed, mPolicyWhenElapsed[i]); |
| } |
| |
| final long oldMaxWhenElapsed = mMaxWhenElapsed; |
| // windowLength should always be >= 0 here. |
| final long maxRequestedElapsed = clampPositive( |
| mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] + windowLength); |
| mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed); |
| |
| return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(128); |
| sb.append("Alarm{"); |
| sb.append(Integer.toHexString(System.identityHashCode(this))); |
| sb.append(" type "); |
| sb.append(type); |
| sb.append(" origWhen "); |
| sb.append(origWhen); |
| sb.append(" whenElapsed "); |
| sb.append(getWhenElapsed()); |
| sb.append(" "); |
| sb.append(sourcePackage); |
| sb.append('}'); |
| return sb.toString(); |
| } |
| |
| private static String policyIndexToString(int index) { |
| switch (index) { |
| case REQUESTER_POLICY_INDEX: |
| return "requester"; |
| case APP_STANDBY_POLICY_INDEX: |
| return "app_standby"; |
| case DEVICE_IDLE_POLICY_INDEX: |
| return "device_idle"; |
| case BATTERY_SAVER_POLICY_INDEX: |
| return "battery_saver"; |
| default: |
| return "--unknown--"; |
| } |
| } |
| |
| private static String exactReasonToString(int reason) { |
| switch (reason) { |
| case EXACT_ALLOW_REASON_ALLOW_LIST: |
| return "allow-listed"; |
| case EXACT_ALLOW_REASON_COMPAT: |
| return "compat"; |
| case EXACT_ALLOW_REASON_PERMISSION: |
| return "permission"; |
| case EXACT_ALLOW_REASON_NOT_APPLICABLE: |
| return "N/A"; |
| default: |
| return "--unknown--"; |
| } |
| } |
| |
| public static String typeToString(int type) { |
| switch (type) { |
| case RTC: |
| return "RTC"; |
| case RTC_WAKEUP: |
| return "RTC_WAKEUP"; |
| case ELAPSED_REALTIME: |
| return "ELAPSED"; |
| case ELAPSED_REALTIME_WAKEUP: |
| return "ELAPSED_WAKEUP"; |
| default: |
| return "--unknown--"; |
| } |
| } |
| |
| public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) { |
| final boolean isRtc = (type == RTC || type == RTC_WAKEUP); |
| ipw.print("tag="); |
| ipw.println(statsTag); |
| |
| ipw.print("type="); |
| ipw.print(typeToString(type)); |
| ipw.print(" origWhen="); |
| if (isRtc) { |
| ipw.print(sdf.format(new Date(origWhen))); |
| } else { |
| TimeUtils.formatDuration(origWhen, nowELAPSED, ipw); |
| } |
| ipw.print(" window="); |
| TimeUtils.formatDuration(windowLength, ipw); |
| if (mExactAllowReason != EXACT_ALLOW_REASON_NOT_APPLICABLE) { |
| ipw.print(" exactAllowReason="); |
| ipw.print(exactReasonToString(mExactAllowReason)); |
| } |
| ipw.print(" repeatInterval="); |
| ipw.print(repeatInterval); |
| ipw.print(" count="); |
| ipw.print(count); |
| ipw.print(" flags=0x"); |
| ipw.println(Integer.toHexString(flags)); |
| |
| ipw.print("policyWhenElapsed:"); |
| for (int i = 0; i < NUM_POLICIES; i++) { |
| ipw.print(" " + policyIndexToString(i) + "="); |
| TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowELAPSED, ipw); |
| } |
| ipw.println(); |
| |
| ipw.print("whenElapsed="); |
| TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw); |
| ipw.print(" maxWhenElapsed="); |
| TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw); |
| ipw.println(); |
| |
| if (alarmClock != null) { |
| ipw.println("Alarm clock:"); |
| |
| ipw.print(" triggerTime="); |
| ipw.println(sdf.format(new Date(alarmClock.getTriggerTime()))); |
| |
| ipw.print(" showIntent="); |
| ipw.println(alarmClock.getShowIntent()); |
| } |
| if (operation != null) { |
| ipw.print("operation="); |
| ipw.println(operation); |
| } |
| if (listener != null) { |
| ipw.print("listener="); |
| ipw.println(listener.asBinder()); |
| } |
| if (mIdleOptions != null) { |
| ipw.print("idle-options="); |
| ipw.println(mIdleOptions.toString()); |
| } |
| } |
| |
| public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { |
| final long token = proto.start(fieldId); |
| |
| proto.write(AlarmProto.TAG, statsTag); |
| proto.write(AlarmProto.TYPE, type); |
| proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, getWhenElapsed() - nowElapsed); |
| proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength); |
| proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval); |
| proto.write(AlarmProto.COUNT, count); |
| proto.write(AlarmProto.FLAGS, flags); |
| if (alarmClock != null) { |
| alarmClock.dumpDebug(proto, AlarmProto.ALARM_CLOCK); |
| } |
| if (operation != null) { |
| operation.dumpDebug(proto, AlarmProto.OPERATION); |
| } |
| if (listener != null) { |
| proto.write(AlarmProto.LISTENER, listener.asBinder().toString()); |
| } |
| |
| proto.end(token); |
| } |
| } |