Merge "Show system notifications when DnD is enabled" into tm-dev
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 2e6b8bd..fd2bb13 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -123,6 +123,7 @@
     public AlarmManagerService.PriorityClass priorityClass;
     /** Broadcast options to use when delivering this alarm */
     public Bundle mIdleOptions;
+    public boolean mUsingReserveQuota;
 
     Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
             PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
@@ -151,6 +152,7 @@
         mExactAllowReason = exactAllowReason;
         sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
         creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
+        mUsingReserveQuota = false;
     }
 
     public static String makeTag(PendingIntent pi, String tag, int type) {
@@ -340,6 +342,9 @@
         TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw);
         ipw.print(" maxWhenElapsed=");
         TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw);
+        if (mUsingReserveQuota) {
+            ipw.print(" usingReserveQuota=true");
+        }
         ipw.println();
 
         if (alarmClock != null) {
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 7baf805..0de0a1c 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -213,6 +213,8 @@
     static final int RARE_INDEX = 3;
     static final int NEVER_INDEX = 4;
 
+    private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
+
     private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
 
@@ -282,6 +284,7 @@
     AppWakeupHistory mAppWakeupHistory;
     AppWakeupHistory mAllowWhileIdleHistory;
     AppWakeupHistory mAllowWhileIdleCompatHistory;
+    TemporaryQuotaReserve mTemporaryQuotaReserve;
     private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
     private final SparseArray<RingBuffer<RemovedAlarm>> mRemovalHistory = new SparseArray<>();
     ClockReceiver mClockReceiver;
@@ -359,6 +362,133 @@
     boolean mAppStandbyParole;
 
     /**
+     * Holds information about temporary quota that can be allotted to apps to use as a "reserve"
+     * when they run out of their standard app-standby quota.
+     * This reserve only lasts for a fixed duration of time from when it was last replenished.
+     */
+    static class TemporaryQuotaReserve {
+
+        private static class QuotaInfo {
+            public int remainingQuota;
+            public long expirationTime;
+            public long lastUsage;
+        }
+        /** Map of {package, user} -> {quotaInfo} */
+        private final ArrayMap<Pair<String, Integer>, QuotaInfo> mQuotaBuffer = new ArrayMap<>();
+
+        private long mMaxDuration;
+
+        TemporaryQuotaReserve(long maxDuration) {
+            mMaxDuration = maxDuration;
+        }
+
+        void replenishQuota(String packageName, int userId, int quota, long nowElapsed) {
+            if (quota <= 0) {
+                return;
+            }
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            QuotaInfo currentQuotaInfo = mQuotaBuffer.get(packageUser);
+            if (currentQuotaInfo == null) {
+                currentQuotaInfo = new QuotaInfo();
+                mQuotaBuffer.put(packageUser, currentQuotaInfo);
+            }
+            currentQuotaInfo.remainingQuota = quota;
+            currentQuotaInfo.expirationTime = nowElapsed + mMaxDuration;
+        }
+
+        /** Returns if the supplied package has reserve quota to fire at the given time. */
+        boolean hasQuota(String packageName, int userId, long triggerElapsed) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
+
+            return quotaInfo != null && quotaInfo.remainingQuota > 0
+                    && triggerElapsed <= quotaInfo.expirationTime;
+        }
+
+        /**
+         * Records quota usage of the given package at the given time and subtracts quota if
+         * required.
+         */
+        void recordUsage(String packageName, int userId, long nowElapsed) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
+
+            if (quotaInfo == null) {
+                Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
+                        + " but not found for package: " + packageName + ", user: " + userId);
+                return;
+            }
+            // Only consume quota if this usage is later than the last one recorded. This is
+            // needed as this can be called multiple times when a batch of alarms is delivered.
+            if (nowElapsed > quotaInfo.lastUsage) {
+                if (quotaInfo.remainingQuota <= 0) {
+                    Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
+                            + " but remaining only " + quotaInfo.remainingQuota
+                            + " for package: " + packageName + ", user: " + userId);
+                } else if (quotaInfo.expirationTime < nowElapsed) {
+                    Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
+                            + " but expired at " + quotaInfo.expirationTime
+                            + " for package: " + packageName + ", user: " + userId);
+                } else {
+                    quotaInfo.remainingQuota--;
+                    // We keep the quotaInfo entry even if remaining quota reduces to 0 as
+                    // following calls can be made with nowElapsed <= lastUsage. The object will
+                    // eventually be removed in cleanUpExpiredQuotas or reused in replenishQuota.
+                }
+                quotaInfo.lastUsage = nowElapsed;
+            }
+        }
+
+        /** Clean up any quotas that have expired before the given time. */
+        void cleanUpExpiredQuotas(long nowElapsed) {
+            for (int i = mQuotaBuffer.size() - 1; i >= 0; i--) {
+                final QuotaInfo quotaInfo = mQuotaBuffer.valueAt(i);
+                if (quotaInfo.expirationTime < nowElapsed) {
+                    mQuotaBuffer.removeAt(i);
+                }
+            }
+        }
+
+        void removeForUser(int userId) {
+            for (int i = mQuotaBuffer.size() - 1; i >= 0; i--) {
+                final Pair<String, Integer> packageUserKey = mQuotaBuffer.keyAt(i);
+                if (packageUserKey.second == userId) {
+                    mQuotaBuffer.removeAt(i);
+                }
+            }
+        }
+
+        void removeForPackage(String packageName, int userId) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            mQuotaBuffer.remove(packageUser);
+        }
+
+        void dump(IndentingPrintWriter pw, long nowElapsed) {
+            pw.increaseIndent();
+            for (int i = 0; i < mQuotaBuffer.size(); i++) {
+                final Pair<String, Integer> packageUser = mQuotaBuffer.keyAt(i);
+                final QuotaInfo quotaInfo = mQuotaBuffer.valueAt(i);
+                pw.print(packageUser.first);
+                pw.print(", u");
+                pw.print(packageUser.second);
+                pw.print(": ");
+                if (quotaInfo == null) {
+                    pw.print("--");
+                } else {
+                    pw.print("quota: ");
+                    pw.print(quotaInfo.remainingQuota);
+                    pw.print(", expiration: ");
+                    TimeUtils.formatDuration(quotaInfo.expirationTime, nowElapsed, pw);
+                    pw.print(" last used: ");
+                    TimeUtils.formatDuration(quotaInfo.lastUsage, nowElapsed, pw);
+                }
+                pw.println();
+            }
+            pw.decreaseIndent();
+        }
+    }
+
+    /**
      * A container to keep rolling window history of previous times when an alarm was sent to
      * a package.
      */
