Fix issue #34471029: Don't allow audio use from background apps.

This is becoming a common pattern (keeping track of which processes
are cached and not allowing them to do stuff in that state), so I
am turning this in to a general mechanism for monitoring this state
through the activity manager's IUidObserver.  Now we can just have
AudoService implement its own IUidObserver to get this state and
update which uids it is blocking.

This required making some changes to uid change reports so that
the integer is now a bit mask instead of an enumerations, but that
is what it was already turning in to anyway.  (This gets rid of
the crazy GONE_IDLE constant that we'd needed to add before because
it wasn't a bit mask).

Eventually the power manager should be changed to be told about
these changes to cached state instead of listening to every proc
state change, but we'll do that later, it is more disruption than
I want to take for now.  However, while working on this, I noticed
that the power manager had regressed in the cached uids it would
actually block, because the activity manager was no longer telling
it about all uids that are idle.  (I think this happened when I had
to change the default idle state of UidRecord to true.)  So I am
adding a bit of new code to keep track of what idle state we last
reported to observers, to make sure we tell it about newly created
uids that are idle but have never actually become active.

Test: runtest -c com.android.server.am.ActivityManagerServiceTest frameworks-services

Change-Id: I7bfd46bacadd4cab2a69f40e6e52afb4e67b456a
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 20a4ba1..d432544 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -172,6 +172,9 @@
         @Override
         public void onUidIdle(int uid, boolean disabled) {
         }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
     }
 
     final ArrayMap<OnUidImportanceListener, UidObserver> mImportanceListeners = new ArrayMap<>();
@@ -563,6 +566,9 @@
     /** @hide Flag for registerUidObserver: report uid has become active. */
     public static final int UID_OBSERVER_ACTIVE = 1<<3;
 
+    /** @hide Flag for registerUidObserver: report uid cached state has changed. */
+    public static final int UID_OBSERVER_CACHED = 1<<4;
+
     /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: normal free-to-run operation. */
     public static final int APP_START_MODE_NORMAL = 0;
 
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index ae64875..01a9cbf 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -43,4 +43,14 @@
      * a sufficient period of time, or all of its processes have gone away.
      */
     void onUidIdle(int uid, boolean disabled);
+
+    /**
+     * Report when the cached state of a uid has changed.
+     * If true, a uid has become cached -- that is, it has some active processes that are
+     * all in the cached state.  It should be doing as little as possible at this point.
+     * If false, that a uid is no longer cached.  This will only be called after
+     * onUidCached() has been reported true.  It will happen when either one of its actively
+     * running processes is no longer cached, or it no longer has any actively running processes.
+     */
+    void onUidCachedChanged(int uid, boolean cached);
 }
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 2b5ac5e..0a1de33 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -59,15 +59,4 @@
 
         int getRingerModeAffectedStreams(int streams);
     }
