blob: 515b1773bb732f7a43ebf364481aadcf05de2c08 [file] [log] [blame]
/*
* 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 android.packageinstaller.install.cts
import android.Manifest
import android.content.pm.PackageInstaller
import android.platform.test.annotations.AppModeFull
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import java.io.File
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
@AppModeFull(reason = "Instant apps cannot create installer sessions")
@RunWith(AndroidJUnit4::class)
class UpdateOwnershipEnforcementTest : UpdateOwnershipEnforcementTestBase() {
companion object {
const val TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME =
"CtsEmptyTestApp_NotAllowUpdateOwnership.apk"
}
private val notAllowUpdateOwnershipApkFile = File(
context.filesDir,
TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME
)
/**
* Checks that we can get default value from isRequestUpdateOwnership.
*/
@Test
fun isRequestUpdateOwnership_notSet_returnFalse() {
val (sessionId, session) = createSession(
0 /* installFlags */,
false /* isMultiPackage */,
null /* packageSource */
)
val sessionInfo = pi.getSessionInfo(sessionId)
assertNotNull(sessionInfo)
assertEquals(false, sessionInfo!!.isRequestUpdateOwnership)
assertEquals(false, session.isRequestUpdateOwnership)
}
/**
* Checks that we can get correct value from isRequestUpdateOwnership.
*/
@Test
fun isRequestUpdateOwnership_set_returnTrue() {
val (sessionId, session) = createSession(
INSTALL_REQUEST_UPDATE_OWNERSHIP,
false /* isMultiPackage */,
null /* packageSource */
)
val sessionInfo = pi.getSessionInfo(sessionId)
assertNotNull(sessionInfo)
assertEquals(true, sessionInfo!!.isRequestUpdateOwnership)
assertEquals(true, session.isRequestUpdateOwnership)
}
/**
* Checks that we can enforce the update ownership when the first install.
*/
@Test
fun setRequestUpdateOwnership_whenInitialInstall_hasUpdateOwner() {
// First install the test app with enforcing the update ownership.
startInstallationViaSession(INSTALL_REQUEST_UPDATE_OWNERSHIP)
clickInstallerUIButton(INSTALL_BUTTON_ID)
// request should have succeeded
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
val sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
// This installer should be the update owner
assertEquals(context.opPackageName, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that we cannot enforce the update ownership when the update.
*/
@Test
fun setRequestUpdateOwnership_whenUpdate_hasNoUpdateOwner() {
// First install the test app without enforcing the update ownership.
installTestPackage()
assertInstalled()
// Try to update the app with using SessionParams.setRequestUpdateOwnership.
startInstallationViaSession(INSTALL_REQUEST_UPDATE_OWNERSHIP)
clickInstallerUIButton(INSTALL_BUTTON_ID)
// request should have succeeded
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
val sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
// Since we don't allow enabling the update ownership when the update, the update
// owner should be null.
assertEquals(null, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that update owner is removed after it is uninstalled.
*/
@Test
fun uninstallUpdateOwner_hasNoUpdateOwner() {
// Install the test apk and assign above test installer as the update owner
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
var sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(TEST_INSTALLER_APK_PACKAGE_NAME, sourceInfo.updateOwnerPackageName)
uninstallPackage(TEST_INSTALLER_APK_PACKAGE_NAME)
sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(null, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that shell command can enable the update ownership enforcement.
*/
@Test
fun installViaShellCommand_enableUpdateOwnership() {
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
val sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(TEST_INSTALLER_APK_PACKAGE_NAME, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that an update owner can update the package without user action.
*/
@Test
fun updateOwnershipEnforcement_updateByOwner_hasNoUserAction() {
// Install the test app and enable update ownership enforcement with self package
installTestPackage("--update-ownership -i " + context.opPackageName)
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES)
startInstallationViaSessionNoPrompt()
// No need to click installer UI here.
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
assertInstalled()
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity()
}
}
/**
* Checks that an installer needs user action to update a package when
* it's not the update owner even if it has granted INSTALL_PACKAGES permission.
*/
@Test
fun updateOwnershipEnforcement_updateByNonOwner_hasUserAction() {
// Install the test app and enable update ownership enforcement with another package
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES)
startInstallationViaSession()
// Expecting a prompt to proceed.
clickInstallerUIButton(INSTALL_BUTTON_ID)
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
assertInstalled()
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity()
}
}
/**
* Checks that a privileged installer can update the package without user action even if
* it's not the update owner when the feature flag is turn off.
*/
@Test
fun featureDisabled_updateByNonOwner_hasNoUserAction() {
// Install the test app and enable update ownership enforcement with another package
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
setDeviceProperty(PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE, "false")
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES)
startInstallationViaSessionNoPrompt()
// No need to click installer UI here.
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
assertInstalled()
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity()
}
}
/**
* Checks that the update owner will be cleared once the installer changes.
*/
@Test
fun updateOwnershipEnforcement_updateByNonOwner_hasNoUpdateOwner() {
// Install the test app and enable update ownership enforcement with another package
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
startInstallationViaSession(INSTALL_REQUEST_UPDATE_OWNERSHIP)
clickInstallerUIButton(INSTALL_BUTTON_ID)
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
val sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(null, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that the update owner will retain if the installer doesn't change.
*/
@Test
fun setRequestUpdateOwnership_notRequestWhenUpdate_ownerRetained() {
// Install the test app and enable update ownership enforcement with another package
installTestPackage("--update-ownership -i " + context.opPackageName)
startInstallationViaSession()
clickInstallerUIButton(INSTALL_BUTTON_ID)
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
val sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(context.opPackageName, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that the pending user action reason is REASON_UNSPECIFIED when not requesting the
* update ownership.
*/
@Test
fun getPendingUserActionReason_notRequestUpdateOwnership_reasonUnspecified() {
installTestPackage()
assertInstalled()
val (sessionId, session) = createSession(
0 /* installFlags */,
false /* isMultiPackage */,
null /* packageSource*/
)
writeAndCommitSession(TEST_APK_NAME, session)
// Since SessionInfo will be null once install is complete, we need to get it when prompting
val sessionInfo = pi.getSessionInfo(sessionId)
assertNotNull(sessionInfo)
assertEquals(
PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE,
sessionInfo!!.getPendingUserActionReason()
)
clickInstallerUIButton(INSTALL_BUTTON_ID)
// request should have succeeded
getInstallSessionResult()
}
/**
* Checks that user pending action is not required when installing an update that opts out
* of ownership enforcement.
*/
@Test
fun getPendingUserActionReason_requestUpdateOwnershipWhenOwnershipNotAllowed_hasNoUserAction() {
// Install the test app and enable update ownership enforcement with another package
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
copyTestNotAllowUpdateOwnershipApk()
try {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES)
startInstallationViaSession(
apkName = TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME, expectedPrompt = false)
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
assertInstalled()
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity()
}
}
/**
* Checks that the pending user action reason is REASON_REMIND_OWNERSHIP when update owner
* isn't changed.
*/
@Test
fun getPendingUserActionReason_notRequestUpdateOwner_reasonRemindOwnership() {
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
assertInstalled()
val (sessionId, session) = createSession(
0 /* installFlags */,
false /* isMultiPackage */,
null /* packageSource*/
)
writeAndCommitSession(TEST_APK_NAME, session)
// Since SessionInfo will be null once install is complete, we need to get it when prompting
val sessionInfo = pi.getSessionInfo(sessionId)
assertNotNull(sessionInfo)
assertEquals(
PackageInstaller.REASON_REMIND_OWNERSHIP,
sessionInfo!!.getPendingUserActionReason()
)
clickInstallerUIButton(INSTALL_BUTTON_ID)
// request should have succeeded
getInstallSessionResult()
}
/**
* Checks that the app can opt out the update ownership via manifest attr.
*/
@Test
fun allowUpdateOwnership_isFalse_shouldRemoveUpdateOwner() {
copyTestNotAllowUpdateOwnershipApk()
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
var sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(TEST_INSTALLER_APK_PACKAGE_NAME, sourceInfo.updateOwnerPackageName)
startInstallationViaSession(
0 /* installFlags */,
TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME
)
clickInstallerUIButton(INSTALL_BUTTON_ID)
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(null, sourceInfo.updateOwnerPackageName)
}
private fun copyTestNotAllowUpdateOwnershipApk() {
File(
TEST_APK_LOCATION,
TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME
).copyTo(target = notAllowUpdateOwnershipApkFile, overwrite = true)
}
/**
* Checks that the update owner can relinquish the update ownership.
*/
@Test
fun relinquishUpdateOwnership_fromUpdateOwner_shouldRemoveUpdateOwner() {
installTestPackage("--update-ownership -i " + context.opPackageName)
var sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(context.opPackageName, sourceInfo.updateOwnerPackageName)
pm.relinquishUpdateOwnership(TEST_APK_PACKAGE_NAME)
sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(null, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that we cannot relinquish the update ownership from non-update owner.
*/
@Test
fun relinquishUpdateOwnership_notFromUpdateOwner_throwSecurityException() {
installTestPackage("--update-ownership")
var sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(SHELL_PACKAGE_NAME, sourceInfo.updateOwnerPackageName)
try {
pm.relinquishUpdateOwnership(TEST_APK_PACKAGE_NAME)
fail("relinquishUpdateOwnership from non-update owner should throw SecurityException.")
} catch (e: SecurityException) {
// Expected behavior
sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(SHELL_PACKAGE_NAME, sourceInfo.updateOwnerPackageName)
}
}
}