Loading core/java/android/content/pm/PackageInstaller.java +11 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) Loading core/java/android/content/pm/PackageItemInfo.java +10 −2 Original line number Diff line number Diff line Loading @@ -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; Loading core/tests/PackageInstallerSessions/Android.bp 0 → 100644 +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"], } core/tests/PackageInstallerSessions/AndroidManifest.xml 0 → 100644 +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> core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt 0 → 100644 +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
core/java/android/content/pm/PackageInstaller.java +11 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) Loading
core/java/android/content/pm/PackageItemInfo.java +10 −2 Original line number Diff line number Diff line Loading @@ -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; Loading
core/tests/PackageInstallerSessions/Android.bp 0 → 100644 +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"], }
core/tests/PackageInstallerSessions/AndroidManifest.xml 0 → 100644 +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>
core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt 0 → 100644 +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) } } }