1 /* <lambda>null2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.packageinstaller.install.cts 18 19 import android.app.PendingIntent 20 import android.app.PendingIntent.FLAG_MUTABLE 21 import android.app.PendingIntent.FLAG_UPDATE_CURRENT 22 import android.content.BroadcastReceiver 23 import android.content.Context 24 import android.content.Intent 25 import android.content.Intent.EXTRA_INTENT 26 import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK 27 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 28 import android.content.IntentFilter 29 import android.content.pm.PackageInfo 30 import android.content.pm.PackageInstaller 31 import android.content.pm.PackageInstaller.EXTRA_PRE_APPROVAL 32 import android.content.pm.PackageInstaller.EXTRA_STATUS 33 import android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE 34 import android.content.pm.PackageInstaller.PreapprovalDetails 35 import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID 36 import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION 37 import android.content.pm.PackageInstaller.Session 38 import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL 39 import android.content.pm.PackageManager 40 import android.provider.DeviceConfig 41 import android.support.test.uiautomator.By 42 import android.support.test.uiautomator.BySelector 43 import android.support.test.uiautomator.UiDevice 44 import android.support.test.uiautomator.Until 45 import android.util.Log 46 import androidx.core.content.FileProvider 47 import androidx.test.InstrumentationRegistry 48 import androidx.test.rule.ActivityTestRule 49 import com.android.compatibility.common.util.DisableAnimationRule 50 import com.android.compatibility.common.util.FutureResultActivity 51 import com.android.compatibility.common.util.SystemUtil 52 import java.io.File 53 import java.util.concurrent.CompletableFuture 54 import java.util.concurrent.LinkedBlockingQueue 55 import java.util.concurrent.TimeUnit 56 import org.junit.After 57 import org.junit.Assert 58 import org.junit.Before 59 import org.junit.Rule 60 61 open class PackageInstallerTestBase { 62 63 companion object { 64 const val TAG = "PackageInstallerTest" 65 66 const val INSTALL_BUTTON_ID = "button1" 67 const val CANCEL_BUTTON_ID = "button2" 68 69 const val TEST_APK_NAME = "CtsEmptyTestApp.apk" 70 const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts" 71 const val TEST_APK_LOCATION = "/data/local/tmp/cts/packageinstaller" 72 73 const val INSTALL_ACTION_CB = "PackageInstallerTestBase.install_cb" 74 75 const val CONTENT_AUTHORITY = "android.packageinstaller.install.cts.fileprovider" 76 77 const val PACKAGE_INSTALLER_PACKAGE_NAME = "com.android.packageinstaller" 78 const val SYSTEM_PACKAGE_NAME = "android" 79 const val SHELL_PACKAGE_NAME = "com.android.shell" 80 const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES" 81 82 const val PROPERTY_IS_PRE_APPROVAL_REQUEST_AVAILABLE = "is_preapproval_available" 83 const val PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE = 84 "is_update_ownership_enforcement_available" 85 86 const val TIMEOUT = 60000L 87 const val INSTALL_INSTANT_APP = 0x00000800 88 const val INSTALL_REQUEST_UPDATE_OWNERSHIP = 0x02000000 89 90 val context: Context = InstrumentationRegistry.getTargetContext() 91 val testUserId: Int = context.user.identifier 92 } 93 94 @get:Rule 95 val disableAnimationsRule = DisableAnimationRule() 96 97 @get:Rule 98 val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java) 99 100 protected val pm: PackageManager = context.packageManager 101 protected val pi = pm.packageInstaller 102 protected val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 103 private val apkFile = File(context.filesDir, TEST_APK_NAME) 104 105 data class SessionResult(val status: Int?, val preapproval: Boolean?, val message: String?) 106 107 /** If a status was received the value of the status, otherwise null */ 108 private var installSessionResult = LinkedBlockingQueue<SessionResult>() 109 110 private val receiver = object : BroadcastReceiver() { 111 override fun onReceive(context: Context, intent: Intent) { 112 val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID) 113 val preapproval = intent.getBooleanExtra(EXTRA_PRE_APPROVAL, false /* defaultValue */) 114 val msg = intent.getStringExtra(EXTRA_STATUS_MESSAGE) 115 Log.d(TAG, "status: $status, msg: $msg") 116 117 if (status == STATUS_PENDING_USER_ACTION) { 118 val activityIntent = intent.getParcelableExtra(EXTRA_INTENT, Intent::class.java) 119 Assert.assertEquals(activityIntent!!.extras!!.keySet().size, 1) 120 activityIntent.addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) 121 installDialogStarter.activity.startActivityForResult(activityIntent) 122 } 123 124 installSessionResult.offer(SessionResult(status, preapproval, msg)) 125 } 126 } 127 128 @Before 129 fun copyTestApk() { 130 File(TEST_APK_LOCATION, TEST_APK_NAME).copyTo(target = apkFile, overwrite = true) 131 } 132 133 @Before 134 fun wakeUpScreen() { 135 if (!uiDevice.isScreenOn) { 136 uiDevice.wakeUp() 137 } 138 uiDevice.executeShellCommand("wm dismiss-keyguard") 139 } 140 141 @Before 142 fun assertTestPackageNotInstalled() { 143 try { 144 context.packageManager.getPackageInfo(TEST_APK_PACKAGE_NAME, 0) 145 Assert.fail("Package should not be installed") 146 } catch (expected: PackageManager.NameNotFoundException) { 147 } 148 } 149 150 @Before 151 fun registerInstallResultReceiver() { 152 context.registerReceiver(receiver, IntentFilter(INSTALL_ACTION_CB), 153 Context.RECEIVER_EXPORTED) 154 } 155 156 @Before 157 fun waitForUIIdle() { 158 uiDevice.waitForIdle() 159 } 160 161 /** 162 * Wait for session's install result and return it 163 */ 164 protected fun getInstallSessionResult(timeout: Long = TIMEOUT): SessionResult { 165 return getInstallSessionResult(installSessionResult, timeout) 166 } 167 168 protected fun getInstallSessionResult( 169 installResult: LinkedBlockingQueue<SessionResult>, 170 timeout: Long = TIMEOUT 171 ): SessionResult { 172 return installResult.poll(timeout, TimeUnit.MILLISECONDS) 173 ?: SessionResult(null /* status */, null /* preapproval */, "Fail to poll result") 174 } 175 176 protected fun startInstallationViaSessionNoPrompt() { 177 startInstallationViaSession( 178 0 /* installFlags */, 179 TEST_APK_NAME, 180 null /* packageSource */, 181 false /* expectedPrompt */ 182 ) 183 } 184 185 protected fun startInstallationViaSessionWithPackageSource(packageSource: Int?) { 186 startInstallationViaSession(0 /* installFlags */, TEST_APK_NAME, packageSource) 187 } 188 189 protected fun createSession( 190 installFlags: Int, 191 isMultiPackage: Boolean, 192 packageSource: Int?, 193 paramsBlock: (PackageInstaller.SessionParams) -> Unit = {}, 194 ): Pair<Int, Session> { 195 // Create session 196 val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL) 197 // Handle additional install flags 198 if (installFlags and INSTALL_INSTANT_APP != 0) { 199 sessionParam.setInstallAsInstantApp(true) 200 } 201 if (installFlags and INSTALL_REQUEST_UPDATE_OWNERSHIP != 0) { 202 sessionParam.setRequestUpdateOwnership(true) 203 } 204 if (isMultiPackage) { 205 sessionParam.setMultiPackage() 206 } 207 if (packageSource != null) { 208 sessionParam.setPackageSource(packageSource) 209 } 210 211 paramsBlock(sessionParam) 212 213 val sessionId = pi.createSession(sessionParam) 214 val session = pi.openSession(sessionId)!! 215 216 return Pair(sessionId, session) 217 } 218 219 protected fun writeSession(session: Session, apkName: String) { 220 val apkFile = File(context.filesDir, apkName) 221 // Write data to session 222 apkFile.inputStream().use { fileOnDisk -> 223 session.openWrite(apkName, 0, -1).use { sessionFile -> 224 fileOnDisk.copyTo(sessionFile) 225 } 226 } 227 } 228 229 protected fun commitSession( 230 session: Session, 231 expectedPrompt: Boolean = true, 232 needFuture: Boolean = false 233 ): CompletableFuture<Int>? { 234 var intent = Intent(INSTALL_ACTION_CB) 235 .setPackage(context.getPackageName()) 236 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) 237 val pendingIntent = PendingIntent.getBroadcast( 238 context, 0 /* requestCode */, intent, FLAG_UPDATE_CURRENT or FLAG_MUTABLE) 239 240 var dialog: CompletableFuture<Int>? = null 241 242 if (!expectedPrompt) { 243 session.commit(pendingIntent.intentSender) 244 return dialog 245 } 246 247 // Commit session 248 if (needFuture) { 249 dialog = FutureResultActivity.doAndAwaitStart { 250 session.commit(pendingIntent.intentSender) 251 } 252 } else { 253 session.commit(pendingIntent.intentSender) 254 } 255 256 // The system should have asked us to launch the installer 257 val result = getInstallSessionResult() 258 Assert.assertEquals(STATUS_PENDING_USER_ACTION, result.status) 259 Assert.assertEquals(false, result.preapproval) 260 261 return dialog 262 } 263 264 protected fun startRequestUserPreapproval( 265 session: Session, 266 details: PreapprovalDetails, 267 expectedPrompt: Boolean = true 268 ) { 269 // In some abnormal cases, passing expectedPrompt as false to return immediately without 270 // waiting for timeout (60 secs). 271 if (!expectedPrompt) { requestSession(session, details); return } 272 273 FutureResultActivity.doAndAwaitStart { 274 requestSession(session, details) 275 } 276 277 // The system should have asked us to launch the installer 278 val result = getInstallSessionResult() 279 Assert.assertEquals(STATUS_PENDING_USER_ACTION, result.status) 280 Assert.assertEquals(true, result.preapproval) 281 } 282 283 private fun requestSession(session: Session, details: PreapprovalDetails) { 284 val pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */, 285 Intent(INSTALL_ACTION_CB).setPackage(context.packageName), 286 FLAG_UPDATE_CURRENT or FLAG_MUTABLE) 287 session.requestUserPreapproval(details, pendingIntent.intentSender) 288 } 289 290 protected fun startInstallationViaSession( 291 installFlags: Int = 0, 292 apkName: String = TEST_APK_NAME, 293 packageSource: Int? = null, 294 expectedPrompt: Boolean = true, 295 needFuture: Boolean = false, 296 paramsBlock: (PackageInstaller.SessionParams) -> Unit = {} 297 ): CompletableFuture<Int>? { 298 val (_, session) = createSession(installFlags, false, packageSource, paramsBlock) 299 writeSession(session, apkName) 300 return commitSession(session, expectedPrompt, needFuture) 301 } 302 303 protected fun writeAndCommitSession( 304 apkName: String, 305 session: Session, 306 expectedPrompt: Boolean = true 307 ) { 308 writeSession(session, apkName) 309 commitSession(session, expectedPrompt) 310 } 311 312 protected fun startInstallationViaMultiPackageSession( 313 installFlags: Int, 314 vararg apkNames: String, 315 needFuture: Boolean = false 316 ): CompletableFuture<Int>? { 317 val (sessionId, session) = createSession(installFlags, true, null) 318 for (apkName in apkNames) { 319 val (childSessionId, childSession) = createSession(installFlags, false, null) 320 writeSession(childSession, apkName) 321 session.addChildSessionId(childSessionId) 322 } 323 return commitSession(session, needFuture = needFuture) 324 } 325 326 /** 327 * Start an installation via an Intent 328 */ 329 protected fun startInstallationViaIntent( 330 intent: Intent = getInstallationIntent() 331 ): CompletableFuture<Int> { 332 return installDialogStarter.activity.startActivityForResult(intent) 333 } 334 335 protected fun getInstallationIntent(): Intent { 336 val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) 337 intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile) 338 intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION 339 intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) 340 341 return intent 342 } 343 344 protected fun startInstallationViaPreapprovalSession(session: Session) { 345 val pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */, 346 Intent(INSTALL_ACTION_CB).setPackage(context.packageName), 347 FLAG_UPDATE_CURRENT or FLAG_MUTABLE) 348 session.commit(pendingIntent.intentSender) 349 } 350 351 fun assertInstalled( 352 flags: PackageManager.PackageInfoFlags = PackageManager.PackageInfoFlags.of(0) 353 ): PackageInfo { 354 // Throws exception if package is not installed. 355 return pm.getPackageInfo(TEST_APK_PACKAGE_NAME, flags) 356 } 357 358 fun assertNotInstalled() { 359 try { 360 pm.getPackageInfo(TEST_APK_PACKAGE_NAME, PackageManager.PackageInfoFlags.of(0)) 361 Assert.fail("Package should not be installed") 362 } catch (expected: PackageManager.NameNotFoundException) { 363 } 364 } 365 366 /** 367 * Click a button in the UI of the installer app 368 * 369 * @param resId The resource ID of the button to click 370 */ 371 fun clickInstallerUIButton(resId: String) { 372 clickInstallerUIButton(By.res(PACKAGE_INSTALLER_PACKAGE_NAME, resId)) 373 } 374 375 /** 376 * Click a button in the UI of the installer app 377 * 378 * @param bySelector The bySelector of the button to click 379 */ 380 fun clickInstallerUIButton(bySelector: BySelector) { 381 val startTime = System.currentTimeMillis() 382 while (startTime + TIMEOUT > System.currentTimeMillis()) { 383 try { 384 uiDevice.wait(Until.findObject(bySelector), 1000).click() 385 return 386 } catch (ignore: Throwable) { 387 } 388 } 389 Assert.fail("Failed to click the button: $bySelector") 390 } 391 392 /** 393 * Sets the given secure setting to the provided value. 394 */ 395 fun setSecureSetting(secureSetting: String, value: Int) { 396 uiDevice.executeShellCommand("settings put --user $testUserId secure $secureSetting $value") 397 } 398 399 fun setSecureFrp(secureFrp: Boolean) { 400 uiDevice.executeShellCommand("settings " + 401 "put global secure_frp_mode ${if (secureFrp) 1 else 0}") 402 } 403 404 @After 405 fun unregisterInstallResultReceiver() { 406 try { 407 context.unregisterReceiver(receiver) 408 } catch (ignored: IllegalArgumentException) { 409 } 410 } 411 412 @After 413 @Before 414 fun uninstallTestPackage() { 415 uninstallPackage(TEST_APK_PACKAGE_NAME) 416 } 417 418 fun uninstallPackage(packageName: String) { 419 uiDevice.executeShellCommand("pm uninstall $packageName") 420 } 421 422 fun installTestPackage(extraArgs: String = "") { 423 installPackage(TEST_APK_NAME, extraArgs) 424 } 425 426 fun installPackage(apkName: String, extraArgs: String = "") { 427 Log.d(TAG, "installPackage(): apkName=$apkName, extraArgs='$extraArgs'") 428 uiDevice.executeShellCommand("pm install $extraArgs " + 429 File(TEST_APK_LOCATION, apkName).canonicalPath) 430 } 431 432 fun getDeviceProperty(name: String): String? { 433 return SystemUtil.callWithShellPermissionIdentity { 434 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name) 435 } 436 } 437 438 fun setDeviceProperty(name: String, value: String?) { 439 SystemUtil.callWithShellPermissionIdentity { 440 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name, value, 441 false /* makeDefault */) 442 } 443 } 444 } 445