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

Commit 1a53c56e authored by Evan Severson's avatar Evan Severson
Browse files

Only show packages for the current user in FgsManager

Also fix an issue where the package icon wasn't badged correctly for the
profile user.

Test: Run Fgs in user, work profile, secondary user
      SystemUITests
Fixes: 224666674
Change-Id: I0a45d6019118bae12e42edefa42bc3e5d022a2bb
parent 866cd169
Loading
Loading
Loading
Loading
+64 −24
Original line number Diff line number Diff line
@@ -23,10 +23,12 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.graphics.drawable.Drawable
import android.os.IBinder
import android.os.PowerExemptionManager
import android.os.RemoteException
import android.os.UserHandle
import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI
import android.text.format.DateUtils
import android.util.ArrayMap
@@ -51,6 +53,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.indentIfPossible
@@ -69,6 +72,7 @@ class FgsManagerController @Inject constructor(
    private val systemClock: SystemClock,
    private val activityManager: IActivityManager,
    private val packageManager: PackageManager,
    private val userTracker: UserTracker,
    private val deviceConfigProxy: DeviceConfigProxy,
    private val dialogLaunchAnimator: DialogLaunchAnimator,
    private val broadcastDispatcher: BroadcastDispatcher,
@@ -82,13 +86,20 @@ class FgsManagerController @Inject constructor(
    var changesSinceDialog = false
        private set

    private var isAvailable = false
    var isAvailable = false
        private set

    private val lock = Any()

    @GuardedBy("lock")
    var initialized = false

    @GuardedBy("lock")
    private var lastNumberOfVisiblePackages = 0

    @GuardedBy("lock")
    private var currentProfileIds = mutableSetOf<Int>()

    @GuardedBy("lock")
    private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()

@@ -101,6 +112,19 @@ class FgsManagerController @Inject constructor(
    @GuardedBy("lock")
    private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap()

    private val userTrackerCallback = object : UserTracker.Callback {
        override fun onUserChanged(newUser: Int, userContext: Context) {}

        override fun onProfilesChanged(profiles: List<UserInfo>) {
            synchronized(lock) {
                currentProfileIds.clear()
                currentProfileIds.addAll(profiles.map { it.id })
                lastNumberOfVisiblePackages = 0
                updateNumberOfVisibleRunningPackagesLocked()
            }
        }
    }

    interface OnNumberOfPackagesChangedListener {
        fun onNumberOfPackagesChanged(numPackages: Int)
    }
@@ -120,6 +144,10 @@ class FgsManagerController @Inject constructor(
                e.rethrowFromSystemServer()
            }

            userTracker.addCallback(userTrackerCallback, backgroundExecutor)

            currentProfileIds.addAll(userTracker.userProfiles.map { it.id })

            deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
                    backgroundExecutor) {
                isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
@@ -153,10 +181,9 @@ class FgsManagerController @Inject constructor(
        isForeground: Boolean
    ) {
        synchronized(lock) {
            val numPackagesBefore = getNumRunningPackagesLocked()
            val userPackageKey = UserPackage(userId, packageName)
            if (isForeground) {
                runningServiceTokens.getOrPut(userPackageKey, { StartTimeAndTokens(systemClock) })
                runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) }
                        .addToken(token)
            } else {
                if (runningServiceTokens[userPackageKey]?.also {
@@ -165,14 +192,7 @@ class FgsManagerController @Inject constructor(
                }
            }

            val numPackagesAfter = getNumRunningPackagesLocked()

            if (numPackagesAfter != numPackagesBefore) {
                changesSinceDialog = true
                onNumberOfPackagesChangedListeners.forEach {
                    backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) }
                }
            }
            updateNumberOfVisibleRunningPackagesLocked()

            updateAppItemsLocked()
        }
@@ -209,18 +229,30 @@ class FgsManagerController @Inject constructor(
        }
    }

    fun isAvailable(): Boolean {
        return isAvailable
    }

    fun getNumRunningPackages(): Int {
        synchronized(lock) {
            return getNumRunningPackagesLocked()
            return getNumVisiblePackagesLocked()
        }
    }

    private fun getNumVisiblePackagesLocked(): Int {
        return runningServiceTokens.keys.count {
            it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
        }
    }

    private fun getNumRunningPackagesLocked() =
            runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY }
    private fun updateNumberOfVisibleRunningPackagesLocked() {
        val num = getNumVisiblePackagesLocked()
        if (num != lastNumberOfVisiblePackages) {
            lastNumberOfVisiblePackages = num
            changesSinceDialog = true
            onNumberOfPackagesChangedListeners.forEach {
                backgroundExecutor.execute {
                    it.onNumberOfPackagesChanged(num)
                }
            }
        }
    }

    fun shouldUpdateFooterVisibility() = dialog == null

