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

Commit 1af8333a authored by Rhed Jao's avatar Rhed Jao Committed by Android (Google) Code Review
Browse files

Merge changes from topic "br_suspended_pkg" into sc-dev

* changes:
  Adds multiple target packages support for `pm suspend`
  Applies package visibility for the broadcast of suspended packages
parents 4c224aa3 d6f58154
Loading
Loading
Loading
Loading
+48 −9
Original line number Diff line number Diff line
@@ -15442,16 +15442,55 @@ public class PackageManagerService extends IPackageManager.Stub
                null);
    }
    private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
            boolean suspended) {
        final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
        final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
        final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
        final int[] userIds = new int[] {userId};
        // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
        // allow lists are the same.
        synchronized (mLock) {
            for (int i = 0; i < pkgList.length; i++) {
                final String pkgName = pkgList[i];
                final int uid = uidList[i];
                SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList(
                        getPackageSettingInternal(pkgName, Process.SYSTEM_UID),
                        userIds, mSettings.getPackagesLocked());
                if (allowList == null) {
                    allowList = new SparseArray<>(0);
                }
                boolean merged = false;
                for (int j = 0; j < allowListsToSend.size(); j++) {
                    if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
                        pkgsToSend.get(j).add(pkgName);
                        uidsToSend.get(j).add(uid);
                        merged = true;
                        break;
                    }
                }
                if (!merged) {
                    pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
                    uidsToSend.add(IntArray.wrap(new int[] {uid}));
                    allowListsToSend.add(allowList);
                }
            }
        }
        for (int i = 0; i < pkgsToSend.size(); i++) {
            final Bundle extras = new Bundle(3);
        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
                    pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
            final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
                    ? null : allowListsToSend.get(i);
            sendPackageBroadcast(
                    suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                            : Intent.ACTION_PACKAGES_UNSUSPENDED,
                    null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
                new int[] {userId}, null, null, null);
                    userIds, null, allowList, null);
        }
    }
    /**
+14 −11
Original line number Diff line number Diff line
@@ -104,8 +104,8 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
import com.android.server.pm.verify.domain.DomainVerificationShell;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationShell;

import dalvik.system.DexFile;

@@ -2251,8 +2251,8 @@ class PackageManagerShellCommand extends ShellCommand {
            }
        }

        final String packageName = getNextArg();
        if (packageName == null) {
        final List<String> packageNames = getRemainingArgs();
        if (packageNames.isEmpty()) {
            pw.println("Error: package name not specified");
            return 1;
        }
@@ -2270,12 +2270,15 @@ class PackageManagerShellCommand extends ShellCommand {
        try {
            final int translatedUserId =
                    translateUserId(userId, UserHandle.USER_NULL, "runSuspend");
            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
                    ((appExtras.size() > 0) ? appExtras : null),
            mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
                    suspendedState, ((appExtras.size() > 0) ? appExtras : null),
                    ((launcherExtras.size() > 0) ? launcherExtras : null),
                    info, callingPackage, translatedUserId);
            for (int i = 0; i < packageNames.size(); i++) {
                final String packageName = packageNames.get(i);
                pw.println("Package " + packageName + " new suspended state: "
                        + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
            }
            return 0;
        } catch (RemoteException | IllegalArgumentException e) {
            pw.println(e.toString());
@@ -3643,11 +3646,11 @@ class PackageManagerShellCommand extends ShellCommand {
        pw.println("  hide [--user USER_ID] PACKAGE_OR_COMPONENT");
        pw.println("  unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
        pw.println("");
        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
        pw.println("    Suspends the specified package (as user).");
        pw.println("  suspend [--user USER_ID] PACKAGE [PACKAGE...]");
        pw.println("    Suspends the specified package(s) (as user).");
        pw.println("");
        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
        pw.println("    Unsuspends the specified package (as user).");
        pw.println("  unsuspend [--user USER_ID] PACKAGE [PACKAGE...]");
        pw.println("    Unsuspends the specified package(s) (as user).");
        pw.println("");
        pw.println("  grant [--user USER_ID] PACKAGE PERMISSION");
        pw.println("  revoke [--user USER_ID] PACKAGE PERMISSION");
+163 −0
Original line number 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.server.pm

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.SparseArray
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.android.server.utils.WatchedArrayMap
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@RunWith(JUnit4::class)
class SuspendPackagesBroadcastTest {

    companion object {
        const val TEST_PACKAGE_1 = "com.android.test.package1"
        const val TEST_PACKAGE_2 = "com.android.test.package2"
        const val TEST_USER_ID = 0
    }

    lateinit var pms: PackageManagerService
    lateinit var packageSetting1: PackageSetting
    lateinit var packageSetting2: PackageSetting
    lateinit var packagesToSuspend: Array<String>
    lateinit var uidsToSuspend: IntArray

    @Captor
    lateinit var bundleCaptor: ArgumentCaptor<Bundle>

    @Rule
    @JvmField
    val rule = MockSystemRule()

    @Before
    @Throws(Exception::class)
    fun setup() {
        MockitoAnnotations.initMocks(this)
        rule.system().stageNominalSystemState()
        pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2))
        packageSetting1 = pms.getPackageSetting(TEST_PACKAGE_1)!!
        packageSetting2 = pms.getPackageSetting(TEST_PACKAGE_2)!!
        packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
        uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
    }

    @Test
    @Throws(Exception::class)
    fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
        mockAllowList(packageSetting2, allowList(10001, 10002, 10003))

        pms.sendPackagesSuspendedForUser(
                packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
        verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())

        var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
        var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
        assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
        assertThat(changedUids).asList().containsExactly(
                packageSetting1.appId, packageSetting2.appId)
    }

    @Test
    @Throws(Exception::class)
    fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
        mockAllowList(packageSetting2, allowList(10001, 10002, 10007))

        pms.sendPackagesSuspendedForUser(
                packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
        verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())

        bundleCaptor.allValues.forEach {
            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
            assertThat(changedPackages?.size).isEqualTo(1)
            assertThat(changedUids?.size).isEqualTo(1)
            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
        }
    }

    @Test
    @Throws(Exception::class)
    fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
        mockAllowList(packageSetting2, null)

        pms.sendPackagesSuspendedForUser(
                packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
        verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())

        bundleCaptor.allValues.forEach {
            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
            assertThat(changedPackages?.size).isEqualTo(1)
            assertThat(changedUids?.size).isEqualTo(1)
            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
        }
    }

    private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
        this.put(TEST_USER_ID, uids)
    }

    private fun mockAllowList(pkgSetting: PackageSetting, list: SparseArray<IntArray>?) {
        whenever(rule.mocks().injector.appsFilter.getVisibilityAllowList(eq(pkgSetting),
                any(IntArray::class.java), any() as WatchedArrayMap<String, PackageSetting>))
                .thenReturn(list)
    }

    private fun createPackageManagerService(vararg stageExistingPackages: String):
            PackageManagerService {
        stageExistingPackages.forEach {
            rule.system().stageScanExistingPackage(it, 1L,
                    rule.system().dataAppDirectory)
        }
        var pms = PackageManagerService(rule.mocks().injector,
                false /*coreOnly*/,
                false /*factoryTest*/,
                MockSystem.DEFAULT_VERSION_INFO.fingerprint,
                false /*isEngBuild*/,
                false /*isUserDebugBuild*/,
                Build.VERSION_CODES.CUR_DEVELOPMENT,
                Build.VERSION.INCREMENTAL)
        rule.system().validateFinalState()
        return pms
    }
}
+21 −0
Original line number Diff line number Diff line
@@ -93,3 +93,24 @@ inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) =
        spyThrowOnUnmocked<T>(null, block)

inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)

/**
 * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
 * null is returned.
 *
 * Generic T is nullable because implicitly bounded by Any?.
 */
fun <T> any(type: Class<T>): T = Mockito.any<T>(type)

/**
 * Wrapper around [Mockito.any] for generic types.
 */
inline fun <reified T> any() = any(T::class.java)

/**
 * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
 * null is returned.
 *
 * Generic T is nullable because implicitly bounded by Any?.
 */
fun <T> eq(obj: T): T = Mockito.eq<T>(obj)