@@ -569,6 +699,8 @@
         @VisibleForTesting
         static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
                 "kill_on_schedule_exact_alarm_revoked";
+        @VisibleForTesting
+        static final String KEY_TEMPORARY_QUOTA_BUMP = "temporary_quota_bump";
 
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -613,6 +745,8 @@
 
         private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true;
 
+        private static final int DEFAULT_TEMPORARY_QUOTA_BUMP = 0;
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -702,6 +836,17 @@
 
         public boolean USE_TARE_POLICY = Settings.Global.DEFAULT_ENABLE_TARE == 1;
 
+        /**
+         * The amount of temporary reserve quota to give apps on receiving the
+         * {@link AppIdleStateChangeListener#triggerTemporaryQuotaBump(String, int)} callback
+         * from {@link com.android.server.usage.AppStandbyController}.
+         * <p> This quota adds on top of the standard standby bucket quota available to the app, and
+         * works the same way, i.e. each count of quota denotes one point in time when the app can
+         * receive any number of alarms together.
+         * This quota is tracked per package and expires after {@link #TEMPORARY_QUOTA_DURATION}.
+         */
+        public int TEMPORARY_QUOTA_BUMP = DEFAULT_TEMPORARY_QUOTA_BUMP;
+
         private long mLastAllowWhileIdleWhitelistDuration = -1;
         private int mVersion = 0;
 
@@ -886,6 +1031,10 @@
                                     KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
                                     DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
                             break;
+                        case KEY_TEMPORARY_QUOTA_BUMP:
+                            TEMPORARY_QUOTA_BUMP = properties.getInt(KEY_TEMPORARY_QUOTA_BUMP,
+                                    DEFAULT_TEMPORARY_QUOTA_BUMP);
+                            break;
                         default:
                             if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
                                 // The quotas need to be updated in order, so we can't just rely
@@ -1136,6 +1285,9 @@
             pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY);
             pw.println();
 
