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

Commit e83659c9 authored by George Chan's avatar George Chan
Browse files

Added new permission guard to BIC Service in preparation of adding SystemAPI.

Test: atest BackgroundInstallControlServiceHostTest BackgroundInstallControlServiceTest BinaryTransparencyHostTest
Bug: 296060433
API-Coverage-Bug: 319762342
Change-Id: I88c0d664ebe4b26e3f5bb131c9468c8876a286d4
parent b487e625
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
package: "android.app"

flag {
     namespace: "background_install_control"
     name: "bic_client"
     description: "System API for background install control."
     is_fixed_read_only: true
     bug: "287507984"
}
+107 −71
Original line number Diff line number Diff line
@@ -16,7 +16,12 @@

package com.android.server.pm;

import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.Flags;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
@@ -27,6 +32,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
@@ -69,26 +75,29 @@ public class BackgroundInstallControlService extends SystemService {
    private static final String DISK_FILE_NAME = "states";
    private static final String DISK_DIR_NAME = "bic";

    private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
    private static final String ENFORCE_PERMISSION_ERROR_MSG =
            "User is not permitted to call service: ";

    private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
    private static final int MSG_USAGE_EVENT_RECEIVED = 0;
    private static final int MSG_PACKAGE_ADDED = 1;
    private static final int MSG_PACKAGE_REMOVED = 2;

    private final BinderService mBinderService;
    private final PackageManager mPackageManager;
    // TODO migrate all internal PackageManager calls to PackageManagerInternal where possible.
    // b/310983905
    private final PackageManagerInternal mPackageManagerInternal;
    private final PermissionManagerServiceInternal mPermissionManager;
    private final Handler mHandler;
    private final File mDiskFile;

    private final Context mContext;

    private SparseSetArray<String> mBackgroundInstalledPackages = null;

    // User ID -> package name -> set of foreground time frame
    private final SparseArrayMap<String,
            TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
            new SparseArrayMap<>();
    private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
            mInstallerForegroundTimeFrames = new SparseArrayMap<>();

    public BackgroundInstallControlService(@NonNull Context context) {
        this(new InjectorImpl(context));
@@ -102,15 +111,13 @@ public class BackgroundInstallControlService extends SystemService {
        mPermissionManager = injector.getPermissionManager();
        mHandler = new EventHandler(injector.getLooper(), this);
        mDiskFile = injector.getDiskFile();
        mContext = injector.getContext();
        UsageStatsManagerInternal usageStatsManagerInternal =
                injector.getUsageStatsManagerInternal();
        usageStatsManagerInternal.registerListener(
                (userId, event) ->
                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
                                userId,
                                0,
                                event).sendToTarget()
        );
                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, userId, 0, event)
                                .sendToTarget());
        mBinderService = new BinderService(this);
    }