-
-    /**
-     * Disable or restore the ability to play audio for a given UID.
-     * When a UID isn't meant to be tracked anymore (e.g. client died), re-enable audio for this UID
-     * to prevent disabling audio for future UIDs that would reuse the same value.
-     * This operation is asynchronous.
-     * @param disable when true, prevents playback of audio streams from the given uid. If false,
-     *         restores the ability to play, or no-op if playback hadn't been disabled before.
-     * @param uid the client UID whose ability to play will be affected.
-     */
-    public abstract void disableAudioForUid(boolean disable, int uid);
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a15992d..e860dd6 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2893,11 +2893,10 @@
     }
 
     final class UidObserver extends IUidObserver.Stub {
-        @Override public void onUidStateChanged(int uid, int procState,
-                long procStateSeq) throws RemoteException {
+        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
         }
 
-        @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
+        @Override public void onUidGone(int uid, boolean disabled) {
             if (disabled) {
                 synchronized (mLock) {
                     removeForStoppedLocked(uid);
@@ -2905,16 +2904,19 @@
             }
         }
 
-        @Override public void onUidActive(int uid) throws RemoteException {
+        @Override public void onUidActive(int uid) {
         }
 
-        @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+        @Override public void onUidIdle(int uid, boolean disabled) {
             if (disabled) {
                 synchronized (mLock) {
                     removeForStoppedLocked(uid);
                 }
             }
         }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
     };
 
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ec1709a..150b52e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4344,8 +4344,7 @@
         if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
             for (int j = 0; j < N; ++j) {
                 final UidRecord.ChangeItem item = mActiveUidChanges[j];
-                if (item.change == UidRecord.CHANGE_GONE
-                        || item.change == UidRecord.CHANGE_GONE_IDLE) {
+                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
                     mValidateUids.remove(item.uid);
                 } else {
                     UidRecord validateUid = mValidateUids.get(item.uid);
@@ -4353,9 +4352,9 @@
                         validateUid = new UidRecord(item.uid);
                         mValidateUids.put(item.uid, validateUid);
                     }
-                    if (item.change == UidRecord.CHANGE_IDLE) {
+                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
                         validateUid.idle = true;
-                    } else if (item.change == UidRecord.CHANGE_ACTIVE) {
+                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
                         validateUid.idle = false;
                     }
                     validateUid.curProcState = validateUid.setProcState = item.processState;
@@ -4380,22 +4379,37 @@
             for (int j = 0; j < changesSize; j++) {
                 UidRecord.ChangeItem item = mActiveUidChanges[j];
                 final int change = item.change;
-                if (change == UidRecord.CHANGE_IDLE
-                        || change == UidRecord.CHANGE_GONE_IDLE) {
+                if (change == UidRecord.CHANGE_PROCSTATE &&
+                        (reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
+                    // No-op common case: no significant change, the observer is not
+                    // interested in all proc state changes.
+                    continue;
+                }
+                if ((change & UidRecord.CHANGE_IDLE) != 0) {
                     if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
                         if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                                 "UID idle uid=" + item.uid);
                         observer.onUidIdle(item.uid, item.ephemeral);
                     }
-                } else if (change == UidRecord.CHANGE_ACTIVE) {
+                } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
                     if ((reg.which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
                         if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                                 "UID active uid=" + item.uid);
                         observer.onUidActive(item.uid);
                     }
                 }
-                if (change == UidRecord.CHANGE_GONE
-                        || change == UidRecord.CHANGE_GONE_IDLE) {
+                if ((reg.which & ActivityManager.UID_OBSERVER_CACHED) != 0) {
+                    if ((change & UidRecord.CHANGE_CACHED) != 0) {
+                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                "UID cached uid=" + item.uid);
+                        observer.onUidCachedChanged(item.uid, true);
+                    } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
+                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                "UID active uid=" + item.uid);
+                        observer.onUidCachedChanged(item.uid, false);
+                    }
+                }
+                if ((change & UidRecord.CHANGE_GONE) != 0) {
                     if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) {
                         if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                                 "UID gone uid=" + item.uid);
@@ -22170,10 +22184,10 @@
             }
             if (uidRec != null) {
                 uidRec.pendingChange = pendingChange;
-                if (change == UidRecord.CHANGE_GONE && !uidRec.idle) {
+                if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
                     // If this uid is going away, and we haven't yet reported it is gone,
                     // then do so now.
-                    change = UidRecord.CHANGE_GONE_IDLE;
+                    change |= UidRecord.CHANGE_IDLE;
                 }
             } else if (uid < 0) {
                 throw new IllegalArgumentException("No UidRecord or uid");
@@ -22183,8 +22197,26 @@
             mPendingUidChanges.add(pendingChange);
         } else {
             pendingChange = uidRec.pendingChange;
-            if (change == UidRecord.CHANGE_GONE && pendingChange.change == UidRecord.CHANGE_IDLE) {
-                change = UidRecord.CHANGE_GONE_IDLE;
+            // If there is no change in idle or active state, then keep whatever was pending.
+            if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
+                change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
+                        | UidRecord.CHANGE_ACTIVE));
+            }
+            // If there is no change in cached or uncached state, then keep whatever was pending.
+            if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
+                change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
+                        | UidRecord.CHANGE_UNCACHED));
+            }
+            // If this is a report of the UID being gone, then we shouldn't keep any previous
+            // report of it being active or cached.  (That is, a gone uid is never active,
+            // and never cached.)
+            if ((change & UidRecord.CHANGE_GONE) != 0) {
+                change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
+                if (!uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change |= UidRecord.CHANGE_IDLE;
+                }
             }
         }
         pendingChange.change = change;