+            pw.print(KEY_TEMPORARY_QUOTA_BUMP, TEMPORARY_QUOTA_BUMP);
+            pw.println();
+
             pw.decreaseIndent();
         }
 
@@ -1748,6 +1900,8 @@
             mAllowWhileIdleHistory = new AppWakeupHistory(INTERVAL_HOUR);
             mAllowWhileIdleCompatHistory = new AppWakeupHistory(INTERVAL_HOUR);
 
+            mTemporaryQuotaReserve = new TemporaryQuotaReserve(TEMPORARY_QUOTA_DURATION);
+
             mNextWakeup = mNextNonWakeup = 0;
 
             // We have to set current TimeZone info to kernel
@@ -2391,6 +2545,12 @@
             final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
             if (wakeupsInWindow >= quotaForBucket) {
                 final long minElapsed;
+                if (mTemporaryQuotaReserve.hasQuota(sourcePackage, sourceUserId, nowElapsed)) {
+                    // We will let this alarm go out as usual, but mark it so it consumes the quota
+                    // at the time of delivery.
+                    alarm.mUsingReserveQuota = true;
+                    return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
+                }
                 if (quotaForBucket <= 0) {
                     // Just keep deferring indefinitely till the quota changes.
                     minElapsed = nowElapsed + INDEFINITE_DELAY;
@@ -2405,6 +2565,7 @@
             }
         }
         // wakeupsInWindow are less than the permitted quota, hence no deferring is needed.
+        alarm.mUsingReserveQuota = false;
         return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
     }
 
@@ -3165,6 +3326,10 @@
             pw.println("App Alarm history:");
             mAppWakeupHistory.dump(pw, nowELAPSED);
 
+            pw.println();
+            pw.println("Temporary Quota Reserves:");
+            mTemporaryQuotaReserve.dump(pw, nowELAPSED);
+
             if (mPendingIdleUntil != null) {
                 pw.println();
                 pw.println("Idle mode state:");
@@ -4573,6 +4738,7 @@
                                 }
                             }
                             deliverAlarmsLocked(triggerList, nowELAPSED);
+                            mTemporaryQuotaReserve.cleanUpExpiredQuotas(nowELAPSED);
                             if (mConstants.USE_TARE_POLICY) {
                                 reorderAlarmsBasedOnTare(triggerPackages);
                             } else {
@@ -4682,6 +4848,7 @@
         public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
         public static final int TARE_AFFORDABILITY_CHANGED = 12;
         public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
+        public static final int TEMPORARY_QUOTA_CHANGED = 14;
 
         AlarmHandler() {
             super(Looper.myLooper());
@@ -4747,6 +4914,7 @@
                     }
                     break;
 
+                case TEMPORARY_QUOTA_CHANGED:
                 case APP_STANDBY_BUCKET_CHANGED:
                     synchronized (mLock) {
                         final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
@@ -4958,6 +5126,7 @@
                             mAppWakeupHistory.removeForUser(userHandle);
                             mAllowWhileIdleHistory.removeForUser(userHandle);
                             mAllowWhileIdleCompatHistory.removeForUser(userHandle);
+                            mTemporaryQuotaReserve.removeForUser(userHandle);
                         }
                         return;
                     case Intent.ACTION_UID_REMOVED:
@@ -5006,6 +5175,7 @@
                             mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                             mAllowWhileIdleCompatHistory.removeForPackage(pkg,
                                     UserHandle.getUserId(uid));
+                            mTemporaryQuotaReserve.removeForPackage(pkg, UserHandle.getUserId(uid));
                             removeLocked(uid, REMOVE_REASON_UNDEFINED);
                         } else {
                             // external-applications-unavailable case
@@ -5040,6 +5210,30 @@
             mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
                     .sendToTarget();
         }