@@ -124,12 +131,17 @@ public class BackgroundInstallControlService extends SystemService {
        @Override
        public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
                @PackageManager.PackageInfoFlagsBits long flags, int userId) {
            if (Flags.bicClient()) {
                mService.enforceCallerPermissions();
            }
            if (!Build.IS_DEBUGGABLE) {
                return mService.getBackgroundInstalledPackages(flags, userId);
            }
            // The debug.transparency.bg-install-apps (only works for debuggable builds)
            // is used to set mock list of background installed apps for testing.
            // The list of apps' names is delimited by ",".
            // TODO: Remove after migrating test to new background install method using
            // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905
            String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
            if (TextUtils.isEmpty(propertyString)) {
                return mService.getBackgroundInstalledPackages(flags, userId);
@@ -137,16 +149,24 @@ public class BackgroundInstallControlService extends SystemService {
                return mService.getMockBackgroundInstalledPackages(propertyString);
            }
        }

    }

    @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
    void enforceCallerPermissions() throws SecurityException {
        mContext.enforceCallingOrSelfPermission(GET_BACKGROUND_INSTALLED_PACKAGES,
                ENFORCE_PERMISSION_ERROR_MSG + GET_BACKGROUND_INSTALLED_PACKAGES);
    }

    @VisibleForTesting
    ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
        final long token = Binder.clearCallingIdentity();
        try {
            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
                    PackageManager.PackageInfoFlags.of(flags), userId);

            initBackgroundInstalledPackages();

            ListIterator<PackageInfo> iter = packages.listIterator();
            while (iter.hasNext()) {
                String packageName = iter.next().packageName;
@@ -156,6 +176,9 @@ public class BackgroundInstallControlService extends SystemService {
            }

            return new ParceledListSlice<>(packages);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
@@ -168,8 +191,9 @@ public class BackgroundInstallControlService extends SystemService {
        List<PackageInfo> mockPackages = new ArrayList<>();
        for (String name : mockPackageNames) {
            try {
                PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
                PackageInfo packageInfo =
                        mPackageManager.getPackageInfo(
                                name, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
                mockPackages.add(packageInfo);
            } catch (PackageManager.NameNotFoundException e) {
                Slog.w(TAG, "Package's PackageInfo not found " + name);
@@ -190,18 +214,16 @@ public class BackgroundInstallControlService extends SystemService {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_USAGE_EVENT_RECEIVED: {
                    mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
                case MSG_USAGE_EVENT_RECEIVED:
                    mService.handleUsageEvent(
                            (UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
                    break;
                }
                case MSG_PACKAGE_ADDED: {
                case MSG_PACKAGE_ADDED:
                    mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
                    break;
                }
                case MSG_PACKAGE_REMOVED: {
                case MSG_PACKAGE_REMOVED:
                    mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
                    break;
                }
                default:
                    Slog.w(TAG, "Unknown message: " + msg.what);
            }
@@ -211,8 +233,9 @@ public class BackgroundInstallControlService extends SystemService {
    void handlePackageAdd(String packageName, int userId) {
        ApplicationInfo appInfo = null;
        try {
            appInfo = mPackageManager.getApplicationInfoAsUser(packageName,
                    PackageManager.ApplicationInfoFlags.of(0), userId);
            appInfo =
                    mPackageManager.getApplicationInfoAsUser(
                            packageName, PackageManager.ApplicationInfoFlags.of(0), userId);
        } catch (PackageManager.NameNotFoundException e) {
            Slog.w(TAG, "Package's appInfo not found " + packageName);
            return;
@@ -231,15 +254,18 @@ public class BackgroundInstallControlService extends SystemService {

        // the installers without INSTALL_PACKAGES perm can't perform
        // the installation in background. So we can just filter out them.
        if (mPermissionManager.checkPermission(installerPackageName,
                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
                userId) != PackageManager.PERMISSION_GRANTED) {
        if (mPermissionManager.checkPermission(
                installerPackageName,
                android.Manifest.permission.INSTALL_PACKAGES,
                Context.DEVICE_ID_DEFAULT,
                userId)
                != PERMISSION_GRANTED) {
            return;
        }

        // convert up-time to current time.
        final long installTimestamp = System.currentTimeMillis()
                - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
        final long installTimestamp =
                System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);

        if (installedByAdb(initiatingPackageName)
                || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -257,8 +283,8 @@ public class BackgroundInstallControlService extends SystemService {
        return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
    }

    private boolean wasForegroundInstallation(String installerPackageName,
            int userId, long installTimestamp) {
    private boolean wasForegroundInstallation(
            String installerPackageName, int userId, long installTimestamp) {
        TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
                mInstallerForegroundTimeFrames.get(userId, installerPackageName);

@@ -347,12 +373,12 @@ public class BackgroundInstallControlService extends SystemService {
            for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
                int userId = mBackgroundInstalledPackages.keyAt(i);
                for (String packageName : mBackgroundInstalledPackages.get(userId)) {
                    long token = protoOutputStream.start(
                    long token =
                            protoOutputStream.start(
                                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                    protoOutputStream.write(
                            BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
                    protoOutputStream.write(
                            BackgroundInstalledPackageProto.USER_ID, userId + 1);
                    protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, userId + 1);
                    protoOutputStream.end(token);
                }
            }
@@ -385,22 +411,27 @@ public class BackgroundInstallControlService extends SystemService {
                        != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                    continue;
                }
                long token = protoInputStream.start(
                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                long token =
                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                String packageName = null;
                int userId = UserHandle.USER_NULL;
                while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                    switch (protoInputStream.getFieldNumber()) {
                        case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
                            packageName = protoInputStream.readString(
                            packageName =
                                    protoInputStream.readString(
                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
                            break;
                        case (int) BackgroundInstalledPackageProto.USER_ID:
                            userId = protoInputStream.readInt(
                                    BackgroundInstalledPackageProto.USER_ID) - 1;
                            userId =
                                    protoInputStream.readInt(
                                            BackgroundInstalledPackageProto.USER_ID)
                                            - 1;
                            break;
                        default:
                            Slog.w(TAG, "Undefined field in proto: "
                            Slog.w(
                                    TAG,
                                    "Undefined field in proto: "
                                            + protoInputStream.getFieldNumber());
                    }
                }
@@ -430,9 +461,12 @@ public class BackgroundInstallControlService extends SystemService {
        if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
            return true;
        }
        return mPermissionManager.checkPermission(pkgName,
                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
                userId) == PackageManager.PERMISSION_GRANTED;
        return mPermissionManager.checkPermission(
                pkgName,
                android.Manifest.permission.INSTALL_PACKAGES,
                Context.DEVICE_ID_DEFAULT,
                userId)
                == PERMISSION_GRANTED;
    }

    @Override
@@ -446,19 +480,20 @@ public class BackgroundInstallControlService extends SystemService {
            publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
        }

        mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
        mPackageManagerInternal.getPackageList(
                new PackageManagerInternal.PackageListObserver() {
                    @Override
                    public void onPackageAdded(String packageName, int uid) {
                        final int userId = UserHandle.getUserId(uid);
                mHandler.obtainMessage(MSG_PACKAGE_ADDED,
                        userId, 0, packageName).sendToTarget();
                        mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
                                .sendToTarget();
                    }

                    @Override
                    public void onPackageRemoved(String packageName, int uid) {
                        final int userId = UserHandle.getUserId(uid);
                mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
                        userId, 0, packageName).sendToTarget();
                        mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
                                .sendToTarget();
                    }
                });
    }
@@ -516,7 +551,7 @@ public class BackgroundInstallControlService extends SystemService {
    }

    /**
     * Dependency injector for {@link #BackgroundInstallControlService)}.
     * Dependency injector for {@link BackgroundInstallControlService}.
     */
    interface Injector {
        Context getContext();
@@ -532,6 +567,7 @@ public class BackgroundInstallControlService extends SystemService {
        Looper getLooper();

        File getDiskFile();

    }

    private static final class InjectorImpl implements Injector {
@@ -568,11 +604,11 @@ public class BackgroundInstallControlService extends SystemService {

        @Override
        public Looper getLooper() {
            ServiceThread serviceThread = new ServiceThread(TAG,
                    android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
            ServiceThread serviceThread =
                    new ServiceThread(
                            TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
            serviceThread.start();
            return serviceThread.getLooper();

        }

        @Override
+0 −1
Original line number Diff line number Diff line
@@ -11,7 +11,6 @@
// 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 {
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
+16 −7
Original line number Diff line number Diff line
@@ -41,6 +41,10 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit
    private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk";
    private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk";

    // TODO: Move the silent installs to test-app using {@link
    // BackgroundInstallControlServiceTest#installPackage(String, String)} and remove deviceConfig
    // branch in BICS.
    // b/310983905
    @Test
    public void testGetMockBackgroundInstalledPackages() throws Exception {
        installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1);
@@ -49,9 +53,14 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit
        assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull();
        assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull();

        assertThat(getDevice().setProperty("debug.transparency.bg-install-apps",
                    MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue();
        runDeviceTest("testGetMockBackgroundInstalledPackages");
        assertThat(
                getDevice()
                        .setProperty(
                                "debug.transparency.bg-install-apps",
                                MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2))
                .isTrue();
        runDeviceTest(
                "BackgroundInstallControlServiceTest", "testGetMockBackgroundInstalledPackages");
        assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull();
        assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull();

@@ -65,9 +74,9 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit
        assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue();
    }

    private void runDeviceTest(String method) throws DeviceNotAvailableException {
    private void runDeviceTest(String testName, String method) throws DeviceNotAvailableException {
        var options = new DeviceTestRunOptions(PACKAGE_NAME);
        options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest");
        options.setTestClassName(PACKAGE_NAME + "." + testName);
        options.setTestMethodName(method);
        runDeviceTests(options);
    }
+39 −11
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.server.pm.test.app;

import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;

import static com.android.compatibility.common.util.SystemUtil.runShellCommand;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
@@ -23,12 +27,15 @@ import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;

import androidx.test.runner.AndroidJUnit4;

import com.android.compatibility.common.util.ShellIdentityUtils;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,30 +46,51 @@ import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class BackgroundInstallControlServiceTest {
    private static final String TAG = "BackgroundInstallControlServiceTest";
    private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3";

    private IBackgroundInstallControlService mIBics;

    @Before
    public void setUp() {
        mIBics = IBackgroundInstallControlService.Stub.asInterface(
        mIBics =
                IBackgroundInstallControlService.Stub.asInterface(
                        ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
        assertThat(mIBics).isNotNull();
    }

    @After
    public void tearDown() {
        runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME);
    }

    @Test
    public void testGetMockBackgroundInstalledPackages() throws RemoteException {
        ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages(
                    PackageManager.MATCH_ALL,
                    UserHandle.USER_ALL);
        ParceledListSlice<PackageInfo> slice =
                ShellIdentityUtils.invokeMethodWithShellPermissions(
                        mIBics,
                        (bics) -> {
                            try {
                                return bics.getBackgroundInstalledPackages(
                                        PackageManager.MATCH_ALL, Process.myUserHandle()
                                                .getIdentifier());
                            } catch (RemoteException e) {
                                throw new RuntimeException(e);
                            }
                        },
                        GET_BACKGROUND_INSTALLED_PACKAGES);
        assertThat(slice).isNotNull();

        var packageList = slice.getList();
        assertThat(packageList).isNotNull();
        assertThat(packageList).hasSize(2);

        var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1",
        var expectedPackageNames =
                Set.of(
                        "com.android.servicestests.apps.bicmockapp1",
                        "com.android.servicestests.apps.bicmockapp2");
        var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName)
        var actualPackageNames =
                packageList.stream()
                        .map((packageInfo) -> packageInfo.packageName)
                        .collect(Collectors.toSet());
        assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
    }
Loading