@@ -22193,27 +22225,26 @@
         pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
         pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
         if (uidRec != null) {
+            uidRec.lastReportedChange = change;
             uidRec.updateLastDispatchedProcStateSeq(change);
         }
 
         // Directly update the power manager, since we sit on top of it and it is critical
         // it be kept in sync (so wake locks will be held as soon as appropriate).
         if (mLocalPowerManager != null) {
-            switch (change) {
-                case UidRecord.CHANGE_GONE:
-                case UidRecord.CHANGE_GONE_IDLE:
-                    mLocalPowerManager.uidGone(pendingChange.uid);
-                    break;
-                case UidRecord.CHANGE_IDLE:
-                    mLocalPowerManager.uidIdle(pendingChange.uid);
-                    break;
-                case UidRecord.CHANGE_ACTIVE:
-                    mLocalPowerManager.uidActive(pendingChange.uid);
-                    break;
-                default:
-                    mLocalPowerManager.updateUidProcState(pendingChange.uid,
-                            pendingChange.processState);
-                    break;
+            // TO DO: dispatch cached/uncached changes here, so we don't need to report
+            // all proc state changes.
+            if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+                mLocalPowerManager.uidActive(pendingChange.uid);
+            }
+            if ((change & UidRecord.CHANGE_IDLE) != 0) {
+                mLocalPowerManager.uidIdle(pendingChange.uid);
+            }
+            if ((change & UidRecord.CHANGE_GONE) != 0) {
+                mLocalPowerManager.uidGone(pendingChange.uid);
+            } else {
+                mLocalPowerManager.updateUidProcState(pendingChange.uid,
+                        pendingChange.processState);
             }
         }
     }
@@ -22797,6 +22828,9 @@
                                     mConstants.BACKGROUND_SETTLE_TIME);
                         }
                     }
+                    if (!uidRec.setIdle) {
+                        uidChange = UidRecord.CHANGE_IDLE;
+                    }
                 } else {
                     if (uidRec.idle) {
                         uidChange = UidRecord.CHANGE_ACTIVE;
@@ -22805,8 +22839,17 @@
                     }
                     uidRec.lastBackgroundTime = 0;
                 }
+                final boolean wasCached = uidRec.setProcState
+                        > ActivityManager.PROCESS_STATE_RECEIVER && uidRec.setProcState
+                        != ActivityManager.PROCESS_STATE_NONEXISTENT;
+                final boolean isCached = uidRec.curProcState
+                        > ActivityManager.PROCESS_STATE_RECEIVER;
+                if (wasCached != isCached) {
+                    uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
+                }
                 uidRec.setProcState = uidRec.curProcState;
                 uidRec.setWhitelist = uidRec.curWhitelist;
