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

Commit b755a233 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add PackageInstaller SessionParams restrictions" into rvc-dev am: a817a051

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11621266

Change-Id: I46e51847d66829def5559b9081c9a89e5c6492ea
parents 5f0d3981 a817a051
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -1449,6 +1449,13 @@ public class PackageInstaller {
        /** {@hide} */
        public static final int UID_UNKNOWN = -1;

        /**
         * This value is derived from the maximum file name length. No package above this limit
         * can ever be successfully installed on the device.
         * @hide
         */
        public static final int MAX_PACKAGE_NAME_LENGTH = 255;

        /** {@hide} */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public int mode = MODE_INVALID;
@@ -1642,6 +1649,8 @@ public class PackageInstaller {

        /**
         * Optionally set a label representing the app being installed.
         *
         * This value will be trimmed to the first 1000 characters.
         */
        public void setAppLabel(@Nullable CharSequence appLabel) {
            this.appLabel = (appLabel != null) ? appLabel.toString() : null;
@@ -1711,7 +1720,8 @@ public class PackageInstaller {
         *
         * <p>Initially, all restricted permissions are whitelisted but you can change
         * which ones are whitelisted by calling this method or the corresponding ones
         * on the {@link PackageManager}.
         * on the {@link PackageManager}. Only soft or hard restricted permissions on the current
         * Android version are supported and any invalid entries will be removed.
         *
         * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int)
         * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int)
+10 −2
Original line number Diff line number Diff line
@@ -49,8 +49,16 @@ import java.util.Objects;
 * in the implementation of Parcelable in subclasses.
 */
public class PackageItemInfo {
    /** The maximum length of a safe label, in characters */
    private static final int MAX_SAFE_LABEL_LENGTH = 50000;

    /**
     * The maximum length of a safe label, in characters
     *
     * TODO(b/157997155): It may make sense to expose this publicly so that apps can check for the
     *  value and truncate the strings/use a different label, without having to hardcode and make
     *  assumptions about the value.
     * @hide
     */
    public static final int MAX_SAFE_LABEL_LENGTH = 1000;

    /** @hide */
    public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
+42 −0
Original line number Diff line number Diff line
//
// Copyright 2020 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.
//

android_test {
    name: "FrameworksCorePackageInstallerSessionsTests",

    srcs: [
        "src/**/*.kt",
    ],
    static_libs: [
        "androidx.test.rules",
        "compatibility-device-util-axt",
        "frameworks-base-testutils",
        "platform-test-annotations",
        "testng",
        "truth-prebuilt",
    ],

    libs: [
        "android.test.runner",
        "android.test.base",
        "framework",
        "framework-res",
    ],

    platform_apis: true,
    sdk_version: "core_platform",
    test_suites: ["device-tests"],
}
+29 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
 * Copyright (C) 2020 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.
 -->

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.frameworks.coretests.package_installer_sessions"
    >

    <application>
        <uses-library android:name="android.test.runner" />
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
        android:targetPackage="com.android.frameworks.coretests.package_installer_sessions"/>
</manifest>
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.content.pm

import android.content.Context
import android.content.pm.PackageInstaller.SessionParams
import android.platform.test.annotations.Presubmit
import androidx.test.InstrumentationRegistry
import androidx.test.filters.LargeTest
import com.android.compatibility.common.util.ShellIdentityUtils
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.testng.Assert.assertThrows
import kotlin.random.Random

/**
 * For verifying public [PackageInstaller] session APIs. This differs from
 * [com.android.server.pm.PackageInstallerSessionTest] in services because that mocks the session,
 * whereas this test uses the installer on device.
 */
@Presubmit
class PackageSessionTests {

    companion object {
        /**
         * Permissions marked "hardRestricted" or "softRestricted" in core/res/AndroidManifest.xml.
         */
        private val RESTRICTED_PERMISSIONS = listOf(
                "android.permission.SEND_SMS",
                "android.permission.RECEIVE_SMS",
                "android.permission.READ_SMS",
                "android.permission.RECEIVE_WAP_PUSH",
                "android.permission.RECEIVE_MMS",
                "android.permission.READ_CELL_BROADCASTS",
                "android.permission.ACCESS_BACKGROUND_LOCATION",
                "android.permission.READ_CALL_LOG",
                "android.permission.WRITE_CALL_LOG",
                "android.permission.PROCESS_OUTGOING_CALLS"
        )
    }

    private val context: Context = InstrumentationRegistry.getContext()

    private val installer = context.packageManager.packageInstaller

    @Before
    @After
    fun abandonAllSessions() {
        installer.mySessions.asSequence()
                .map { it.sessionId }
                .forEach {
                    try {
                        installer.abandonSession(it)
                    } catch (ignored: Exception) {
                        // Querying for sessions checks by calling package name, but abandoning
                        // checks by UID, which won't match if this test failed to clean up
                        // on a previous install + run + uninstall, so ignore these failures.
                    }
                }
    }

    @Test
    fun truncateAppLabel() {
        val longLabel = invalidAppLabel()
        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
            setAppLabel(longLabel)
        }

        createSession(params) {
            assertThat(installer.getSessionInfo(it)?.appLabel)
                    .isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH))
        }
    }

    @Test
    fun removeInvalidAppPackageName() {
        val longName = invalidPackageName()
        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
            setAppPackageName(longName)
        }

        createSession(params) {
            assertThat(installer.getSessionInfo(it)?.appPackageName)
                    .isEqualTo(null)
        }
    }

    @Test
    fun removeInvalidInstallerPackageName() {
        val longName = invalidPackageName()
        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
            setInstallerPackageName(longName)
        }

        createSession(params) {
            // If a custom installer name is dropped, it defaults to the caller
            assertThat(installer.getSessionInfo(it)?.installerPackageName)
                    .isEqualTo(context.packageName)
        }
    }

    @Test
    fun truncateWhitelistPermissions() {
        val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
            setWhitelistedRestrictedPermissions(invalidPermissions())
        }

        createSession(params) {
            assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!)
                    .containsExactlyElementsIn(RESTRICTED_PERMISSIONS)
        }
    }

    @LargeTest
    @Test
    fun allocateMaxSessionsWithPermission() {
        ShellIdentityUtils.invokeWithShellPermissions {
            repeat(1024) { createDummySession() }
            assertThrows(IllegalStateException::class.java) { createDummySession() }
        }
    }

    @LargeTest
    @Test
    fun allocateMaxSessionsNoPermission() {
        repeat(50) { createDummySession() }
        assertThrows(IllegalStateException::class.java) { createDummySession() }
    }

    private fun createDummySession() {
        installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL)
                .apply {
                    setAppPackageName(invalidPackageName())
                    setAppLabel(invalidAppLabel())
                    setWhitelistedRestrictedPermissions(invalidPermissions())
                })
    }

    private fun invalidPackageName(maxLength: Int = SessionParams.MAX_PACKAGE_NAME_LENGTH): String {
        return (0 until (maxLength + 10))
                .asSequence()
                .mapIndexed { index, _ ->
                    // A package name needs at least one separator
                    if (index == 2) {
                        '.'
                    } else {
                        Random.nextInt('z' - 'a').toChar() + 'a'.toInt()
                    }
                }
                .joinToString(separator = "")
    }

    private fun invalidAppLabel() = (0 until PackageItemInfo.MAX_SAFE_LABEL_LENGTH + 10)
            .asSequence()
            .map { Random.nextInt(Char.MAX_VALUE.toInt()).toChar() }
            .joinToString(separator = "")

    private fun invalidPermissions() = RESTRICTED_PERMISSIONS.toMutableSet()
            .apply {
                // Add some invalid permission names
                repeat(10) { add(invalidPackageName(300)) }
            }

    private fun createSession(params: SessionParams, block: (Int) -> Unit = {}) {
        val sessionId = installer.createSession(params)
        try {
            block(sessionId)
        } finally {
            installer.abandonSession(sessionId)
        }
    }
}
Loading