+
+        @Override
+        public void triggerTemporaryQuotaBump(String packageName, int userId) {
+            final int quotaBump;
+            synchronized (mLock) {
+                quotaBump = mConstants.TEMPORARY_QUOTA_BUMP;
+            }
+            if (quotaBump <= 0) {
+                return;
+            }
+            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+            if (uid < 0 || UserHandle.isCore(uid)) {
+                return;
+            }
+            if (DEBUG_STANDBY) {
+                Slog.d(TAG, "Bumping quota temporarily for " + packageName + " for user " + userId);
+            }
+            synchronized (mLock) {
+                mTemporaryQuotaReserve.replenishQuota(packageName, userId, quotaBump,
+                        mInjector.getElapsedRealtime());
+            }
+            mHandler.obtainMessage(AlarmHandler.TEMPORARY_QUOTA_CHANGED, userId, -1,
+                    packageName).sendToTarget();
+        }
     }
 
     private final EconomyManagerInternal.AffordabilityChangeListener mAffordabilityChangeListener =
@@ -5448,8 +5642,13 @@
                 }
             }
             if (!isExemptFromAppStandby(alarm)) {
-                mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
-                        UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
+                final int userId = UserHandle.getUserId(alarm.creatorUid);
+                if (alarm.mUsingReserveQuota) {
+                    mTemporaryQuotaReserve.recordUsage(alarm.sourcePackage, userId, nowELAPSED);
+                } else {
+                    mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage, userId,
+                            nowELAPSED);
+                }
             }
             final BroadcastStats bs = inflight.mBroadcastStats;
             bs.count++;
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index a9af187..c382a65 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -27,7 +27,8 @@
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:paddingTop="24dp"
+        android:layout_marginTop="@dimen/autofill_save_outer_top_margin"
+        android:layout_marginBottom="24dp"
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
         android:orientation="vertical">
@@ -44,6 +45,7 @@
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:gravity="center_horizontal"
+            android:layout_marginTop="16dp"
             android:paddingStart="@dimen/autofill_save_inner_padding"
             android:paddingEnd="@dimen/autofill_save_inner_padding"
             android:visibility="gone" />
@@ -55,38 +57,30 @@
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:paddingStart="@dimen/autofill_save_inner_padding"
-        android:paddingEnd="@dimen/autofill_save_inner_padding"
         android:visibility="gone"
+        android:paddingBottom="24dp"
         android:layout_marginStart="24dp"
-        android:layout_marginEnd="24dp"
-        android:background="@drawable/autofill_dataset_picker_background"/>
+        android:layout_marginEnd="24dp" />
 
-    <LinearLayout
+    <ListView
+        android:id="@+id/autofill_dialog_list"
+        android:layout_weight="1"
         android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:paddingBottom="24dp"
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
-        android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogList"
-        android:orientation="vertical">
-        <ListView
-            android:id="@+id/autofill_dialog_list"
-            android:layout_weight="1"
-            android:layout_width="fill_parent"
-            android:layout_height="0dp"
-            android:drawSelectorOnTop="true"
-            android:clickable="true"
-            android:divider="@drawable/list_divider_material"
-            android:background="@drawable/autofill_dataset_picker_background"
-            android:visibility="gone"/>
-    </LinearLayout>
+        android:clipToPadding="false"
+        android:drawSelectorOnTop="true"
+        android:clickable="true"
+        android:divider="@null"
+        android:visibility="gone" />
 
     <com.android.internal.widget.ButtonBarLayout
         android:layout_width="match_parent"
         android:layout_height="48dp"
         android:layout_gravity="end"
-        android:clipToPadding="false"
-        android:layout_marginTop="32dp"
+        android:layout_marginTop="8dp"
         android:layout_marginBottom="18dp"
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 5381017..fd08241 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -29,7 +29,6 @@
         android:layout_marginTop="@dimen/autofill_save_outer_top_margin"
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
-        android:elevation="@dimen/autofill_elevation"
         android:background="?android:attr/colorSurface"
         android:gravity="center_horizontal"
         android:orientation="vertical">
@@ -52,6 +51,7 @@
                 android:layout_height="wrap_content"
                 android:text="@string/autofill_save_title"
                 android:layout_marginTop="16dp"
+                android:paddingBottom="24dp"
                 android:gravity="center"
                 android:textAppearance="@style/AutofillSaveUiTitle">
             </TextView>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bb36ede..b754100 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -873,7 +873,7 @@
     <dimen name="autofill_save_title_start_padding">8dp</dimen>
     <dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
     <dimen name="autofill_save_button_bar_padding">16dp</dimen>