+                uidRec.setIdle = uidRec.idle;
                 enqueueUidChangeLocked(uidRec, -1, uidChange);
                 noteUidProcessState(uidRec.uid, uidRec.curProcState);
                 if (uidRec.foregroundServices) {
@@ -22881,6 +22924,7 @@
                                     userId == UserHandle.getUserId(uidRec.uid)) {
                                 EventLogTags.writeAmUidIdle(uidRec.uid);
                                 uidRec.idle = true;
+                                uidRec.setIdle = true;
                                 Slog.w(TAG, "Idling uid " + UserHandle.formatUid(uidRec.uid)
                                         + " from package " + packageName + " user " + userId);
                                 doStopUidLocked(uidRec.uid, uidRec);
@@ -22916,6 +22960,7 @@
                     if (bgTime <= maxBgTime) {
                         EventLogTags.writeAmUidIdle(uidRec.uid);
                         uidRec.idle = true;
+                        uidRec.setIdle = true;
                         doStopUidLocked(uidRec.uid, uidRec);
                     } else {
                         if (nextTime == 0 || nextTime > bgTime) {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index c411bce..8efcb4f 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -39,6 +39,7 @@
     boolean curWhitelist;
     boolean setWhitelist;
     boolean idle;
+    boolean setIdle;
     int numProcs;
 
     /**
@@ -79,10 +80,11 @@
     final Object networkStateLock = new Object();
 
     static final int CHANGE_PROCSTATE = 0;
-    static final int CHANGE_GONE = 1;
-    static final int CHANGE_GONE_IDLE = 2;
-    static final int CHANGE_IDLE = 3;
-    static final int CHANGE_ACTIVE = 4;
+    static final int CHANGE_GONE = 1<<0;
+    static final int CHANGE_IDLE = 1<<1;
+    static final int CHANGE_ACTIVE = 1<<2;
+    static final int CHANGE_CACHED = 1<<3;
+    static final int CHANGE_UNCACHED = 1<<4;
 
     static final class ChangeItem {
         UidRecord uidRecord;
@@ -94,6 +96,7 @@
     }
 
     ChangeItem pendingChange;
+    int lastReportedChange;
 
     public UidRecord(int _uid) {
         uid = _uid;
@@ -112,12 +115,12 @@
     }
 
     /**
-     * If the change being dispatched is neither CHANGE_GONE nor CHANGE_GONE_IDLE (not interested in
+     * If the change being dispatched is not CHANGE_GONE (not interested in
      * these changes), then update the {@link #lastDispatchedProcStateSeq} with
      * {@link #curProcStateSeq}.
      */
     public void updateLastDispatchedProcStateSeq(int changeToDispatch) {
-        if (changeToDispatch != CHANGE_GONE && changeToDispatch != CHANGE_GONE_IDLE) {
+        if ((changeToDispatch & CHANGE_GONE) == 0) {
             lastDispatchedProcStateSeq = curProcStateSeq;
         }
     }
@@ -146,6 +149,41 @@
         if (idle) {
             sb.append(" idle");
         }
+        if (lastReportedChange != 0) {
+            sb.append(" change:");
+            boolean printed = false;
+            if ((lastReportedChange & CHANGE_GONE) != 0) {
+                printed = true;
+                sb.append("gone");
+            }
+            if ((lastReportedChange & CHANGE_IDLE) != 0) {
+                if (printed) {
+                    sb.append("|");
+                }
+                printed = true;
+                sb.append("idle");
+            }
+            if ((lastReportedChange & CHANGE_ACTIVE) != 0) {
+                if (printed) {
+                    sb.append("|");
+                }
+                printed = true;
+                sb.append("active");
+            }
+            if ((lastReportedChange & CHANGE_CACHED) != 0) {
+                if (printed) {
+                    sb.append("|");
+                }
+                printed = true;
+                sb.append("cached");
+            }
+            if ((lastReportedChange & CHANGE_UNCACHED) != 0) {
+                if (printed) {
+                    sb.append("|");
+                }
+                sb.append("uncached");
+            }
+        }
         sb.append(" procs:");
         sb.append(numProcs);
         sb.append(" seq(");
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7eee3e4..f868a33 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -28,6 +28,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.IUidObserver;
 import android.app.NotificationManager;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
@@ -624,6 +625,32 @@
         }
     }
 
+    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+        }
+
+        @Override public void onUidGone(int uid, boolean disabled) {
+            // Once the uid is no longer running, no need to keep trying to disable its audio.
+            disableAudioForUid(false, uid);
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+            disableAudioForUid(cached, uid);
+        }
+
+        private void disableAudioForUid(boolean disable, int uid) {
+            queueMsgUnderWakeLock(mAudioHandler, MSG_DISABLE_AUDIO_FOR_UID,
+                    disable ? 1 : 0 /* arg1 */,  uid /* arg2 */,
+                    null /* obj */,  0 /* delay */);
+        }
+    };
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -757,6 +784,13 @@
     public void systemReady() {
         sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE,
                 0, 0, null, 0);
+        try {
+            ActivityManager.getService().registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE,
+                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
     }
 
     public void onSystemReady() {
@@ -6602,13 +6636,6 @@
                 }
             }
         }
-
-        @Override
-        public void disableAudioForUid(boolean disable, int uid) {
-            queueMsgUnderWakeLock(mAudioHandler, MSG_DISABLE_AUDIO_FOR_UID,
-                    disable ? 1 : 0 /* arg1 */,  uid /* arg2 */,
-                    null /* obj */,  0 /* delay */);
-        }
     }
 
     //==========================================================================================
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 3fea51f..d99c27c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -603,12 +603,11 @@
     }
 
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
-        @Override public void onUidStateChanged(int uid, int procState,
-                long procStateSeq) throws RemoteException {
+        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
             updateUidState(uid, procState);
         }
 
-        @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
+        @Override public void onUidGone(int uid, boolean disabled) {
             updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
             if (disabled) {
                 cancelJobsForUid(uid, "uid gone");
@@ -618,11 +617,14 @@
         @Override public void onUidActive(int uid) throws RemoteException {
         }
 
-        @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+        @Override public void onUidIdle(int uid, boolean disabled) {
             if (disabled) {
                 cancelJobsForUid(uid, "app uid idle");
             }
         }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
     };
 
     public Object getLock() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0f1cfa5..b6af076 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -800,20 +800,22 @@
     }
 
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
-        @Override public void onUidStateChanged(int uid, int procState,
-                long procStateSeq) throws RemoteException {
+        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
             mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED,
                     uid, procState, procStateSeq).sendToTarget();
         }
 
-        @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
+        @Override public void onUidGone(int uid, boolean disabled) {
             mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
         }
 
