From 1d5ea8c336fde11c959efcefb8bca822e91776cf Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Wed, 21 Apr 2021 13:49:14 -0700 Subject: [PATCH] Adding metrics to alarm manager Push atoms: - Every time an alarm is scheduled, we push some attributes of the alarm and the reason why such an alarm was allowed. This will help audit spammy behavior from apps that misuse alarm API, e.g. apps that have SCHEDULE_EXACT_ALARM. - Every time an alarm batch is delivered, we log the size of the batch and whether or not it was a wakeup batch. This is the essential measure of alarm manager's batching efficiency. Pulled atoms: - Logging general stats on pending alarms every once in a while. Test: atest CtsAlarmManagerTestCases atest FrameworksMockingServicesTests:com.android.server.alarm statsd-testdrive 377 378 10106 Bug: 161497385 Bug: 171306433 Bug: 177556103 Change-Id: Ib13c9c380b641d318c6b8777996aa37f9b3d1bfc --- .../java/com/android/server/alarm/Alarm.java | 42 ++++++- .../server/alarm/AlarmManagerService.java | 58 +++++++--- .../com/android/server/alarm/AlarmStore.java | 6 +- .../server/alarm/BatchingAlarmStore.java | 19 +++- .../android/server/alarm/LazyAlarmStore.java | 16 +++ .../android/server/alarm/MetricsHelper.java | 105 ++++++++++++++++++ .../server/alarm/AlarmManagerServiceTest.java | 68 +++++++++--- .../android/server/alarm/AlarmStoreTest.java | 33 +++++- .../com/android/server/alarm/AlarmTest.java | 4 +- .../alarm/BackgroundRestrictedAlarmsTest.java | 2 +- 10 files changed, 312 insertions(+), 41 deletions(-) create mode 100644 apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java index 3bc7b307c3344..9ffef9cdcbde3 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java @@ -66,6 +66,23 @@ class Alarm { */ 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 @@ -92,13 +109,15 @@ class Alarm { /** 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) { + AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions, + int exactAllowReason) { this.type = type; origWhen = when; wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP @@ -119,6 +138,7 @@ class Alarm { this.uid = uid; packageName = pkgName; mIdleOptions = idleOptions; + mExactAllowReason = exactAllowReason; sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid; } @@ -216,7 +236,6 @@ class Alarm { sb.append(type); sb.append(" origWhen "); sb.append(origWhen); - sb.append(" "); sb.append(" whenElapsed "); sb.append(getWhenElapsed()); sb.append(" "); @@ -240,6 +259,21 @@ class Alarm { } } + 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: @@ -270,6 +304,10 @@ class Alarm { } 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="); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index e63a7c41ea98e..5554ad27acd84 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -38,6 +38,10 @@ import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX; import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_ALLOW_LIST; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; import android.Manifest; @@ -223,6 +227,7 @@ public class AlarmManagerService extends SystemService { private final Injector mInjector; int mBroadcastRefCount = 0; + MetricsHelper mMetricsHelper; PowerManager.WakeLock mWakeLock; SparseIntArray mAlarmsPerUid = new SparseIntArray(); ArrayList mPendingNonWakeupAlarms = new ArrayList<>(); @@ -1221,7 +1226,7 @@ public class AlarmManagerService extends SystemService { setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed, nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null, null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid, - alarm.packageName, null); + alarm.packageName, null, EXACT_ALLOW_REASON_NOT_APPLICABLE); // Kernel alarms will be rescheduled as needed in setImplLocked } } @@ -1442,6 +1447,7 @@ public class AlarmManagerService extends SystemService { @Override public void onStart() { mInjector.init(); + mMetricsHelper = new MetricsHelper(getContext()); mListenerDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -1584,6 +1590,7 @@ public class AlarmManagerService extends SystemService { }); } catch (RemoteException e) { } + mMetricsHelper.registerPuller(mAlarmStore); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleInternal.class); @@ -1691,7 +1698,7 @@ public class AlarmManagerService extends SystemService { void setImpl(int type, long triggerAtTime, long windowLength, long interval, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock, - int callingUid, String callingPackage, Bundle idleOptions) { + int callingUid, String callingPackage, Bundle idleOptions, int exactAllowReason) { if ((operation == null && directReceiver == null) || (operation != null && directReceiver != null)) { Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver"); @@ -1788,7 +1795,7 @@ public class AlarmManagerService extends SystemService { } setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, - callingPackage, idleOptions); + callingPackage, idleOptions, exactAllowReason); } } @@ -1796,10 +1803,10 @@ public class AlarmManagerService extends SystemService { long interval, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage, - Bundle idleOptions) { + Bundle idleOptions, int exactAllowReason) { final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval, operation, directReceiver, listenerTag, workSource, flags, alarmClock, - callingUid, callingPackage, idleOptions); + callingUid, callingPackage, idleOptions, exactAllowReason); if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) { Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a + " -- package not allowed to start"); @@ -1808,6 +1815,7 @@ public class AlarmManagerService extends SystemService { removeLocked(operation, directReceiver); incrementAlarmCount(a.uid); setImplLocked(a); + MetricsHelper.pushAlarmScheduled(a); } /** @@ -2220,6 +2228,8 @@ public class AlarmManagerService extends SystemService { // Make sure the caller is allowed to use the requested kind of alarm, and also // decide what quota and broadcast options to use. + boolean allowListed = false; // For logging the reason. + boolean changeDisabled = false; // For logging the reason. Bundle idleOptions = null; if ((flags & FLAG_PRIORITIZE) != 0) { getContext().enforcePermission( @@ -2236,6 +2246,7 @@ public class AlarmManagerService extends SystemService { lowerQuota = !exact; idleOptions = exact ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle(); } else { + changeDisabled = true; needsPermission = false; lowerQuota = allowWhileIdle; idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null; @@ -2250,6 +2261,8 @@ public class AlarmManagerService extends SystemService { } else { Slog.wtf(TAG, errorMessage); } + } else { + allowListed = true; } // If the app is on the full system power allow-list (not except-idle), or we're // in a soft failure mode, we still allow the alarms. @@ -2263,15 +2276,25 @@ public class AlarmManagerService extends SystemService { flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT; } } - - // If this is an exact time alarm, then it can't be batched with other alarms. + final int exactAllowReason; if (exact) { + // If this is an exact time alarm, then it can't be batched with other alarms. flags |= AlarmManager.FLAG_STANDALONE; + + if (changeDisabled) { + exactAllowReason = EXACT_ALLOW_REASON_COMPAT; + } else if (allowListed) { + exactAllowReason = EXACT_ALLOW_REASON_ALLOW_LIST; + } else { + exactAllowReason = EXACT_ALLOW_REASON_PERMISSION; + } + } else { + exactAllowReason = EXACT_ALLOW_REASON_NOT_APPLICABLE; } setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, callingPackage, - idleOptions); + idleOptions, exactAllowReason); } @Override @@ -3501,8 +3524,8 @@ public class AlarmManagerService extends SystemService { private static native int setKernelTimezone(long nativeData, int minuteswest); private static native long getNextAlarm(long nativeData, int type); - boolean triggerAlarmsLocked(ArrayList triggerList, final long nowELAPSED) { - boolean hasWakeup = false; + int triggerAlarmsLocked(ArrayList triggerList, final long nowELAPSED) { + int wakeUps = 0; final ArrayList pendingAlarms = mAlarmStore.removePendingAlarms(nowELAPSED); for (final Alarm alarm : pendingAlarms) { if (isBackgroundRestricted(alarm)) { @@ -3559,11 +3582,11 @@ public class AlarmManagerService extends SystemService { setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed, nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null, null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid, - alarm.packageName, null); + alarm.packageName, null, EXACT_ALLOW_REASON_NOT_APPLICABLE); } if (alarm.wakeup) { - hasWakeup = true; + wakeUps++; } // We removed an alarm clock. Let the caller recompute the next alarm clock. @@ -3584,7 +3607,7 @@ public class AlarmManagerService extends SystemService { } } - return hasWakeup; + return wakeUps; } long currentNonWakeupFuzzLocked(long nowELAPSED) { @@ -3843,8 +3866,8 @@ public class AlarmManagerService extends SystemService { } mLastTrigger = nowELAPSED; - boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED); - if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) { + final int wakeUps = triggerAlarmsLocked(triggerList, nowELAPSED); + if (wakeUps == 0 && checkAllowNonWakeupDelayLocked(nowELAPSED)) { // if there are no wakeup alarms and the screen is off, we can // delay what we have so far until the future. if (mPendingNonWakeupAlarms.size() == 0) { @@ -3896,6 +3919,7 @@ public class AlarmManagerService extends SystemService { reorderAlarmsBasedOnStandbyBuckets(triggerPackages); rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); + MetricsHelper.pushAlarmBatchDelivered(triggerList.size(), wakeUps); } } @@ -4126,7 +4150,7 @@ public class AlarmManagerService extends SystemService { setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0, 0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null, - Process.myUid(), "android", null); + Process.myUid(), "android", null, EXACT_ALLOW_REASON_ALLOW_LIST); // Finally, remember when we set the tick alarm synchronized (mLock) { @@ -4146,7 +4170,7 @@ public class AlarmManagerService extends SystemService { final WorkSource workSource = null; // Let system take blame for date change events. setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null, AlarmManager.FLAG_STANDALONE, workSource, null, - Process.myUid(), "android", null); + Process.myUid(), "android", null, EXACT_ALLOW_REASON_ALLOW_LIST); } } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java index e684b84748b12..0172748a95fa0 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java @@ -138,6 +138,11 @@ public interface AlarmStore { */ String getName(); + /** + * Returns the number of alarms that satisfy the given condition. + */ + int getCount(Predicate condition); + /** * A functional interface used to update the alarm. Used to describe the update in * {@link #updateAlarmDeliveries(AlarmDeliveryCalculator)} @@ -153,4 +158,3 @@ public interface AlarmStore { boolean updateAlarmDelivery(Alarm a); } } - diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java index cb528ba2769a9..1a4efb8ffcd9d 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java @@ -17,7 +17,6 @@ package com.android.server.alarm; import static com.android.server.alarm.AlarmManagerService.DEBUG_BATCH; -import static com.android.server.alarm.AlarmManagerService.TAG; import static com.android.server.alarm.AlarmManagerService.clampPositive; import static com.android.server.alarm.AlarmManagerService.dumpAlarmList; import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm; @@ -50,10 +49,12 @@ public class BatchingAlarmStore implements AlarmStore { interface Stats { int REBATCH_ALL_ALARMS = 0; + int GET_COUNT = 1; } final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{ "REBATCH_ALL_ALARMS", + "GET_COUNT", }); private static final Comparator sBatchOrder = Comparator.comparingLong(b -> b.mStart); @@ -219,6 +220,22 @@ public class BatchingAlarmStore implements AlarmStore { return TAG; } + @Override + public int getCount(Predicate condition) { + long start = mStatLogger.getTime(); + + int count = 0; + for (Batch b : mAlarmBatches) { + for (int i = 0; i < b.size(); i++) { + if (condition.test(b.get(i))) { + count++; + } + } + } + mStatLogger.logDurationStat(Stats.GET_COUNT, start); + return count; + } + private void insertAndBatchAlarm(Alarm alarm) { final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed()); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java index 9b1b06658a988..2e12e2f34ea07 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java @@ -47,11 +47,13 @@ public class LazyAlarmStore implements AlarmStore { interface Stats { int GET_NEXT_DELIVERY_TIME = 0; int GET_NEXT_WAKEUP_DELIVERY_TIME = 1; + int GET_COUNT = 2; } final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{ "GET_NEXT_DELIVERY_TIME", "GET_NEXT_WAKEUP_DELIVERY_TIME", + "GET_COUNT", }); // Decreasing time order because it is more efficient to remove from the tail of an array list. @@ -221,4 +223,18 @@ public class LazyAlarmStore implements AlarmStore { public String getName() { return TAG; } + + @Override + public int getCount(Predicate condition) { + long start = mStatLogger.getTime(); + + int count = 0; + for (final Alarm a : mAlarms) { + if (condition.test(a)) { + count++; + } + } + mStatLogger.logDurationStat(Stats.GET_COUNT, start); + return count; + } } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java new file mode 100644 index 0000000000000..a8cf7b2ae3f8d --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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 com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__ALLOW_LIST; +import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__CHANGE_DISABLED; +import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__NOT_APPLICABLE; +import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__PERMISSION; +import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; + +import android.app.AlarmManager; +import android.app.StatsManager; +import android.content.Context; +import android.os.SystemClock; + +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.FrameworkStatsLog; + +/** + * A helper class to write logs to statsd. + */ +class MetricsHelper { + private Context mContext; + + MetricsHelper(Context context) { + mContext = context; + } + + void registerPuller(AlarmStore alarmStore) { + final StatsManager statsManager = mContext.getSystemService(StatsManager.class); + statsManager.setPullAtomCallback(FrameworkStatsLog.PENDING_ALARM_INFO, null, + BackgroundThread.getExecutor(), (atomTag, data) -> { + if (atomTag != FrameworkStatsLog.PENDING_ALARM_INFO) { + throw new UnsupportedOperationException("Unknown tag" + atomTag); + } + final long now = SystemClock.elapsedRealtime(); + data.add(FrameworkStatsLog.buildStatsEvent(atomTag, + alarmStore.size(), + alarmStore.getCount(a -> a.windowLength == 0), + alarmStore.getCount(a -> a.wakeup), + alarmStore.getCount( + a -> (a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0), + alarmStore.getCount(a -> (a.flags & AlarmManager.FLAG_PRIORITIZE) != 0), + alarmStore.getCount(a -> (a.operation != null + && a.operation.isForegroundService())), + alarmStore.getCount( + a -> (a.operation != null && a.operation.isActivity())), + alarmStore.getCount( + a -> (a.operation != null && a.operation.isService())), + alarmStore.getCount(a -> (a.listener != null)), + alarmStore.getCount( + a -> (a.getRequestedElapsed() > now + INDEFINITE_DELAY)), + alarmStore.getCount(a -> (a.repeatInterval != 0)), + alarmStore.getCount(a -> (a.alarmClock != null)) + )); + return StatsManager.PULL_SUCCESS; + }); + } + + private static int reasonToStatsReason(int reasonCode) { + switch (reasonCode) { + case Alarm.EXACT_ALLOW_REASON_ALLOW_LIST: + return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__ALLOW_LIST; + case Alarm.EXACT_ALLOW_REASON_PERMISSION: + return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__PERMISSION; + case Alarm.EXACT_ALLOW_REASON_COMPAT: + return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__CHANGE_DISABLED; + default: + return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__NOT_APPLICABLE; + } + } + + static void pushAlarmScheduled(Alarm a) { + FrameworkStatsLog.write( + FrameworkStatsLog.ALARM_SCHEDULED, + a.uid, + a.windowLength == 0, + a.wakeup, + (a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0, + a.alarmClock != null, + a.repeatInterval != 0, + reasonToStatsReason(a.mExactAllowReason)); + } + + static void pushAlarmBatchDelivered(int numAlarms, int wakeups) { + FrameworkStatsLog.write( + FrameworkStatsLog.ALARM_BATCH_DELIVERED, + numAlarms, + wakeups); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 683fbd17b78d1..3bc1c8a4037d8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -51,6 +51,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_ALLOW_LIST; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE; +import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION; import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; @@ -359,6 +363,7 @@ public class AlarmManagerServiceTest { .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) .spyStatic(Looper.class) + .mockStatic(MetricsHelper.class) .mockStatic(Settings.Global.class) .mockStatic(ServiceManager.class) .spyStatic(UserHandle.class) @@ -418,6 +423,9 @@ public class AlarmManagerServiceTest { spyOn(mService); mService.onStart(); + // Unable to mock mMockContext to return a mock stats manager. + // So just mocking the whole MetricsHelper instance. + mService.mMetricsHelper = mock(MetricsHelper.class); spyOn(mService.mHandler); // Stubbing the handler. Test should simulate any handling of messages synchronously. doReturn(true).when(mService.mHandler).sendMessageAtTime(any(Message.class), anyLong()); @@ -492,7 +500,7 @@ public class AlarmManagerServiceTest { IAlarmListener listener) { mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test", FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID, - TEST_CALLING_PACKAGE, null); + TEST_CALLING_PACKAGE, null, 0); } private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi, @@ -516,12 +524,12 @@ public class AlarmManagerServiceTest { PendingIntent operation, long interval, int flags, int callingUid, String callingPackage, Bundle idleOptions) { mService.setImpl(type, triggerTime, windowLength, interval, operation, null, "test", flags, - null, null, callingUid, callingPackage, idleOptions); + null, null, callingUid, callingPackage, idleOptions, 0); } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", - FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); + FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null, 0); } @@ -1898,7 +1906,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), + eq(EXACT_ALLOW_REASON_COMPAT)); } @Test @@ -1915,7 +1924,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), + eq(EXACT_ALLOW_REASON_COMPAT)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -1935,7 +1945,8 @@ public class AlarmManagerServiceTest { final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), - isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), + eq(EXACT_ALLOW_REASON_NOT_APPLICABLE)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -1955,7 +1966,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), - isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); + isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), + eq(EXACT_ALLOW_REASON_COMPAT)); } @Test @@ -1979,7 +1991,7 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), - bundleCaptor.capture()); + bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2042,7 +2054,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), + eq(EXACT_ALLOW_REASON_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2067,7 +2080,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), + eq(EXACT_ALLOW_REASON_ALLOW_LIST)); } @Test @@ -2087,7 +2101,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), + eq(EXACT_ALLOW_REASON_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2113,7 +2128,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), + eq(EXACT_ALLOW_REASON_ALLOW_LIST)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2167,7 +2183,8 @@ public class AlarmManagerServiceTest { final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), - isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), + eq(EXACT_ALLOW_REASON_NOT_APPLICABLE)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); @@ -2192,7 +2209,8 @@ public class AlarmManagerServiceTest { verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_STANDALONE), isNull(), isNull(), - eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull()); + eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), + eq(EXACT_ALLOW_REASON_ALLOW_LIST)); } @Test @@ -2492,6 +2510,28 @@ public class AlarmManagerServiceTest { assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); } + @Test + public void alarmScheduledAtomPushed() { + for (int i = 0; i < 10; i++) { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i, pi); + + verify(() -> MetricsHelper.pushAlarmScheduled(argThat(a -> a.matches(pi, null)))); + } + } + + @Test + public void alarmBatchDeliveredAtomPushed() throws InterruptedException { + for (int i = 0; i < 10; i++) { + final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP; + setTestAlarm(type, mNowElapsedTest + i, getNewMockPendingIntent()); + } + mNowElapsedTest += 100; + mTestTimer.expire(); + + verify(() -> MetricsHelper.pushAlarmBatchDelivered(10, 5)); + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java index 12894ae4f75e7..ba0e555020ac6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java @@ -18,7 +18,10 @@ 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.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE; import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE; import static com.android.server.alarm.Constants.TEST_CALLING_UID; @@ -33,6 +36,7 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.platform.test.annotations.Presubmit; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -69,12 +73,13 @@ public class AlarmStoreTest { mock(PendingIntent.class)); return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0, mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID, - TEST_CALLING_PACKAGE, null); + TEST_CALLING_PACKAGE, null, EXACT_ALLOW_REASON_NOT_APPLICABLE); } private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) { return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class), - null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); + null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null, + EXACT_ALLOW_REASON_NOT_APPLICABLE); } private void addAlarmsToStore(Alarm... alarms) { @@ -83,6 +88,11 @@ public class AlarmStoreTest { } } + @Before + public void clear() { + mAlarmStore.remove(unused -> true); + } + @Test public void add() { final Alarm a1 = createAlarm(1, 0); @@ -209,7 +219,6 @@ public class AlarmStoreTest { final Alarm a8 = createAlarm(8, 0); final Alarm a10 = createAlarm(10, 0); addAlarmsToStore(a8, a10, a5); - assertEquals(5, mAlarmStore.getNextDeliveryTime()); mAlarmStore.updateAlarmDeliveries(a -> { @@ -241,4 +250,22 @@ public class AlarmStoreTest { mAlarmStore.remove(alarmClock::equals); verify(onRemoved).run(); } + + @Test + public void getCount() { + for (int i = 0; i < 10; i++) { + mAlarmStore.add(createAlarm(ELAPSED_REALTIME, i, 4, 0)); + } + assertEquals(5, mAlarmStore.getCount(a -> a.getRequestedElapsed() < 5)); + assertEquals(10, mAlarmStore.getCount(a -> a.windowLength == 4)); + + addAlarmsToStore( + createAlarm(RTC_WAKEUP, 45, 0, 53), + createAlarm(ELAPSED_REALTIME_WAKEUP, 60, 0, 53) + ); + + assertEquals(2, mAlarmStore.getCount(a -> a.wakeup)); + assertEquals(2, mAlarmStore.getCount(a -> a.flags == 53)); + assertEquals(0, mAlarmStore.getCount(a -> a.type == RTC)); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java index b64528c2f4d4b..f11cba0db7b9d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java @@ -55,14 +55,14 @@ public class AlarmTest { private Alarm createDefaultAlarm(long requestedElapsed, long windowLength, int flags) { return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0, createAlarmSender(), null, null, null, flags, null, TEST_CALLING_UID, - TEST_CALLING_PACKAGE, null); + TEST_CALLING_PACKAGE, null, 0); } private Alarm createAlarmClock(long requestedRtc) { final AlarmManager.AlarmClockInfo info = mock(AlarmManager.AlarmClockInfo.class); return new Alarm(RTC_WAKEUP, requestedRtc, requestedRtc, 0, 0, createAlarmSender(), null, null, null, FLAG_WAKE_FROM_IDLE | FLAG_STANDALONE, info, TEST_CALLING_UID, - TEST_CALLING_PACKAGE, null); + TEST_CALLING_PACKAGE, null, 0); } private PendingIntent createAlarmSender() { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java index 0e795a938d482..403b4cd7e57eb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java @@ -45,7 +45,7 @@ public class BackgroundRestrictedAlarmsTest { } uidAlarms.add(new Alarm( removeIt ? RTC : RTC_WAKEUP, - 0, 0, 0, 0, null, null, null, null, 0, null, uid, name, null)); + 0, 0, 0, 0, null, null, null, null, 0, null, uid, name, null, 0)); return all; }