-    <dimen name="autofill_dialog_corner_radius">28dp</dimen>
+    <dimen name="autofill_dialog_corner_radius">24dp</dimen>
 
     <!-- Max height of the the autofill save custom subtitle as a fraction of the screen width/height -->
     <dimen name="autofill_save_custom_subtitle_max_height">20%</dimen>
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index d4f4245..eb8387f 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -20,12 +20,13 @@
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.media.AudioSystem;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.util.Log;
+import android.view.Display;
 import android.view.Surface;
-import android.view.WindowManager;
 
 /**
  * Class to handle device rotation events for AudioService, and forward device rotation
@@ -48,6 +49,8 @@
 
     private static final String TAG = "AudioService.RotationHelper";
 
+    private static final boolean DEBUG_ROTATION = false;
+
     private static AudioDisplayListener sDisplayListener;
     private static FoldStateListener sFoldStateListener;
 
@@ -98,8 +101,8 @@
         // Even though we're responding to device orientation events,
         // use display rotation so audio stays in sync with video/dialogs
         // TODO(b/148458001): Support multi-display
-        int newRotation = ((WindowManager) sContext.getSystemService(
-                Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
+        int newRotation = DisplayManagerGlobal.getInstance()
+                .getDisplayInfo(Display.DEFAULT_DISPLAY).rotation;
         synchronized(sRotationLock) {
             if (newRotation != sDeviceRotation) {
                 sDeviceRotation = newRotation;
@@ -109,7 +112,9 @@
     }
 
     private static void publishRotation(int rotation) {
-        Log.v(TAG, "publishing device rotation =" + rotation + " (x90deg)");
+        if (DEBUG_ROTATION) {
+            Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
+        }
         switch (rotation) {
             case Surface.ROTATION_0:
                 AudioSystem.setParameters("rotation=0");
@@ -159,6 +164,9 @@
 
         @Override
         public void onDisplayChanged(int displayId) {
+            if (DEBUG_ROTATION) {
+                Log.i(TAG, "onDisplayChanged diplayId:" + displayId);
+            }
             updateOrientation();
         }
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 2c1be72..b3ba20b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.permission;
 
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
@@ -1364,8 +1365,8 @@
             // the only use case for this, so simply override here.
             if (!permissionGranted
                     && Process.isIsolated(uid) // simple check which fails-fast for the common case
-                    && (permission.equals(RECORD_AUDIO)
-                    || permission.equals(CAPTURE_AUDIO_HOTWORD))) {
+                    && (permission.equals(RECORD_AUDIO) || permission.equals(CAPTURE_AUDIO_HOTWORD)
+                    || permission.equals(CAPTURE_AUDIO_OUTPUT))) {
                 HotwordDetectionServiceProvider hotwordServiceProvider =
                         permissionManagerServiceInt.getHotwordDetectionServiceProvider();
                 permissionGranted = hotwordServiceProvider != null
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index d2c4ec4..95badb3 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -449,8 +449,8 @@
 
     /**
      * Provides the uid of the currently active
-     * {@link android.service.voice.HotwordDetectionService}, which should be granted RECORD_AUDIO
-     * and CAPTURE_AUDIO_HOTWORD permissions.
+     * {@link android.service.voice.HotwordDetectionService}, which should be granted RECORD_AUDIO,
+     * CAPTURE_AUDIO_HOTWORD and CAPTURE_AUDIO_OUTPUT permissions.
      */
     interface HotwordDetectionServiceProvider {
         int getUid();
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 cdc9d71..4942464 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -67,6 +67,7 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TARE_AFFORDABILITY_CHANGED;
+import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TEMPORARY_QUOTA_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
@@ -83,6 +84,7 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
 import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
 import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
@@ -110,6 +112,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -654,6 +657,7 @@
         setDeviceConfigLong(KEY_MIN_INTERVAL, 0);
         mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
         mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
+        mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[FREQUENT_INDEX]);
         doReturn(50).when(mDeviceConfigProperties)
                 .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]), anyInt());
         doReturn(35).when(mDeviceConfigProperties)
@@ -732,6 +736,7 @@
         setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, 55);
         setDeviceConfigLong(KEY_MIN_DEVICE_IDLE_FUZZ, 60);
         setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 65);
+        setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 70);
         assertEquals(5, mService.mConstants.MIN_FUTURITY);
         assertEquals(10, mService.mConstants.MIN_INTERVAL);
         assertEquals(15, mService.mConstants.MAX_INTERVAL);
