Add lastProcStateSeqDispatchedToObservers to UidRecord.

This will help in identifying the last procStateSeq for which AMS
notified NPMS about the process state change.

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

Change-Id: I741a8ffe31de92070f3ebbc9c4f95fc1c0a340cd
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c9021ad..71d7b0f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1721,7 +1721,7 @@
 
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
-    final UiHandler mUiHandler;
+    final Handler mUiHandler;
 
     final ActivityManagerConstants mConstants;
 
@@ -2706,11 +2706,11 @@
     }
 
     @VisibleForTesting
-    public ActivityManagerService(AppOpsService appOpsService) {
+    public ActivityManagerService(Injector injector) {
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
-        mAppOpsService = appOpsService;
+        mAppOpsService = injector.getAppOpsService();
         mBatteryStatsService = null;
         mCompatModePackages = null;
         mConstants = null;
@@ -2728,7 +2728,7 @@
         mStackSupervisor = null;
         mSystemThread = null;
         mTaskChangeNotificationController = null;
-        mUiHandler = null;
+        mUiHandler = injector.getHandler();
         mUserController = null;
     }
 
@@ -21633,7 +21633,8 @@
                 packages[0]);
     }
 
-    private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+    @VisibleForTesting
+    final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
         final UidRecord.ChangeItem pendingChange;
         if (uidRec == null || uidRec.pendingChange == null) {
             if (mPendingUidChanges.size() == 0) {
@@ -21675,6 +21676,9 @@
                 ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
         pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
         pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+        if (uidRec != null) {
+            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).
@@ -23590,4 +23594,9 @@
             throw new IllegalStateException("Process disappeared");
         }
     }
+
+    static interface Injector {
+        public AppOpsService getAppOpsService();
+        public Handler getHandler();
+    }
 }
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index e002e97..1164876 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -34,18 +34,26 @@
     boolean setWhitelist;
     boolean idle;
     int numProcs;
+
     /**
      * Sequence number associated with the {@link #curProcState}. This is incremented using
      * {@link ActivityManagerService#mProcStateSeqCounter}
      * when {@link #curProcState} changes from background to foreground or vice versa.
      */
     long curProcStateSeq;
+
     /**
      * Last seq number for which NetworkPolicyManagerService notified ActivityManagerService that
      * network policies rules were updated.
      */
     long lastNetworkUpdatedProcStateSeq;
 
+    /**
+     * Last seq number for which AcitivityManagerService dispatched uid state change to
+     * NetworkPolicyManagerService.
+     */
+    long lastDispatchedProcStateSeq;
+
     static final int CHANGE_PROCSTATE = 0;
     static final int CHANGE_GONE = 1;
     static final int CHANGE_GONE_IDLE = 2;
@@ -72,6 +80,17 @@
         curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
     }
 
