Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e57035b6 authored by Evan Severson's avatar Evan Severson Committed by Android (Google) Code Review
Browse files

Merge "Add tests for RunningFgsController"

parents 94ebf6a3 fe460dd6
Loading
Loading
Loading
Loading
+340 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
        }
    }
}