diff options
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java new file mode 100644 index 000000000000..c7c8d04b21bd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.app.IForegroundServiceObserver; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.util.Pair; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.test.filters.MediumTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.RunningFgsController; +import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime; +import com.android.systemui.statusbar.policy.RunningFgsControllerImpl; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Random; +import java.util.function.Consumer; + +@MediumTest +@RunWith(AndroidTestingRunner.class) +public class RunningFgsControllerTest extends SysuiTestCase { + + private RunningFgsController mController; + + private FakeSystemClock mSystemClock = new FakeSystemClock(); + private FakeExecutor mExecutor = new FakeExecutor(mSystemClock); + private TestCallback mCallback = new TestCallback(); + + @Mock + private IActivityManager mActivityManager; + @Mock + private Lifecycle mLifecycle; + @Mock + private LifecycleOwner mLifecycleOwner; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle); + mController = new RunningFgsControllerImpl(mExecutor, mSystemClock, mActivityManager); + } + + @Test + public void testInitRegistersListenerInImpl() throws RemoteException { + ((RunningFgsControllerImpl) mController).init(); + verify(mActivityManager, times(1)).registerForegroundServiceObserver(any()); + } + + @Test + public void testAddCallbackCallsInitInImpl() { + verifyInitIsCalled(controller -> controller.addCallback(mCallback)); + } + + @Test + public void testRemoveCallbackCallsInitInImpl() { + verifyInitIsCalled(controller -> controller.removeCallback(mCallback)); + } + + @Test + public void testObserve1CallsInitInImpl() { + verifyInitIsCalled(controller -> controller.observe(mLifecycle, mCallback)); + } + + @Test + public void testObserve2CallsInitInImpl() { + verifyInitIsCalled(controller -> controller.observe(mLifecycleOwner, mCallback)); + } + + @Test + public void testGetPackagesWithFgsCallsInitInImpl() { + verifyInitIsCalled(controller -> controller.getPackagesWithFgs()); + } + + @Test + public void testStopFgsCallsInitInImpl() { + verifyInitIsCalled(controller -> controller.stopFgs(0, "")); + } + + /** + * Tests that callbacks can be added + */ + @Test + public void testAddCallback() throws RemoteException { + String testPackageName = "testPackageName"; + int testUserId = 0; + + IForegroundServiceObserver observer = prepareObserver(); + mController.addCallback(mCallback); + + observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true); + + mExecutor.advanceClockToLast(); + mExecutor.runAllReady(); + + assertEquals("Callback should have been invoked exactly once.", + 1, mCallback.mInvocations.size()); + + List<UserPackageTime> userPackageTimes = mCallback.mInvocations.get(0); + assertEquals("There should have only been one package in callback. packages=" + + userPackageTimes, + 1, userPackageTimes.size()); + + UserPackageTime upt = userPackageTimes.get(0); + assertEquals(testPackageName, upt.getPackageName()); + assertEquals(testUserId, upt.getUserId()); + } + + /** + * Tests that callbacks can be removed. This test is only meaningful if + * {@link #testAddCallback()} can pass. + */ + @Test + public void testRemoveCallback() throws RemoteException { + String testPackageName = "testPackageName"; + int testUserId = 0; + + IForegroundServiceObserver observer = prepareObserver(); + mController.addCallback(mCallback); + mController.removeCallback(mCallback); + + observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true); + + mExecutor.advanceClockToLast(); + mExecutor.runAllReady(); + + assertEquals("Callback should not have been invoked.", + 0, mCallback.mInvocations.size()); + } + + /** + * Tests packages are added when the controller receives a callback from activity manager for + * a foreground service start. + */ + @Test + public void testGetPackagesWithFgsAddingPackages() throws RemoteException { + int numPackages = 20; + int numUsers = 3; + + IForegroundServiceObserver observer = prepareObserver(); + + assertEquals("List should be empty", 0, mController.getPackagesWithFgs().size()); + + List<Pair<Integer, String>> addedPackages = new ArrayList<>(); + for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) { + for (int userId = 0; userId < numUsers; userId++) { + String packageName = "package.name." + pkgNumber; + addedPackages.add(new Pair(userId, packageName)); + + observer.onForegroundStateChanged(new Binder(), packageName, userId, true); + + containsAllAddedPackages(addedPackages, mController.getPackagesWithFgs()); + } + } + } + + /** + * Tests packages are removed when the controller receives a callback from activity manager for + * a foreground service ending. + */ + @Test + public void testGetPackagesWithFgsRemovingPackages() throws RemoteException { + int numPackages = 20; + int numUsers = 3; + int arrayLength = numPackages * numUsers; + + String[] packages = new String[arrayLength]; + int[] users = new int[arrayLength]; + IBinder[] tokens = new IBinder[arrayLength]; + for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) { + for (int userId = 0; userId < numUsers; userId++) { + int i = pkgNumber * numUsers + userId; + packages[i] = "package.name." + pkgNumber; + users[i] = userId; + tokens[i] = new Binder(); + } + } + + IForegroundServiceObserver observer = prepareObserver(); + + for (int i = 0; i < packages.length; i++) { + observer.onForegroundStateChanged(tokens[i], packages[i], users[i], true); + } + + assertEquals(packages.length, mController.getPackagesWithFgs().size()); + + List<Integer> removeOrder = new ArrayList<>(); + for (int i = 0; i < packages.length; i++) { + removeOrder.add(i); + } + Collections.shuffle(removeOrder, new Random(12345)); + + for (int idx : removeOrder) { + removePackageAndAssertRemovedFromList(observer, tokens[idx], packages[idx], users[idx]); + } + + assertEquals(0, mController.getPackagesWithFgs().size()); + } + + /** + * Tests a call on stopFgs forwards to activity manager. + */ + @Test + public void testStopFgs() throws RemoteException { + String pkgName = "package.name"; + mController.stopFgs(0, pkgName); + verify(mActivityManager).makeServicesNonForeground(pkgName, 0); + } + + /** + * Tests a package which starts multiple services is only listed once and is only removed once + * all services are stopped. + */ + @Test + public void testSinglePackageWithMultipleServices() throws RemoteException { + String packageName = "package.name"; + int userId = 0; + IBinder serviceToken1 = new Binder(); + IBinder serviceToken2 = new Binder(); + + IForegroundServiceObserver observer = prepareObserver(); + + assertEquals(0, mController.getPackagesWithFgs().size()); + + observer.onForegroundStateChanged(serviceToken1, packageName, userId, true); + assertSinglePackage(packageName, userId); + + observer.onForegroundStateChanged(serviceToken2, packageName, userId, true); + assertSinglePackage(packageName, userId); + + observer.onForegroundStateChanged(serviceToken2, packageName, userId, false); + assertSinglePackage(packageName, userId); + + observer.onForegroundStateChanged(serviceToken1, packageName, userId, false); + assertEquals(0, mController.getPackagesWithFgs().size()); + } + + private IForegroundServiceObserver prepareObserver() + throws RemoteException { + mController.getPackagesWithFgs(); + + ArgumentCaptor<IForegroundServiceObserver> argumentCaptor = + ArgumentCaptor.forClass(IForegroundServiceObserver.class); + verify(mActivityManager).registerForegroundServiceObserver(argumentCaptor.capture()); + + return argumentCaptor.getValue(); + } + + private void verifyInitIsCalled(Consumer<RunningFgsControllerImpl> c) { + RunningFgsControllerImpl spiedController = Mockito.spy( + ((RunningFgsControllerImpl) mController)); + c.accept(spiedController); + verify(spiedController, atLeastOnce()).init(); + } + + private void containsAllAddedPackages(List<Pair<Integer, String>> addedPackages, + List<UserPackageTime> runningFgsPackages) { + for (Pair<Integer, String> userPkg : addedPackages) { + assertTrue(userPkg + " was not found in returned list", + runningFgsPackages.stream().anyMatch( + upt -> userPkg.first == upt.getUserId() + && Objects.equals(upt.getPackageName(), userPkg.second))); + } + for (UserPackageTime upt : runningFgsPackages) { + int userId = upt.getUserId(); + String packageName = upt.getPackageName(); + assertTrue("Unknown <user=" + userId + ", package=" + packageName + ">" + + " in returned list", + addedPackages.stream().anyMatch(userPkg -> userPkg.first == userId + && Objects.equals(packageName, userPkg.second))); + } + } + + private void removePackageAndAssertRemovedFromList(IForegroundServiceObserver observer, + IBinder token, String pkg, int userId) throws RemoteException { + observer.onForegroundStateChanged(token, pkg, userId, false); + List<UserPackageTime> packagesWithFgs = mController.getPackagesWithFgs(); + assertFalse("Package \"" + pkg + "\" was not removed", + packagesWithFgs.stream().anyMatch(upt -> + Objects.equals(upt.getPackageName(), pkg) && upt.getUserId() == userId)); + } + + private void assertSinglePackage(String packageName, int userId) { + assertEquals(1, mController.getPackagesWithFgs().size()); + assertEquals(packageName, mController.getPackagesWithFgs().get(0).getPackageName()); + assertEquals(userId, mController.getPackagesWithFgs().get(0).getUserId()); + } + + private static class TestCallback implements RunningFgsController.Callback { + + private List<List<UserPackageTime>> mInvocations = new ArrayList<>(); + + @Override + public void onFgsPackagesChanged(List<UserPackageTime> packages) { + mInvocations.add(packages); + } + } +} |