+    /**
+     * If the change being dispatched is neither CHANGE_GONE nor CHANGE_GONE_IDLE (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) {
+            lastDispatchedProcStateSeq = curProcStateSeq;
+        }
+    }
+
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("UidRecord{");
@@ -99,6 +118,8 @@
         sb.append(curProcStateSeq);
         sb.append(" lastNetworkUpdatedProcStateSeq:");
         sb.append(lastNetworkUpdatedProcStateSeq);
+        sb.append(" lastDispatchedProcStateSeq:");
+        sb.append(lastDispatchedProcStateSeq);
         sb.append("}");
         return sb.toString();
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index 6a050ad..b5934ee 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -22,11 +22,11 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.server.AppOpsService;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Test class for {@link ActivityManagerInternal}.
@@ -46,11 +46,15 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ActivityManagerInternalTest {
+    @Mock private ActivityManagerService.Injector mMockInjector;
+
     private ActivityManagerService mAms;
     private ActivityManagerInternal mAmi;
     @Before
     public void setUp() {
-        mAms = new ActivityManagerService((AppOpsService) null);
+        MockitoAnnotations.initMocks(this);
+
+        mAms = new ActivityManagerService(mMockInjector);
         mAmi = mAms.new LocalService();
     }
 
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 556b218..4e9333f 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,6 +28,8 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.util.DebugUtils.valueToString;
+import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.Injector;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -35,6 +37,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -42,14 +45,21 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.server.AppOpsService;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,7 +69,9 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Function;
 
 /**
@@ -80,58 +92,83 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ActivityManagerServiceTest {
+    private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
+
     private static final int TEST_UID = 111;
 
+    private static final long TEST_PROC_STATE_SEQ1 = 555;
+    private static final long TEST_PROC_STATE_SEQ2 = 556;
+
+    private static final int[] UID_RECORD_CHANGES = {
+        UidRecord.CHANGE_PROCSTATE,
+        UidRecord.CHANGE_GONE,
+        UidRecord.CHANGE_GONE_IDLE,
+        UidRecord.CHANGE_IDLE,
+        UidRecord.CHANGE_ACTIVE
+    };
+
     @Mock private AppOpsService mAppOpsService;
 
+    private ActivityManagerService mAms;
+    private HandlerThread mHandlerThread;
+    private TestHandler mHandler;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new TestHandler(mHandlerThread.getLooper());
+        mAms = new ActivityManagerService(new TestInjector());
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
     }
 
     @Test
     public void testIncrementProcStateSeqIfNeeded() {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
         final UidRecord uidRec = new UidRecord(TEST_UID);
 
-        assertEquals("Initially global seq counter should be 0", 0, ams.mProcStateSeqCounter);
+        assertEquals("Initially global seq counter should be 0", 0, mAms.mProcStateSeqCounter);
         assertEquals("Initially seq counter in uidRecord should be 0", 0, uidRec.curProcStateSeq);
 
         // Uid state is not moving from background to foreground or vice versa.
         uidRec.setProcState = PROCESS_STATE_TOP;
         uidRec.curProcState = PROCESS_STATE_TOP;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(0, ams.mProcStateSeqCounter);
+        mAms.incrementProcStateSeqIfNeeded(uidRec);
+        assertEquals(0, mAms.mProcStateSeqCounter);
         assertEquals(0, uidRec.curProcStateSeq);
 
         // Uid state is moving from foreground to background.
         uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
         uidRec.setProcState = PROCESS_STATE_SERVICE;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(1, ams.mProcStateSeqCounter);
+        mAms.incrementProcStateSeqIfNeeded(uidRec);
+        assertEquals(1, mAms.mProcStateSeqCounter);
         assertEquals(1, uidRec.curProcStateSeq);
 
         // Explicitly setting the seq counter for more verification.
-        ams.mProcStateSeqCounter = 42;
+        mAms.mProcStateSeqCounter = 42;
 
         // Uid state is not moving from background to foreground or vice versa.
         uidRec.setProcState = PROCESS_STATE_IMPORTANT_BACKGROUND;
         uidRec.curProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(42, ams.mProcStateSeqCounter);
+        mAms.incrementProcStateSeqIfNeeded(uidRec);
+        assertEquals(42, mAms.mProcStateSeqCounter);
         assertEquals(1, uidRec.curProcStateSeq);
 
         // Uid state is moving from background to foreground.
         uidRec.setProcState = PROCESS_STATE_LAST_ACTIVITY;
         uidRec.curProcState = PROCESS_STATE_TOP;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(43, ams.mProcStateSeqCounter);
+        mAms.incrementProcStateSeqIfNeeded(uidRec);
+        assertEquals(43, mAms.mProcStateSeqCounter);
         assertEquals(43, uidRec.curProcStateSeq);
     }
 
     @Test
     public void testShouldIncrementProcStateSeq() {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
         final UidRecord uidRec = new UidRecord(TEST_UID);
 
         final String error1 = "Seq should be incremented: prevState: %s, curState: %s";
@@ -145,32 +182,32 @@
         // No change in uid state
         uidRec.setProcState = PROCESS_STATE_RECEIVER;
         uidRec.curProcState = PROCESS_STATE_RECEIVER;
-        assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
 
         // Foreground to foreground
         uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
         uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-        assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
 
         // Background to background
         uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
         uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY;
-        assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
 
         // Background to background
         uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
         uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY;
-        assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
 
         // Background to foreground
         uidRec.setProcState = PROCESS_STATE_SERVICE;
         uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
-        assertTrue(errorMsg.apply(error1), ams.shouldIncrementProcStateSeq(uidRec));
+        assertTrue(errorMsg.apply(error1), mAms.shouldIncrementProcStateSeq(uidRec));
 
         // Foreground to background
         uidRec.setProcState = PROCESS_STATE_TOP;
         uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY;
-        assertTrue(errorMsg.apply(error1), ams.shouldIncrementProcStateSeq(uidRec));
+        assertTrue(errorMsg.apply(error1), mAms.shouldIncrementProcStateSeq(uidRec));
     }
 
     /**
@@ -179,7 +216,6 @@
      */
     @Test
     public void testDispatchUids_dispatchNeededChanges() throws RemoteException {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
         when(mAppOpsService.checkOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
 
@@ -195,7 +231,7 @@
         for (int i = 0; i < observers.length; ++i) {
             observers[i] = Mockito.mock(IUidObserver.Stub.class);
             when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
-            ams.registerUidObserver(observers[i], changesToObserve[i] /* which */,
+            mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
                     ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
 
             // When we invoke AMS.registerUidObserver, there are some interactions with observers[i]
@@ -206,13 +242,8 @@
         }
 
         // Add pending uid records each corresponding to a different change type UidRecord.CHANGE_*
-        final int[] changesForPendingUidRecords = {
-            UidRecord.CHANGE_PROCSTATE,
-            UidRecord.CHANGE_GONE,
-            UidRecord.CHANGE_GONE_IDLE,
-            UidRecord.CHANGE_IDLE,
-            UidRecord.CHANGE_ACTIVE
-        };
+        final int[] changesForPendingUidRecords = UID_RECORD_CHANGES;
+
         final int[] procStatesForPendingUidRecords = {
             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
             ActivityManager.PROCESS_STATE_NONEXISTENT,
@@ -228,10 +259,10 @@
             pendingChange.processState = procStatesForPendingUidRecords[i];
             pendingChange.procStateSeq = i;
             changeItems.put(changesForPendingUidRecords[i], pendingChange);
-            ams.mPendingUidChanges.add(pendingChange);
+            mAms.mPendingUidChanges.add(pendingChange);
         }
 
-        ams.dispatchUidsChanged();
+        mAms.dispatchUidsChanged();
         // Verify the required changes have been dispatched to observers.
         for (int i = 0; i < observers.length; ++i) {
             final int changeToObserve = changesToObserve[i];
@@ -310,11 +341,10 @@
      */
     @Test
     public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
         final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
 
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        ams.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
+        mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
                 ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
         // When we invoke AMS.registerUidObserver, there are some interactions with observer
         // mock in RemoteCallbackList class. We don't want to test those interactions and
@@ -327,8 +357,8 @@
         changeItem.change = UidRecord.CHANGE_PROCSTATE;
         changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
         changeItem.procStateSeq = 111;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // First process state message is always delivered regardless of whether the process state
         // change is above or below the cutpoint (PROCESS_STATE_SERVICE).
         verify(observer).onUidStateChanged(TEST_UID,
@@ -336,15 +366,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also below cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is above cutpoint, so callback will be invoked with the
         // current process state change.
@@ -353,15 +383,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also above cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is below cutpoint, so callback will be invoked with the
         // current process state change.
@@ -376,15 +406,8 @@
      */
     @Test
     public void testDispatchUidChanges_validateUidsUpdated() {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+        final int[] changesForPendingItems = UID_RECORD_CHANGES;
 
-        final int[] changesForPendingItems = {
-            UidRecord.CHANGE_PROCSTATE,
-            UidRecord.CHANGE_GONE,
-            UidRecord.CHANGE_GONE_IDLE,
-            UidRecord.CHANGE_IDLE,
-            UidRecord.CHANGE_ACTIVE
-        };
         final int[] procStatesForPendingItems = {
             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
             ActivityManager.PROCESS_STATE_CACHED_EMPTY,
@@ -404,20 +427,20 @@
 
         // Verify that when there no observers listening to uid state changes, then there will
         // be no changes to validateUids.
-        ams.mPendingUidChanges.addAll(pendingItemsForUids);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
         assertEquals("No observers registered, so validateUids should be empty",
-                0, ams.mValidateUids.size());
+                0, mAms.mValidateUids.size());
 
         final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        ams.registerUidObserver(observer, 0, 0, null);
+        mAms.registerUidObserver(observer, 0, 0, null);
         // Verify that when observers are registered, then validateUids is correctly updated.
-        ams.mPendingUidChanges.addAll(pendingItemsForUids);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
-            final UidRecord validateUidRecord = ams.mValidateUids.get(item.uid);
+            final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
             if (item.change == UidRecord.CHANGE_GONE || item.change == UidRecord.CHANGE_GONE_IDLE) {
                 assertNull("validateUidRecord should be null since the change is either "
                         + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -442,16 +465,132 @@
 
         // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
         // will be removed from validateUids.
-        assertNotEquals("validateUids should not be empty", 0, ams.mValidateUids.size());
+        assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             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;
         }
-        ams.mPendingUidChanges.addAll(pendingItemsForUids);
-        ams.dispatchUidsChanged();
-        assertEquals("validateUids should be empty, validateUids: " + ams.mValidateUids,
-                0, ams.mValidateUids.size());
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
+        assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids,
+                0, mAms.mValidateUids.size());
+    }
+
+    @Test
+    public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
+        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+        // Verify with no pending changes for TEST_UID.
+        verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1);
+
+        // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
+        // expected.
+        final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
+        uidRecord.pendingChange = changeItem;
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
+        verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+
+        // Use "null" uidRecord to make sure there is no crash.
+        // TODO: currently it crashes, uncomment after fixing it.
+        // mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+    }
+
+    private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
+        // Test enqueueUidChangeLocked with every UidRecord.CHANGE_*
+        for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+            final int changeToDispatch = UID_RECORD_CHANGES[i];
+            // Reset lastProcStateSeqDispatchToObservers after every test.
+            uidRecord.lastDispatchedProcStateSeq = 0;
+            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) {
+                // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
+                // lastProcStateSeqDispatchedToObservers is not updated.
+                assertNotEquals(uidRecord.curProcStateSeq,
+                        uidRecord.lastDispatchedProcStateSeq);
+            } else {
+                // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that
+                // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq.
+                assertEquals(uidRecord.curProcStateSeq,
+                        uidRecord.lastDispatchedProcStateSeq);
+            }
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
+        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        final int expectedProcState = PROCESS_STATE_SERVICE;
+        uidRecord.setProcState = expectedProcState;
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+        // Test with no pending uid records.
+        for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+            final int changeToDispatch = UID_RECORD_CHANGES[i];
+
+            // Reset the current state
+            mHandler.reset();
+            uidRecord.pendingChange = null;
+            mAms.mPendingUidChanges.clear();
+
+            mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+
+            // Verify that UidRecord.pendingChange is updated correctly.
+            assertNotNull(uidRecord.pendingChange);
+            assertEquals(TEST_UID, uidRecord.pendingChange.uid);
+            assertEquals(expectedProcState, uidRecord.pendingChange.processState);
+            assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
+
+            // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
+            mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+        }
+    }
+
+    private class TestHandler extends Handler {
+        private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
+        private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
+
+        private Set<Integer> mMsgsHandled = new HashSet<>();
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mMsgsHandled.add(msg.what);
+        }
+
+        public void waitForMessage(int msg) {
+            final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS;
+            while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) {
+                SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS);
+            }
+            if (!mMsgsHandled.contains(msg)) {
+                fail("Timed out waiting for the message to be handled, msg: " + msg);
+            }
+        }
+
+        public void reset() {
+            mMsgsHandled.clear();
+        }
+    }
+
+    private class TestInjector implements Injector {
+        @Override
+        public AppOpsService getAppOpsService() {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getHandler() {
+            return mHandler;
+        }
     }
 }
\ No newline at end of file