blob: 9ffef9cdcbde3de335463f906f714dfd464fd1bd [file] [log] [blame]
/*
* 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);
}
}