@@ -745,6 +750,7 @@
         assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY);
         assertEquals(60, mService.mConstants.MIN_DEVICE_IDLE_FUZZ);
         assertEquals(65, mService.mConstants.MAX_DEVICE_IDLE_FUZZ);
+        assertEquals(70, mService.mConstants.TEMPORARY_QUOTA_BUMP);
     }
 
     @Test
@@ -879,6 +885,7 @@
         for (int i = 0; i < quota; i++) {
             alarmSetter.accept(firstTrigger + i);
             mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
             mTestTimer.expire();
         }
         // This one should get deferred on set
@@ -897,6 +904,7 @@
         alarmSetter.accept(firstTrigger + quota);
         for (int i = 0; i < quota; i++) {
             mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
             mTestTimer.expire();
         }
         final long expectedNextTrigger = firstTrigger + window;
@@ -914,6 +922,7 @@
         alarmSetter.accept(expectedNextTrigger);
         for (int i = 0; i < quota; i++) {
             mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
             mTestTimer.expire();
         }
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
@@ -1919,16 +1928,14 @@
                 getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
-        mService.removeLocked(TEST_CALLING_UID,
-                REMOVE_REASON_UNDEFINED);
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
         testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
                 trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
-        mService.removeLocked(TEST_CALLING_UID,
-                REMOVE_REASON_UNDEFINED);
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
         testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
@@ -3303,7 +3310,7 @@
                 Arrays.asList(package4));
 
         mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
+        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
                 package1,
                 package3,
         });
@@ -3315,7 +3322,7 @@
         assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
 
         mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
+        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
                 package1,
                 package3,
         });