@@ -289,7 +321,9 @@ class FgsManagerController @Inject constructor(
            val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
            runningApps[it] = RunningApp(it.userId, it.packageName,
                    runningServiceTokens[it]!!.startTime, it.uiControl,
                    ai.loadLabel(packageManager), ai.loadIcon(packageManager))
                    packageManager.getApplicationLabel(ai),
                    packageManager.getUserBadgedIcon(
                            packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)))
            logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted)
        }

@@ -404,6 +438,7 @@ class FgsManagerController @Inject constructor(
        val packageName: String
    ) {
        val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) }
        var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED

        private var uiControlInitialized = false
        var uiControl: UIControl = UIControl.NORMAL
@@ -416,7 +451,9 @@ class FgsManagerController @Inject constructor(
            private set

        fun updateUiControl() {
            uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) {
            backgroundRestrictionExemptionReason =
                    activityManager.getBackgroundRestrictionExemptionReason(uid)
            uiControl = when (backgroundRestrictionExemptionReason) {
                PowerExemptionManager.REASON_SYSTEM_UID,
                PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY

@@ -448,7 +485,7 @@ class FgsManagerController @Inject constructor(
            pw.indentIfPossible {
                pw.println("userId=$userId")
                pw.println("packageName=$packageName")
                pw.println("uiControl=$uiControl")
                pw.println("uiControl=$uiControl (reason=$backgroundRestrictionExemptionReason)")
            }
            pw.println("]")
        }
@@ -525,7 +562,7 @@ class FgsManagerController @Inject constructor(
                pw.println("userId=$userId")
                pw.println("packageName=$packageName")
                pw.println("timeStarted=$timeStarted (time since start =" +
                        " ${systemClock.elapsedRealtime() - timeStarted}ms)\"")
                        " ${systemClock.elapsedRealtime() - timeStarted}ms)")
                pw.println("uiControl=$uiControl")
                pw.println("appLabel=$appLabel")
                pw.println("icon=$icon")
@@ -542,6 +579,7 @@ class FgsManagerController @Inject constructor(
    override fun dump(printwriter: PrintWriter, args: Array<out String>) {
        val pw = IndentingPrintWriter(printwriter)
        synchronized(lock) {
            pw.println("current user profiles = $currentProfileIds")
            pw.println("changesSinceDialog=$changesSinceDialog")
            pw.println("Running service tokens: [")
            pw.indentIfPossible {
@@ -560,8 +598,10 @@ class FgsManagerController @Inject constructor(
            pw.indentIfPossible {
                runningApps.forEach { (userPackage, runningApp) ->
                    pw.println("{")
                    pw.indentIfPossible {
                        userPackage.dump(pw)
                        runningApp.dump(pw, systemClock)
                    }
                    pw.println("}")
                }
            }
+285 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.qs;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.app.IActivityManager;
import android.app.IForegroundServiceObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import androidx.test.filters.SmallTest;

import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;

@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class FgsManagerControllerTest extends SysuiTestCase {

    FakeSystemClock mSystemClock;
    FakeExecutor mMainExecutor;
    FakeExecutor mBackgroundExecutor;
    DeviceConfigProxyFake mDeviceConfigProxyFake;

    @Mock
    IActivityManager mIActivityManager;
    @Mock
    PackageManager mPackageManager;
    @Mock
    UserTracker mUserTracker;
    @Mock
    DialogLaunchAnimator mDialogLaunchAnimator;
    @Mock
    BroadcastDispatcher mBroadcastDispatcher;
    @Mock
    DumpManager mDumpManager;

    private FgsManagerController mFmc;

    private IForegroundServiceObserver mIForegroundServiceObserver;
    private UserTracker.Callback mUserTrackerCallback;
    private BroadcastReceiver mShowFgsManagerReceiver;

    private List<UserInfo> mUserProfiles;

    @Before
    public void setUp() throws RemoteException {
        MockitoAnnotations.initMocks(this);

        mDeviceConfigProxyFake = new DeviceConfigProxyFake();
        mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, "true", false);
        mSystemClock = new FakeSystemClock();
        mMainExecutor = new FakeExecutor(mSystemClock);
        mBackgroundExecutor = new FakeExecutor(mSystemClock);

        mUserProfiles = new ArrayList<>();
        Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles();

        mFmc = createFgsManagerController();
    }

    @Test
    public void testNumPackages() throws RemoteException {
        setUserProfiles(0);

        Binder b1 = new Binder();
        Binder b2 = new Binder();
        Assert.assertEquals(0, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
        Assert.assertEquals(1, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
        Assert.assertEquals(2, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false);
        Assert.assertEquals(1, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false);
        Assert.assertEquals(0, mFmc.getNumRunningPackages());
    }

    @Test
    public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException {
        setUserProfiles(0);

        // Different tokens == different services
        Binder b1 = new Binder();
        Binder b2 = new Binder();
        Assert.assertEquals(0, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
        Assert.assertEquals(1, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg1", 0, true);
        Assert.assertEquals(1, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false);
        Assert.assertEquals(1, mFmc.getNumRunningPackages());
        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg1", 0, false);
        Assert.assertEquals(0, mFmc.getNumRunningPackages());
    }

    @Test
    public void testNumPackagesListener() throws RemoteException {
        setUserProfiles(0);

        FgsManagerController.OnNumberOfPackagesChangedListener onNumberOfPackagesChangedListener =
                Mockito.mock(FgsManagerController.OnNumberOfPackagesChangedListener.class);

        mFmc.addOnNumberOfPackagesChangedListener(onNumberOfPackagesChangedListener);

        Binder b1 = new Binder();
        Binder b2 = new Binder();

        verify(onNumberOfPackagesChangedListener, never()).onNumberOfPackagesChanged(anyInt());

        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
        mBackgroundExecutor.advanceClockToLast();
        mBackgroundExecutor.runAllReady();
        verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(1);

        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
        mBackgroundExecutor.advanceClockToLast();
        mBackgroundExecutor.runAllReady();
        verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(2);

        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false);
        mBackgroundExecutor.advanceClockToLast();
        mBackgroundExecutor.runAllReady();
        verify(onNumberOfPackagesChangedListener, times(2)).onNumberOfPackagesChanged(1);

        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false);
        mBackgroundExecutor.advanceClockToLast();
        mBackgroundExecutor.runAllReady();
        verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(0);
    }

    @Test
    public void testChangesSinceLastDialog() throws RemoteException {
        setUserProfiles(0);

        Assert.assertFalse(mFmc.getChangesSinceDialog());
        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true);
        Assert.assertTrue(mFmc.getChangesSinceDialog());
    }

    @Test
    public void testProfilePackagesCounted() throws RemoteException {
        setUserProfiles(0, 10);

        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true);
        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true);
        Assert.assertEquals(2, mFmc.getNumRunningPackages());
    }

    @Test
    public void testSecondaryUserPackagesAreNotCounted() throws RemoteException {
        setUserProfiles(0);

        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true);
        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true);
        Assert.assertEquals(1, mFmc.getNumRunningPackages());
    }

    @Test
    public void testSecondaryUserPackagesAreCountedWhenUserSwitch() throws RemoteException {
        setUserProfiles(0);

        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true);
        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true);
        mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg3", 10, true);

        Assert.assertEquals(1, mFmc.getNumRunningPackages());

        setUserProfiles(10);
        Assert.assertEquals(2, mFmc.getNumRunningPackages());
    }



    FgsManagerController createFgsManagerController() throws RemoteException {
        ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
                ArgumentCaptor.forClass(IForegroundServiceObserver.class);
        ArgumentCaptor<UserTracker.Callback> userTrackerCallbackArgumentCaptor =
                ArgumentCaptor.forClass(UserTracker.Callback.class);
        ArgumentCaptor<BroadcastReceiver> showFgsManagerReceiverArgumentCaptor =
                ArgumentCaptor.forClass(BroadcastReceiver.class);

        FgsManagerController result = new FgsManagerController(
                mContext,
                mMainExecutor,
                mBackgroundExecutor,
                mSystemClock,
                mIActivityManager,
                mPackageManager,
                mUserTracker,
                mDeviceConfigProxyFake,
                mDialogLaunchAnimator,
                mBroadcastDispatcher,
                mDumpManager
        );
        result.init();

        verify(mIActivityManager).registerForegroundServiceObserver(
                iForegroundServiceObserverArgumentCaptor.capture()
        );
        verify(mUserTracker).addCallback(
                userTrackerCallbackArgumentCaptor.capture(),
                ArgumentMatchers.eq(mBackgroundExecutor)
        );
        verify(mBroadcastDispatcher).registerReceiver(
                showFgsManagerReceiverArgumentCaptor.capture(),
                argThat(fltr -> fltr.matchAction(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER)),
                eq(mMainExecutor),
                isNull(),
                eq(Context.RECEIVER_NOT_EXPORTED),
                isNull()
        );

        mIForegroundServiceObserver = iForegroundServiceObserverArgumentCaptor.getValue();
        mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue();
        mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.getValue();

        return result;
    }

    private void setUserProfiles(int current, int... profileUserIds) {
        mUserProfiles.clear();
        mUserProfiles.add(new UserInfo(current, "current:" + current, 0));
        for (int id : profileUserIds) {
            mUserProfiles.add(new UserInfo(id, "profile:" + id, 0));
        }

        if (mUserTrackerCallback != null) {
            mUserTrackerCallback.onUserChanged(current, mock(Context.class));
            mUserTrackerCallback.onProfilesChanged(mUserProfiles);
        }
    }
}