| /* |
| * 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).stopAppForUser(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); |
| } |
| } |
| } |