blob: 8179d1b746e7672eb7b9d0ec7b05160ca846bed5 [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.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@AppModeFull(reason = "Instant apps cannot create installer sessions")
@RunWith(AndroidJUnit4::class)
class UpdateOwnershipEnforcementTest : PackageInstallerTestBase() {
companion object {
const val TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME =
"CtsEmptyTestApp_NotAllowUpdateOwnership.apk"
const val TEST_INSTALLER_APK_NAME = "CtsEmptyInstallerApp.apk"
const val TEST_INSTALLER_APK_PACKAGE_NAME = "android.packageinstaller.emptyinstaller.cts"
}
private var isUpdateOwnershipEnforcementAvailable: String? = null
private val notAllowUpdateOwnershipApkFile = File(
context.filesDir,
TEST_NOT_ALLOW_UPDATE_OWNERSHIP_APK_NAME
)
/**
* Make sure the feature flag of update ownership enforcement is available.
*/
@Before
fun setUpdateOwnershipEnforcementAvailable() {
isUpdateOwnershipEnforcementAvailable =
getDeviceProperty(PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE)
setDeviceProperty(PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE, "true")
}
/**
* Install the test installer package.
*/
@Before
fun installTestInstaller() {
installPackage(TEST_INSTALLER_APK_NAME)
}
/**
* Restore the status of update ownership enforcement.
*/
@After
fun recoverUpdateOwnershipEnforcement() {
setDeviceProperty(
PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE,
isUpdateOwnershipEnforcementAvailable
)
}
/**
* Uninstall the test installer package.
*/
@After
fun uninstallTestInstaller() {
uninstallPackage(TEST_INSTALLER_APK_PACKAGE_NAME)
}
/**
* Clean up all sessions created by this test.
*/
@After
fun cleanUpSessions() {
pi.mySessions.forEach {
try {
pi.abandonSession(it.sessionId)
} catch (ignored: Exception) {
}
}
}
/**
* 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 shell command can request to update the update owner.
*/
@Test
fun installViaShellCommand_requestUpdateOwner() {
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
// Update without '--request-update-owner' and assign self as the installer.
installTestPackage("-i " + context.opPackageName)
var sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(TEST_INSTALLER_APK_PACKAGE_NAME, sourceInfo.updateOwnerPackageName)
// Update with '--request-update-owner' and assign self as the installer.
installTestPackage("--update-ownership -i " + context.opPackageName)
sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(context.opPackageName, 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 change if the installer requests.
*/
@Test
fun setRequestUpdateOwnership_requestWhenUpdate_ownerChanged() {
// 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(context.opPackageName, sourceInfo.updateOwnerPackageName)
}
/**
* Checks that the update owner will retain if the installer doesn't request.
*/
@Test
fun setRequestUpdateOwnership_notRequestWhenUpdate_ownerRetained() {
// Install the test app and enable update ownership enforcement with another package
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
startInstallationViaSession()
clickInstallerUIButton(INSTALL_BUTTON_ID)
val result = getInstallSessionResult()
assertEquals(PackageInstaller.STATUS_SUCCESS, result.status)
val sourceInfo = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
assertEquals(TEST_INSTALLER_APK_PACKAGE_NAME, 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 the pending user action reason is REASON_OWNERSHIP_CHANGED when update owner
* is changed.
*/
@Test
fun getPendingUserActionReason_requestUpdateOwner_reasonOwnershipChanged() {
installTestPackage("--update-ownership -i $TEST_INSTALLER_APK_PACKAGE_NAME")
assertInstalled()
val (sessionId, session) = createSession(
INSTALL_REQUEST_UPDATE_OWNERSHIP,
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_OWNERSHIP_CHANGED,
sessionInfo!!.getPendingUserActionReason()
)
clickInstallerUIButton(INSTALL_BUTTON_ID)
// request should have succeeded
getInstallSessionResult()
}
/**
* 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_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)
}
}