@@ -3407,6 +3414,218 @@
         assertFalse(mService.hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
+    @Test
+    public void temporaryQuotaReserve_hasQuota() {
+        final int quotaToFill = 5;
+        final String package1 = "package1";
+        final int user1 = 123;
+        final long startTime = 54;
+        final long quotaDuration = 17;
+
+        final AlarmManagerService.TemporaryQuotaReserve quotaReserve =
+                new AlarmManagerService.TemporaryQuotaReserve(quotaDuration);
+        quotaReserve.replenishQuota(package1, user1, quotaToFill, startTime);
+
+        for (long time = startTime; time <= startTime + quotaDuration; time++) {
+            assertTrue(quotaReserve.hasQuota(package1, user1, time));
+            assertFalse(quotaReserve.hasQuota("some.other.package", 21, time));
+            assertFalse(quotaReserve.hasQuota(package1, 321, time));
+        }
+
+        assertFalse(quotaReserve.hasQuota(package1, user1, startTime + quotaDuration + 1));
+        assertFalse(quotaReserve.hasQuota(package1, user1, startTime + quotaDuration + 435421));
+
+        for (int i = 0; i < quotaToFill - 1; i++) {
+            assertTrue(i < quotaDuration);
+            // Use record usage multiple times with the same timestamp.
+            quotaReserve.recordUsage(package1, user1, startTime + i);
+            quotaReserve.recordUsage(package1, user1, startTime + i);
+            quotaReserve.recordUsage(package1, user1, startTime + i);
+            quotaReserve.recordUsage(package1, user1, startTime + i);
+
+            // Quota should not run out in this loop.
+            assertTrue(quotaReserve.hasQuota(package1, user1, startTime + i));
+        }
+        quotaReserve.recordUsage(package1, user1, startTime + quotaDuration);
+
+        // Should be out of quota now.
+        for (long time = startTime; time <= startTime + quotaDuration; time++) {
+            assertFalse(quotaReserve.hasQuota(package1, user1, time));
+        }
+    }
+
+    @Test
+    public void temporaryQuotaReserve_removeForPackage() {
+        final String[] packages = new String[]{"package1", "test.package2"};
+        final int userId = 472;
+        final long startTime = 59;
+        final long quotaDuration = 100;
+
+        final AlarmManagerService.TemporaryQuotaReserve quotaReserve =
+                new AlarmManagerService.TemporaryQuotaReserve(quotaDuration);
+
+        quotaReserve.replenishQuota(packages[0], userId, 10, startTime);
+        quotaReserve.replenishQuota(packages[1], userId, 10, startTime);
+
+        assertTrue(quotaReserve.hasQuota(packages[0], userId, startTime + 1));
+        assertTrue(quotaReserve.hasQuota(packages[1], userId, startTime + 1));
+
+        quotaReserve.removeForPackage(packages[0], userId);
+
+        assertFalse(quotaReserve.hasQuota(packages[0], userId, startTime + 1));
+        assertTrue(quotaReserve.hasQuota(packages[1], userId, startTime + 1));
+    }
+
+    @Test
+    public void temporaryQuotaReserve_removeForUser() {
+        final String[] packagesUser1 = new String[]{"test1.package1", "test1.package2"};
+        final String[] packagesUser2 = new String[]{"test2.p1", "test2.p2", "test2.p3"};
+        final int user1 = 3201;
+        final int user2 = 5409;
+        final long startTime = 59;
+        final long quotaDuration = 100;
+
+        final AlarmManagerService.TemporaryQuotaReserve quotaReserve =
+                new AlarmManagerService.TemporaryQuotaReserve(quotaDuration);
+
+        for (String packageUser1 : packagesUser1) {
+            quotaReserve.replenishQuota(packageUser1, user1, 10, startTime);
+        }
+        for (String packageUser2 : packagesUser2) {
+            quotaReserve.replenishQuota(packageUser2, user2, 10, startTime);
+        }
+
+        for (String packageUser1 : packagesUser1) {
+            assertTrue(quotaReserve.hasQuota(packageUser1, user1, startTime));
+        }
+        for (String packageUser2 : packagesUser2) {
+            assertTrue(quotaReserve.hasQuota(packageUser2, user2, startTime));
+        }
+
+        quotaReserve.removeForUser(user2);
+
+        for (String packageUser1 : packagesUser1) {
+            assertTrue(quotaReserve.hasQuota(packageUser1, user1, startTime));
+        }
+        for (String packageUser2 : packagesUser2) {
+            assertFalse(quotaReserve.hasQuota(packageUser2, user2, startTime));
+        }
+    }
+
+    @Test
+    public void triggerTemporaryQuotaBump_zeroQuota() {
+        setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 0);
+
+        mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        verifyZeroInteractions(mPackageManagerInternal);
+        verifyZeroInteractions(mService.mHandler);
+    }
+
+    private void testTemporaryQuota_bumpedAfterDeferral(int standbyBucket) throws Exception {
+        final int temporaryQuota = 31;
+        setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, temporaryQuota);
+
+        final int standbyQuota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < standbyQuota + 1; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+        }
+
+        for (int i = 0; i < standbyQuota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+
+        // The last alarm should be deferred due to exceeding the quota
+        final long deferredTrigger = firstTrigger + mAppStandbyWindow;
+        assertEquals(deferredTrigger, mTestTimer.getElapsed());
+
+        // Triggering temporary quota now.
+        mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        assertAndHandleMessageSync(TEMPORARY_QUOTA_CHANGED);
+        // The last alarm should now be rescheduled to go as per original expectations
+        final long originalTrigger = firstTrigger + standbyQuota;
+        assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
+    }
+
+
+    @Test
+    public void temporaryQuota_bumpedAfterDeferral_active() throws Exception {
+        testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedAfterDeferral_working() throws Exception {
+        testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedAfterDeferral_frequent() throws Exception {
+        testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedAfterDeferral_rare() throws Exception {
+        testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_RARE);
+    }
+
+    private void testTemporaryQuota_bumpedBeforeDeferral(int standbyBucket) throws Exception {
+        final int temporaryQuota = 7;
+        setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, temporaryQuota);
+
+        final int standbyQuota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+
+        mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        // No need to handle message TEMPORARY_QUOTA_CHANGED, as the quota change doesn't need to
+        // trigger a re-evaluation in this test.
+        testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
+
+        // refresh the state.
+        mService.removeLocked(TEST_CALLING_PACKAGE);
+        mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+
+        mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
+
+        // refresh the state.
+        mService.removeLocked(TEST_CALLING_PACKAGE);
+        mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+
+        mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedBeforeDeferral_active() throws Exception {
+        testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedBeforeDeferral_working() throws Exception {
+        testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedBeforeDeferral_frequent() throws Exception {
+        testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void temporaryQuota_bumpedBeforeDeferral_rare() throws Exception {
+        testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_RARE);
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {