/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; 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.ActivityManagerInternalTest.CustomThread; import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG; import static com.android.server.am.ActivityManagerService.Injector; import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK; import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE; import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; 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.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.IUidObserver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; 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.internal.os.BatteryStatsImpl; import com.android.server.AppOpsService; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.File; 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; /** * Test class for {@link ActivityManagerService}. * * To run the tests, use * * runtest -c com.android.server.am.ActivityManagerServiceTest frameworks-services * * or the following steps: * * Build: m FrameworksServicesTests * Install: adb install -r \ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerServiceTest -w \ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner */ @SmallTest @RunWith(AndroidJUnit4.class) public class ActivityManagerServiceTest { private static final String TAG = ActivityManagerServiceTest.class.getSimpleName(); private static final int TEST_UID = 11111; 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 | UidRecord.CHANGE_IDLE, UidRecord.CHANGE_IDLE, UidRecord.CHANGE_ACTIVE }; @Mock private Context mContext; @Mock private AppOpsService mAppOpsService; @Mock private PackageManager mPackageManager; @Mock private BatteryStatsImpl mBatteryStatsImpl; private TestInjector mInjector; 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()); mInjector = new TestInjector(); mAms = new ActivityManagerService(mInjector); mAms.mWaitForNetworkTimeoutMs = 2000; when(mContext.getPackageManager()).thenReturn(mPackageManager); } @After public void tearDown() { mHandlerThread.quit(); } @MediumTest @Test public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception { final UidRecord uidRec = addUidRecord(TEST_UID); addUidRecord(TEST_UID + 1); // Uid state is not moving from background to foreground or vice versa. verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_TOP, // prevState PROCESS_STATE_TOP, // curState 0, // expectedGlobalCounter 0, // exptectedCurProcStateSeq NETWORK_STATE_NO_CHANGE, // expectedBlockState false); // expectNotify // Uid state is moving from foreground to background. verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_FOREGROUND_SERVICE, // prevState PROCESS_STATE_SERVICE, // curState 1, // expectedGlobalCounter 1, // exptectedCurProcStateSeq NETWORK_STATE_UNBLOCK, // expectedBlockState true); // expectNotify // Explicitly setting the seq counter for more verification. mAms.mProcStateSeqCounter = 42; // Uid state is not moving from background to foreground or vice versa. verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState PROCESS_STATE_IMPORTANT_FOREGROUND, // curState 42, // expectedGlobalCounter 1, // exptectedCurProcStateSeq NETWORK_STATE_NO_CHANGE, // expectedBlockState false); // expectNotify // Uid state is moving from background to foreground. verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_LAST_ACTIVITY, // prevState PROCESS_STATE_TOP, // curState 43, // expectedGlobalCounter 43, // exptectedCurProcStateSeq NETWORK_STATE_BLOCK, // expectedBlockState false); // expectNotify // verify waiting threads are not notified. uidRec.waitingForNetwork = false; // Uid state is moving from foreground to background. verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_FOREGROUND_SERVICE, // prevState PROCESS_STATE_SERVICE, // curState 44, // expectedGlobalCounter 44, // exptectedCurProcStateSeq NETWORK_STATE_UNBLOCK, // expectedBlockState false); // expectNotify // Verify when uid is not restricted, procStateSeq is not incremented. uidRec.waitingForNetwork = true; mInjector.setNetworkRestrictedForUid(false); verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState PROCESS_STATE_TOP, // curState 44, // expectedGlobalCounter 44, // exptectedCurProcStateSeq -1, // expectedBlockState, -1 to verify there are no interactions with main thread. false); // expectNotify // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented. mAms.mWaitForNetworkTimeoutMs = 0; mInjector.setNetworkRestrictedForUid(true); verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_TOP, // prevState PROCESS_STATE_IMPORTANT_BACKGROUND, // curState 44, // expectedGlobalCounter 44, // exptectedCurProcStateSeq -1, // expectedBlockState, -1 to verify there are no interactions with main thread. false); // expectNotify // Verify when the uid doesn't have internet permission, then procStateSeq is not // incremented. uidRec.hasInternetPermission = false; mAms.mWaitForNetworkTimeoutMs = 111; mInjector.setNetworkRestrictedForUid(true); verifySeqCounterAndInteractions(uidRec, PROCESS_STATE_CACHED_ACTIVITY, // prevState PROCESS_STATE_FOREGROUND_SERVICE, // curState 44, // expectedGlobalCounter 44, // exptectedCurProcStateSeq -1, // expectedBlockState, -1 to verify there are no interactions with main thread. false); // expectNotify // Verify procStateSeq is not incremented when the uid is not an application, regardless // of the process state. final int notAppUid = 111; final UidRecord uidRec2 = addUidRecord(notAppUid); verifySeqCounterAndInteractions(uidRec2, PROCESS_STATE_CACHED_EMPTY, // prevState PROCESS_STATE_TOP, // curState 44, // expectedGlobalCounter 0, // exptectedCurProcStateSeq -1, // expectedBlockState, -1 to verify there are no interactions with main thread. false); // expectNotify } private UidRecord addUidRecord(int uid) { final UidRecord uidRec = new UidRecord(uid); uidRec.waitingForNetwork = true; uidRec.hasInternetPermission = true; mAms.mActiveUids.put(uid, uidRec); final ProcessRecord appRec = new ProcessRecord(mBatteryStatsImpl, new ApplicationInfo(), TAG, uid); appRec.thread = Mockito.mock(IApplicationThread.class); mAms.mLruProcesses.add(appRec); return uidRec; } private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState, int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState, boolean expectNotify) throws Exception { CustomThread thread = new CustomThread(uidRec.networkStateLock); thread.startAndWait("Unexpected state for " + uidRec); uidRec.setProcState = prevState; uidRec.curProcState = curState; mAms.incrementProcStateSeqAndNotifyAppsLocked(); assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter); assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq); for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) { final ProcessRecord app = mAms.mLruProcesses.get(i); // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE. if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) { verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq); } else { verifyZeroInteractions(app.thread); } Mockito.reset(app.thread); } if (expectNotify) { thread.assertTerminated("Unexpected state for " + uidRec); } else { thread.assertWaiting("Unexpected state for " + uidRec); thread.interrupt(); } } @Test public void testBlockStateForUid() { final UidRecord uidRec = new UidRecord(TEST_UID); int expectedBlockState; final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s"; Function errorMsg = (blockState) -> { return String.format(errorTemplate, valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState), valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState), valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState)); }; // No change in uid state uidRec.setProcState = PROCESS_STATE_RECEIVER; uidRec.curProcState = PROCESS_STATE_RECEIVER; expectedBlockState = NETWORK_STATE_NO_CHANGE; assertEquals(errorMsg.apply(expectedBlockState), expectedBlockState, mAms.getBlockStateForUid(uidRec)); // Foreground to foreground uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE; uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; expectedBlockState = NETWORK_STATE_NO_CHANGE; assertEquals(errorMsg.apply(expectedBlockState), expectedBlockState, mAms.getBlockStateForUid(uidRec)); // Background to background uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY; uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY; expectedBlockState = NETWORK_STATE_NO_CHANGE; assertEquals(errorMsg.apply(expectedBlockState), expectedBlockState, mAms.getBlockStateForUid(uidRec)); // Background to background uidRec.setProcState = PROCESS_STATE_NONEXISTENT; uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY; expectedBlockState = NETWORK_STATE_NO_CHANGE; assertEquals(errorMsg.apply(expectedBlockState), expectedBlockState, mAms.getBlockStateForUid(uidRec)); // Background to foreground uidRec.setProcState = PROCESS_STATE_SERVICE; uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE; expectedBlockState = NETWORK_STATE_BLOCK; assertEquals(errorMsg.apply(expectedBlockState), expectedBlockState, mAms.getBlockStateForUid(uidRec)); // Foreground to background uidRec.setProcState = PROCESS_STATE_TOP; uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY; expectedBlockState = NETWORK_STATE_UNBLOCK; assertEquals(errorMsg.apply(expectedBlockState), expectedBlockState, mAms.getBlockStateForUid(uidRec)); } /** * This test verifies that process state changes are dispatched to observers based on the * changes they wanted to listen (this is specified when registering the observer). */ @Test public void testDispatchUids_dispatchNeededChanges() throws RemoteException { when(mAppOpsService.checkOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null)) .thenReturn(AppOpsManager.MODE_ALLOWED); final int[] changesToObserve = { ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.UID_OBSERVER_GONE, ActivityManager.UID_OBSERVER_IDLE, ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_ACTIVE | ActivityManager.UID_OBSERVER_IDLE }; final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length]; for (int i = 0; i < observers.length; ++i) { observers[i] = Mockito.mock(IUidObserver.Stub.class); when(observers[i].asBinder()).thenReturn((IBinder) observers[i]); 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] // mock in RemoteCallbackList class. We don't want to test those interactions and // at the same time, we don't want those to interfere with verifyNoMoreInteractions. // So, resetting the mock here. Mockito.reset(observers[i]); } // Add pending uid records each corresponding to a different change type UidRecord.CHANGE_* final int[] changesForPendingUidRecords = UID_RECORD_CHANGES; final int[] procStatesForPendingUidRecords = { ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, ActivityManager.PROCESS_STATE_NONEXISTENT, ActivityManager.PROCESS_STATE_CACHED_EMPTY, ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, ActivityManager.PROCESS_STATE_TOP }; final Map changeItems = new HashMap<>(); for (int i = 0; i < changesForPendingUidRecords.length; ++i) { final UidRecord.ChangeItem pendingChange = new UidRecord.ChangeItem(); pendingChange.change = changesForPendingUidRecords[i]; pendingChange.uid = i; pendingChange.processState = procStatesForPendingUidRecords[i]; pendingChange.procStateSeq = i; changeItems.put(changesForPendingUidRecords[i], pendingChange); mAms.mPendingUidChanges.add(pendingChange); } mAms.dispatchUidsChanged(); // Verify the required changes have been dispatched to observers. for (int i = 0; i < observers.length; ++i) { final int changeToObserve = changesToObserve[i]; final IUidObserver observerToTest = observers[i]; if ((changeToObserve & ActivityManager.UID_OBSERVER_IDLE) != 0) { // Observer listens to uid idle changes, so change items corresponding to // UidRecord.CHANGE_IDLE or UidRecord.CHANGE_IDLE_GONE needs to be // delivered to this observer. final int[] changesToVerify = { UidRecord.CHANGE_IDLE, UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE }; verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, (observer, changeItem) -> { verify(observer).onUidIdle(changeItem.uid, changeItem.ephemeral); }); } if ((changeToObserve & ActivityManager.UID_OBSERVER_ACTIVE) != 0) { // Observer listens to uid active changes, so change items corresponding to // UidRecord.CHANGE_ACTIVE needs to be delivered to this observer. final int[] changesToVerify = { UidRecord.CHANGE_ACTIVE }; verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, (observer, changeItem) -> { verify(observer).onUidActive(changeItem.uid); }); } if ((changeToObserve & ActivityManager.UID_OBSERVER_GONE) != 0) { // Observer listens to uid gone changes, so change items corresponding to // UidRecord.CHANGE_GONE or UidRecord.CHANGE_IDLE_GONE needs to be // delivered to this observer. final int[] changesToVerify = { UidRecord.CHANGE_GONE, UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE }; verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, (observer, changeItem) -> { verify(observer).onUidGone(changeItem.uid, changeItem.ephemeral); }); } if ((changeToObserve & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { // Observer listens to uid procState changes, so change items corresponding to // UidRecord.CHANGE_PROCSTATE or UidRecord.CHANGE_IDLE or UidRecord.CHANGE_ACTIVE // needs to be delivered to this observer. final int[] changesToVerify = { UidRecord.CHANGE_PROCSTATE, UidRecord.CHANGE_ACTIVE, UidRecord.CHANGE_IDLE }; verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, (observer, changeItem) -> { verify(observer).onUidStateChanged(changeItem.uid, changeItem.processState, changeItem.procStateSeq); }); } // Verify there are no other callbacks for this observer. verifyNoMoreInteractions(observerToTest); } } private interface ObserverChangesVerifier { void verify(IUidObserver observer, UidRecord.ChangeItem changeItem) throws RemoteException; } private void verifyObserverReceivedChanges(IUidObserver observer, int[] changesToVerify, Map changeItems, ObserverChangesVerifier verifier) throws RemoteException { for (int change : changesToVerify) { final UidRecord.ChangeItem changeItem = changeItems.get(change); verifier.verify(observer, changeItem); } } /** * This test verifies that process state changes are dispatched to observers only when they * change across the cutpoint (this is specified when registering the observer). */ @Test public void testDispatchUidChanges_procStateCutpoint() throws RemoteException { final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class); when(observer.asBinder()).thenReturn((IBinder) observer); 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 // at the same time, we don't want those to interfere with verifyNoMoreInteractions. // So, resetting the mock here. Mockito.reset(observer); final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem(); changeItem.uid = TEST_UID; changeItem.change = UidRecord.CHANGE_PROCSTATE; changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; changeItem.procStateSeq = 111; 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, changeItem.processState, changeItem.procStateSeq); verifyNoMoreInteractions(observer); changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER; 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; 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. verify(observer).onUidStateChanged(TEST_UID, changeItem.processState, changeItem.procStateSeq); verifyNoMoreInteractions(observer); changeItem.processState = ActivityManager.PROCESS_STATE_TOP; 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; 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. verify(observer).onUidStateChanged(TEST_UID, changeItem.processState, changeItem.procStateSeq); verifyNoMoreInteractions(observer); } /** * This test verifies that {@link ActivityManagerService#mValidateUids} which is a * part of dumpsys is correctly updated. */ @Test public void testDispatchUidChanges_validateUidsUpdated() { final int[] changesForPendingItems = UID_RECORD_CHANGES; final int[] procStatesForPendingItems = { ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, ActivityManager.PROCESS_STATE_CACHED_EMPTY, ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, ActivityManager.PROCESS_STATE_SERVICE, ActivityManager.PROCESS_STATE_RECEIVER }; final ArrayList pendingItemsForUids = new ArrayList<>(changesForPendingItems.length); for (int i = 0; i < changesForPendingItems.length; ++i) { final UidRecord.ChangeItem item = new UidRecord.ChangeItem(); item.uid = i; item.change = changesForPendingItems[i]; item.processState = procStatesForPendingItems[i]; pendingItemsForUids.add(i, item); } // Verify that when there no observers listening to uid state changes, then there will // be no changes to validateUids. mAms.mPendingUidChanges.addAll(pendingItemsForUids); mAms.dispatchUidsChanged(); assertEquals("No observers registered, so validateUids should be empty", 0, mAms.mValidateUids.size()); final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class); when(observer.asBinder()).thenReturn((IBinder) observer); mAms.registerUidObserver(observer, 0, 0, null); // Verify that when observers are registered, then validateUids is correctly updated. mAms.mPendingUidChanges.addAll(pendingItemsForUids); mAms.dispatchUidsChanged(); 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) != 0) { assertNull("validateUidRecord should be null since the change is either " + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord); } else { assertNotNull("validateUidRecord should not be null since the change is neither " + "CHANGE_GONE nor CHANGE_GONE_IDLE", validateUidRecord); assertEquals("processState: " + item.processState + " curProcState: " + validateUidRecord.curProcState + " should have been equal", item.processState, validateUidRecord.curProcState); assertEquals("processState: " + item.processState + " setProcState: " + validateUidRecord.curProcState + " should have been equal", item.processState, validateUidRecord.setProcState); if (item.change == UidRecord.CHANGE_IDLE) { assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE", validateUidRecord.idle); } else if (item.change == UidRecord.CHANGE_ACTIVE) { assertFalse("UidRecord.idle should be updated to false for CHANGE_ACTIVE", validateUidRecord.idle); } } } // 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, 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 | UidRecord.CHANGE_IDLE) : UidRecord.CHANGE_GONE; } 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); } @Test public void testEnqueueUidChangeLocked_nullUidRecord() { // Use "null" uidRecord to make sure there is no crash. 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) != 0) { // 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); } } @MediumTest @Test public void testWaitForNetworkStateUpdate() throws Exception { // Check there is no crash when there is no UidRecord for myUid mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1); // Verify there is no waiting when UidRecord.curProcStateSeq is greater than // the procStateSeq in the request to wait. verifyWaitingForNetworkStateUpdate( TEST_PROC_STATE_SEQ1, // curProcStateSeq TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait false); // expectWait // Verify there is no waiting when the procStateSeq in the request to wait is // not dispatched to NPMS. verifyWaitingForNetworkStateUpdate( TEST_PROC_STATE_SEQ1, // curProcStateSeq TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq TEST_PROC_STATE_SEQ1, // procStateSeqToWait false); // expectWait // Verify there is not waiting when the procStateSeq in the request already has // an updated network state. verifyWaitingForNetworkStateUpdate( TEST_PROC_STATE_SEQ1, // curProcStateSeq TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq TEST_PROC_STATE_SEQ1, // procStateSeqToWait false); // expectWait // Verify waiting for network works verifyWaitingForNetworkStateUpdate( TEST_PROC_STATE_SEQ1, // curProcStateSeq TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq TEST_PROC_STATE_SEQ1, // procStateSeqToWait true); // expectWait } private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq, long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq, final long procStateSeqToWait, boolean expectWait) throws Exception { final UidRecord record = new UidRecord(Process.myUid()); record.curProcStateSeq = curProcStateSeq; record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq; record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; mAms.mActiveUids.put(Process.myUid(), record); CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() { @Override public void run() { mAms.waitForNetworkStateUpdate(procStateSeqToWait); } }); final String errMsg = "Unexpected state for " + record; if (expectWait) { thread.startAndWait(errMsg, true); thread.assertTimedWaiting(errMsg); synchronized (record.networkStateLock) { record.networkStateLock.notifyAll(); } thread.assertTerminated(errMsg); assertTrue(thread.mNotified); assertFalse(record.waitingForNetwork); } else { thread.start(); thread.assertTerminated(errMsg); } mAms.mActiveUids.clear(); } 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 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 extends Injector { private boolean mRestricted = true; @Override public Context getContext() { return mContext; } @Override public AppOpsService getAppOpsService(File file, Handler handler) { return mAppOpsService; } @Override public Handler getUiHandler(ActivityManagerService service) { return mHandler; } @Override public boolean isNetworkRestrictedForUid(int uid) { return mRestricted; } public void setNetworkRestrictedForUid(boolean restricted) { mRestricted = restricted; } } }