-        @Override public void onUidActive(int uid) throws RemoteException {
+        @Override public void onUidActive(int uid) {
         }
 
-        @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+        @Override public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
         }
     };
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 7911972..0e572d8 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -483,22 +483,24 @@
 
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
-        public void onUidStateChanged(int uid, int procState, long procStateSeq)
-                throws RemoteException {
+        public void onUidStateChanged(int uid, int procState, long procStateSeq) {
             handleOnUidStateChanged(uid, procState);
         }
 
         @Override
-        public void onUidGone(int uid, boolean disabled) throws RemoteException {
+        public void onUidGone(int uid, boolean disabled) {
             handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
         }
 
         @Override
-        public void onUidActive(int uid) throws RemoteException {
+        public void onUidActive(int uid) {
         }
 
         @Override
-        public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+        public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
         }
     };
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a94484a..c688542 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2949,8 +2949,15 @@
             boolean disabled = false;
             final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
             if (appid >= Process.FIRST_APPLICATION_UID) {
+                // Cached inactive processes are never allowed to hold wake locks.
+                if (mConstants.NO_CACHED_WAKE_LOCKS) {
+                    disabled = !wakeLock.mUidState.mActive &&
+                            wakeLock.mUidState.mProcState
+                                    != ActivityManager.PROCESS_STATE_NONEXISTENT &&
+                            wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER;
+                }
                 if (mDeviceIdleMode) {
-                    // If we are in idle mode, we will ignore all partial wake locks that are
+                    // If we are in idle mode, we will also ignore all partial wake locks that are
                     // for application uids that are not whitelisted.
                     final UidState state = wakeLock.mUidState;
                     if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
@@ -2959,11 +2966,6 @@
                             state.mProcState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
                         disabled = true;
                     }
-                } else if (mConstants.NO_CACHED_WAKE_LOCKS) {
-                    disabled = !wakeLock.mUidState.mActive &&
-                            wakeLock.mUidState.mProcState
-                                    != ActivityManager.PROCESS_STATE_NONEXISTENT &&
-                            wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER;
                 }
             }
             if (wakeLock.mDisabled != disabled) {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 6701b71..0ffc61a 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -113,7 +113,7 @@
     private static final int[] UID_RECORD_CHANGES = {
         UidRecord.CHANGE_PROCSTATE,
         UidRecord.CHANGE_GONE,
-        UidRecord.CHANGE_GONE_IDLE,
+        UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE,
         UidRecord.CHANGE_IDLE,
         UidRecord.CHANGE_ACTIVE
     };
@@ -417,7 +417,7 @@
                 // delivered to this observer.
                 final int[] changesToVerify = {
                     UidRecord.CHANGE_IDLE,
-                    UidRecord.CHANGE_GONE_IDLE
+                    UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE
                 };
                 verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
                         (observer, changeItem) -> {
@@ -439,7 +439,7 @@
                 // delivered to this observer.
                 final int[] changesToVerify = {
                         UidRecord.CHANGE_GONE,
-                        UidRecord.CHANGE_GONE_IDLE
+                        UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE
                 };
                 verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
                         (observer, changeItem) -> {
@@ -585,7 +585,7 @@
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
             final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
-            if (item.change == UidRecord.CHANGE_GONE || item.change == UidRecord.CHANGE_GONE_IDLE) {
+            if ((item.change & UidRecord.CHANGE_GONE) != 0) {
                 assertNull("validateUidRecord should be null since the change is either "
                         + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
             } else {
@@ -614,7 +614,8 @@
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
             // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
             // distribution for this assignment.
-            item.change = (i % 2) == 0 ? UidRecord.CHANGE_GONE_IDLE : UidRecord.CHANGE_GONE;
+            item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE)
+                    : UidRecord.CHANGE_GONE;
         }
         mAms.mPendingUidChanges.addAll(pendingItemsForUids);
         mAms.dispatchUidsChanged();
@@ -653,8 +654,7 @@
             mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
             // Verify there is no effect on curProcStateSeq.
             assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
-            if (changeToDispatch == UidRecord.CHANGE_GONE
-                    || changeToDispatch == UidRecord.CHANGE_GONE_IDLE) {
+            if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
                 // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
                 // lastProcStateSeqDispatchedToObservers is not updated.
                 assertNotEquals(uidRecord.curProcStateSeq,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 073a17e..25e471c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -363,19 +363,22 @@
         }
 
         @Override
-        public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+        public void onUidIdle(int uid, boolean disabled) {
             // Ignored
         }
 
         @Override
-        public void onUidGone(int uid, boolean disabled) throws RemoteException {
+        public void onUidGone(int uid, boolean disabled) {
             onUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT, 0);
         }
 
         @Override
-        public void onUidActive(int uid) throws RemoteException {
+        public void onUidActive(int uid) {
             // Ignored
         }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
     };
 
     @Override