1 /* <lambda>null2 * Copyright (C) 2023 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 com.android.packageinstaller.v2.model 18 19 import android.Manifest 20 import android.app.Activity 21 import android.app.AppOpsManager 22 import android.app.PendingIntent 23 import android.app.admin.DevicePolicyManager 24 import android.content.ContentResolver 25 import android.content.Context 26 import android.content.Intent 27 import android.content.pm.ApplicationInfo 28 import android.content.pm.PackageInfo 29 import android.content.pm.PackageInstaller 30 import android.content.pm.PackageInstaller.SessionInfo 31 import android.content.pm.PackageInstaller.SessionParams 32 import android.content.pm.PackageManager 33 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED 34 import android.net.Uri 35 import android.os.ParcelFileDescriptor 36 import android.os.Process 37 import android.os.UserManager 38 import android.text.TextUtils 39 import android.util.EventLog 40 import android.util.Log 41 import androidx.lifecycle.LiveData 42 import androidx.lifecycle.MutableLiveData 43 import com.android.packageinstaller.R 44 import com.android.packageinstaller.common.EventResultPersister 45 import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException 46 import com.android.packageinstaller.common.InstallEventReceiver 47 import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE 48 import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR 49 import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY 50 import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_NONE 51 import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR 52 import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE 53 import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION 54 import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_UNKNOWN_SOURCE 55 import com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery 56 import com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo 57 import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet 58 import com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo 59 import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid 60 import com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner 61 import com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested 62 import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted 63 import com.android.packageinstaller.v2.model.PackageUtil.localLogv 64 import java.io.File 65 import java.io.IOException 66 import kotlinx.coroutines.DelicateCoroutinesApi 67 import kotlinx.coroutines.Dispatchers 68 import kotlinx.coroutines.GlobalScope 69 import kotlinx.coroutines.launch 70 71 class InstallRepository(private val context: Context) { 72 73 private val packageManager: PackageManager = context.packageManager 74 private val packageInstaller: PackageInstaller = packageManager.packageInstaller 75 private val userManager: UserManager? = context.getSystemService(UserManager::class.java) 76 private val devicePolicyManager: DevicePolicyManager? = 77 context.getSystemService(DevicePolicyManager::class.java) 78 private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java) 79 private var isSessionInstall = false 80 private var isTrustedSource = false 81 private val _stagingResult = MutableLiveData<InstallStage>() 82 val stagingResult: LiveData<InstallStage> 83 get() = _stagingResult 84 private val _installResult = MutableLiveData<InstallStage>() 85 val installResult: LiveData<InstallStage> 86 get() = _installResult 87 88 /** 89 * Session ID for a session created when caller uses PackageInstaller APIs 90 */ 91 private var sessionId = SessionInfo.INVALID_ID 92 93 /** 94 * Session ID for a session created by this app 95 */ 96 var stagedSessionId = SessionInfo.INVALID_ID 97 private set 98 99 /** 100 * UID of the last caller of Pia. This can point to a 3P installer if it uses intents to install 101 * an APK, or receives a 102 * [STATUS_PENDING_USER_ACTION][PackageInstaller.STATUS_PENDING_USER_ACTION] status code. 103 * It may point to Pia, when it receives the STATUS_PENDING_USER_ACTION status code in case of 104 * an update-ownership change. 105 */ 106 private var callingUid = Process.INVALID_UID 107 108 /** 109 * UID of the origin of the installation. This UID is used to fetch the app-label of the 110 * source of the install, and also check whether the source app has the AppOp to install other 111 * apps. 112 */ 113 private var originatingUid = Process.INVALID_UID 114 private var callingPackage: String? = null 115 private var sessionStager: SessionStager? = null 116 private lateinit var intent: Intent 117 private lateinit var appOpRequestInfo: AppOpRequestInfo 118 private lateinit var appSnippet: PackageUtil.AppSnippet 119 120 /** 121 * PackageInfo of the app being installed on device. 122 */ 123 private var newPackageInfo: PackageInfo? = null 124 125 /** 126 * Extracts information from the incoming install intent, checks caller's permission to install 127 * packages, verifies that the caller is the install session owner (in case of a session based 128 * install) and checks if the current user has restrictions set that prevent app installation, 129 * 130 * @param intent the incoming [Intent] object for installing a package 131 * @param callerInfo [CallerInfo] that holds the callingUid and callingPackageName 132 * @return 133 * * [InstallAborted] if there are errors while performing the checks 134 * * [InstallStaging] after successfully performing the checks 135 */ 136 fun performPreInstallChecks(intent: Intent, callerInfo: CallerInfo): InstallStage { 137 this.intent = intent 138 139 var callingAttributionTag: String? = null 140 141 isSessionInstall = 142 PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action 143 || PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action 144 145 sessionId = if (isSessionInstall) 146 intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID) 147 else SessionInfo.INVALID_ID 148 149 stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID) 150 151 callingPackage = callerInfo.packageName 152 callingUid = callerInfo.uid 153 154 val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage) 155 if (callingUid == Process.INVALID_UID && sourceInfo == null) { 156 // Caller's identity could not be determined. Abort the install 157 Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null") 158 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 159 } 160 161 originatingUid = callingUid 162 val sessionInfo: SessionInfo? = 163 if (sessionId != SessionInfo.INVALID_ID) 164 packageInstaller.getSessionInfo(sessionId) 165 else null 166 if (sessionInfo != null) { 167 callingAttributionTag = sessionInfo.installerAttributionTag 168 if (sessionInfo.originatingUid != Process.INVALID_UID) { 169 originatingUid = sessionInfo.originatingUid 170 } 171 } 172 173 appOpRequestInfo = AppOpRequestInfo( 174 getPackageNameForUid(context, originatingUid, callingPackage), 175 originatingUid, 176 callingAttributionTag 177 ) 178 179 if (localLogv) { 180 Log.i( 181 LOG_TAG, "Intent: $intent\n" + 182 "sessionId: $sessionId\n" + 183 "staged sessionId: $stagedSessionId\n" + 184 "calling package: $callingPackage\n" + 185 "callingUid: $callingUid\n" + 186 "originatingUid: $originatingUid\n" + 187 "sourceInfo: $sourceInfo" 188 ) 189 } 190 191 if ((sessionId != SessionInfo.INVALID_ID 192 && !isCallerSessionOwner(packageInstaller, callingUid, sessionId)) 193 || (stagedSessionId != SessionInfo.INVALID_ID 194 && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId)) 195 ) { 196 Log.e( 197 LOG_TAG, "UID is not the owner of the session:\n" + 198 "CallingUid: $callingUid | SessionId: $sessionId\n" + 199 "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId" 200 ) 201 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 202 } 203 204 isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, callingUid) 205 if (!isInstallPermissionGrantedOrRequested(context, callingUid, isTrustedSource)) { 206 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 207 } 208 209 val restriction = getDevicePolicyRestrictions(isTrustedSource) 210 if (restriction != null) { 211 val adminSupportDetailsIntent = 212 devicePolicyManager!!.createAdminSupportIntent(restriction) 213 Log.e(LOG_TAG, "$restriction set in place. Cannot install.") 214 return InstallAborted( 215 ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent 216 ) 217 } 218 219 maybeRemoveInvalidInstallerPackageName(callerInfo) 220 221 return InstallStaging() 222 } 223 224 /** 225 * @return the ApplicationInfo for the installation source (the calling package), if available 226 */ 227 private fun getSourceInfo(callingPackage: String?): ApplicationInfo? { 228 return try { 229 callingPackage?.let { packageManager.getApplicationInfo(it, 0) } 230 } catch (ignored: PackageManager.NameNotFoundException) { 231 null 232 } 233 } 234 235 private fun isInstallRequestFromTrustedSource( 236 sourceInfo: ApplicationInfo?, 237 intent: Intent, 238 callingUid: Int, 239 ): Boolean { 240 val isPrivilegedAndKnown = sourceInfo != null && sourceInfo.isPrivilegedApp && 241 intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) 242 val isInstallPkgPermissionGranted = 243 isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid) 244 245 return isPrivilegedAndKnown || isInstallPkgPermissionGranted 246 } 247 248 private fun getDevicePolicyRestrictions(isTrustedSource: Boolean): String? { 249 val restrictions: Array<String> = if (isTrustedSource) { 250 arrayOf(UserManager.DISALLOW_INSTALL_APPS) 251 } else { 252 arrayOf( 253 UserManager.DISALLOW_INSTALL_APPS, 254 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, 255 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY 256 ) 257 } 258 259 for (restriction in restrictions) { 260 if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) { 261 continue 262 } 263 return restriction 264 } 265 return null 266 } 267 268 private fun maybeRemoveInvalidInstallerPackageName(callerInfo: CallerInfo) { 269 val installerPackageNameFromIntent = 270 intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME) ?: return 271 272 if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.packageName) 273 && callerInfo.packageName != null 274 && isPermissionGranted( 275 packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName 276 ) 277 ) { 278 Log.e( 279 LOG_TAG, "The given installer package name $installerPackageNameFromIntent" 280 + " is invalid. Remove it." 281 ) 282 EventLog.writeEvent( 283 0x534e4554, "236687884", callerInfo.uid, 284 "Invalid EXTRA_INSTALLER_PACKAGE_NAME" 285 ) 286 intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME) 287 } 288 } 289 290 @OptIn(DelicateCoroutinesApi::class) 291 fun stageForInstall() { 292 val uri = intent.data 293 if (stagedSessionId != SessionInfo.INVALID_ID 294 || isSessionInstall 295 || (uri != null && SCHEME_PACKAGE == uri.scheme) 296 ) { 297 // For a session based install or installing with a package:// URI, there is no file 298 // for us to stage. 299 _stagingResult.value = InstallReady() 300 return 301 } 302 if (uri != null 303 && ContentResolver.SCHEME_CONTENT == uri.scheme 304 && canPackageQuery(context, callingUid, uri) 305 ) { 306 if (stagedSessionId > 0) { 307 val info: SessionInfo? = packageInstaller.getSessionInfo(stagedSessionId) 308 if (info == null || !info.isActive || info.resolvedBaseApkPath == null) { 309 Log.w(LOG_TAG, "Session $stagedSessionId in funky state; ignoring") 310 if (info != null) { 311 cleanupStagingSession() 312 } 313 stagedSessionId = 0 314 } 315 } 316 317 // Session does not exist, or became invalid. 318 if (stagedSessionId <= 0) { 319 // Create session here to be able to show error. 320 try { 321 context.contentResolver.openAssetFileDescriptor(uri, "r").use { afd -> 322 val pfd: ParcelFileDescriptor? = afd?.parcelFileDescriptor 323 val params: SessionParams = 324 createSessionParams(originatingUid, intent, pfd, uri.toString()) 325 stagedSessionId = packageInstaller.createSession(params) 326 } 327 } catch (e: Exception) { 328 Log.e(LOG_TAG, "Failed to create a staging session", e) 329 _stagingResult.value = InstallAborted( 330 ABORT_REASON_INTERNAL_ERROR, 331 resultIntent = Intent().putExtra( 332 Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK 333 ), 334 activityResultCode = Activity.RESULT_FIRST_USER, 335 errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE 336 ) 337 return 338 } 339 } 340 341 sessionStager = SessionStager(context, uri, stagedSessionId) 342 GlobalScope.launch(Dispatchers.Main) { 343 val wasFileStaged = sessionStager!!.execute() 344 345 if (wasFileStaged) { 346 _stagingResult.value = InstallReady() 347 } else { 348 cleanupStagingSession() 349 Log.e(LOG_TAG, "Could not stage APK.") 350 _stagingResult.value = InstallAborted( 351 ABORT_REASON_INTERNAL_ERROR, 352 resultIntent = Intent().putExtra( 353 Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK 354 ), 355 activityResultCode = Activity.RESULT_FIRST_USER 356 ) 357 } 358 } 359 } else { 360 Log.e(LOG_TAG, "Invalid URI: ${if (uri == null) "null" else uri.scheme}") 361 _stagingResult.value = InstallAborted( 362 ABORT_REASON_INTERNAL_ERROR, 363 resultIntent = Intent().putExtra( 364 Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_URI 365 ), 366 activityResultCode = Activity.RESULT_FIRST_USER 367 ) 368 } 369 } 370 371 private fun cleanupStagingSession() { 372 if (stagedSessionId > 0) { 373 try { 374 packageInstaller.abandonSession(stagedSessionId) 375 } catch (ignored: SecurityException) { 376 } 377 stagedSessionId = 0 378 } 379 } 380 381 private fun createSessionParams( 382 originatingUid: Int, 383 intent: Intent, 384 pfd: ParcelFileDescriptor?, 385 debugPathName: String, 386 ): SessionParams { 387 val params = SessionParams(SessionParams.MODE_FULL_INSTALL) 388 val referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java) 389 params.setPackageSource( 390 if (referrerUri != null) 391 PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE 392 else PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE 393 ) 394 params.setInstallAsInstantApp(false) 395 params.setReferrerUri(referrerUri) 396 params.setOriginatingUri( 397 intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri::class.java) 398 ) 399 params.setOriginatingUid(originatingUid) 400 params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME)) 401 params.setInstallReason(PackageManager.INSTALL_REASON_USER) 402 // Disable full screen intent usage by for sideloads. 403 params.setPermissionState( 404 Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED 405 ) 406 if (pfd != null) { 407 try { 408 val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0) 409 params.setAppPackageName(installInfo.packageName) 410 params.setInstallLocation(installInfo.installLocation) 411 params.setSize(installInfo.calculateInstalledSize(params, pfd)) 412 } catch (e: PackageInstaller.PackageParsingException) { 413 Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e) 414 params.setSize(pfd.statSize) 415 } catch (e: IOException) { 416 Log.e( 417 LOG_TAG, "Cannot calculate installed size $debugPathName. " + 418 "Try only apk size.", e 419 ) 420 } 421 } else { 422 Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.") 423 } 424 return params 425 } 426 427 /** 428 * Processes Install session, file:// or package:// URI to generate data pertaining to user 429 * confirmation for an install. This method also checks if the source app has the AppOp granted 430 * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to 431 * be reused once appOp has been granted 432 * 433 * @return 434 * * [InstallAborted] 435 * * If install session is invalid (not sealed or resolvedBaseApk path is invalid) 436 * * Source app doesn't have visibility to target app 437 * * The APK is invalid 438 * * URI is invalid 439 * * Can't get ApplicationInfo for source app, to request AppOp 440 * 441 * * [InstallUserActionRequired] 442 * * If AppOP is granted and user action is required to proceed with install 443 * * If AppOp grant is to be requested from the user 444 */ 445 fun requestUserConfirmation(): InstallStage? { 446 return if (isTrustedSource) { 447 if (localLogv) { 448 Log.i(LOG_TAG, "Install allowed") 449 } 450 maybeDeferUserConfirmation() 451 } else { 452 val unknownSourceStage = handleUnknownSources(appOpRequestInfo) 453 if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) { 454 // Source app already has appOp granted. 455 maybeDeferUserConfirmation() 456 } else { 457 unknownSourceStage 458 } 459 } 460 } 461 462 /** 463 * If the update-owner for the incoming app is being changed, defer confirming with the 464 * user and directly proceed with the install. The system will request another 465 * user confirmation shortly. 466 */ 467 private fun maybeDeferUserConfirmation(): InstallStage? { 468 // Returns InstallUserActionRequired stage if install details could be successfully 469 // computed, else it returns InstallAborted. 470 val confirmationSnippet: InstallStage = generateConfirmationSnippet() 471 if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) { 472 return confirmationSnippet 473 } 474 475 val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!) 476 return if (sessionId == SessionInfo.INVALID_ID && 477 !TextUtils.isEmpty(existingUpdateOwner) && 478 !TextUtils.equals(existingUpdateOwner, callingPackage) 479 ) { 480 // Since update ownership is being changed, the system will request another 481 // user confirmation shortly. Thus, we don't need to ask the user to confirm 482 // installation here. 483 initiateInstall() 484 null 485 } else { 486 confirmationSnippet 487 } 488 } 489 490 private fun generateConfirmationSnippet(): InstallStage { 491 val packageSource: Any? 492 val pendingUserActionReason: Int 493 494 if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) { 495 val info = packageInstaller.getSessionInfo(sessionId) 496 val resolvedPath = info?.resolvedBaseApkPath 497 if (info == null || !info.isSealed || resolvedPath == null) { 498 Log.e(LOG_TAG, "Session $sessionId in funky state; ignoring") 499 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 500 } 501 packageSource = Uri.fromFile(File(resolvedPath)) 502 // TODO: Not sure where is this used yet. PIA.java passes it to 503 // InstallInstalling if not null 504 // mOriginatingURI = null; 505 // mReferrerURI = null; 506 pendingUserActionReason = info.getPendingUserActionReason() 507 } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action) { 508 val info = packageInstaller.getSessionInfo(sessionId) 509 if (info == null || !info.isPreApprovalRequested) { 510 Log.e(LOG_TAG, "Session $sessionId in funky state; ignoring") 511 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 512 } 513 packageSource = info 514 // mOriginatingURI = null; 515 // mReferrerURI = null; 516 pendingUserActionReason = info.getPendingUserActionReason() 517 } else { 518 // Two possible origins: 519 // 1. Installation with SCHEME_PACKAGE. 520 // 2. Installation with "file://" for session created by this app 521 packageSource = 522 if (intent.data?.scheme == SCHEME_PACKAGE) { 523 intent.data 524 } else { 525 val stagedSessionInfo = packageInstaller.getSessionInfo(stagedSessionId) 526 Uri.fromFile(File(stagedSessionInfo?.resolvedBaseApkPath!!)) 527 } 528 // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); 529 // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER); 530 pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE 531 } 532 533 // if there's nothing to do, quietly slip into the ether 534 if (packageSource == null) { 535 Log.e(LOG_TAG, "Unspecified source") 536 return InstallAborted( 537 ABORT_REASON_INTERNAL_ERROR, 538 resultIntent = Intent().putExtra( 539 Intent.EXTRA_INSTALL_RESULT, 540 PackageManager.INSTALL_FAILED_INVALID_URI 541 ), 542 activityResultCode = Activity.RESULT_FIRST_USER 543 ) 544 } 545 return processAppSnippet(packageSource, pendingUserActionReason) 546 } 547 548 /** 549 * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install 550 * session) to set up the installer for this install. 551 * 552 * @param source The source of package URI or SessionInfo 553 * @return 554 * * [InstallUserActionRequired] if source could be processed 555 * * [InstallAborted] if source is invalid or there was an error is processing a source 556 */ 557 private fun processAppSnippet(source: Any, userActionReason: Int): InstallStage { 558 return when (source) { 559 is Uri -> processPackageUri(source, userActionReason) 560 is SessionInfo -> processSessionInfo(source, userActionReason) 561 else -> InstallAborted(ABORT_REASON_INTERNAL_ERROR) 562 } 563 } 564 565 /** 566 * Parse the Uri and set up the installer for this package. 567 * 568 * @param packageUri The URI to parse 569 * @return 570 * * [InstallUserActionRequired] if source could be processed 571 * * [InstallAborted] if source is invalid or there was an error is processing a source 572 */ 573 private fun processPackageUri(packageUri: Uri, userActionReason: Int): InstallStage { 574 val scheme = packageUri.scheme 575 val packageName = packageUri.schemeSpecificPart 576 if (scheme == null) { 577 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 578 } 579 if (localLogv) { 580 Log.i(LOG_TAG, "processPackageUri(): uri = $packageUri, scheme = $scheme") 581 } 582 when (scheme) { 583 SCHEME_PACKAGE -> { 584 for (handle in userManager!!.getUserHandles(true)) { 585 val pmForUser = context.createContextAsUser(handle, 0).packageManager 586 try { 587 if (pmForUser.canPackageQuery(callingPackage!!, packageName)) { 588 newPackageInfo = pmForUser.getPackageInfo( 589 packageName, 590 PackageManager.GET_PERMISSIONS 591 or PackageManager.MATCH_UNINSTALLED_PACKAGES 592 ) 593 } 594 } catch (ignored: PackageManager.NameNotFoundException) { 595 } 596 } 597 if (newPackageInfo == null) { 598 Log.e( 599 LOG_TAG, "Requested package " + packageUri.schemeSpecificPart 600 + " not available. Discontinuing installation" 601 ) 602 return InstallAborted( 603 ABORT_REASON_INTERNAL_ERROR, 604 errorDialogType = DLG_PACKAGE_ERROR, 605 resultIntent = Intent().putExtra( 606 Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK 607 ), 608 activityResultCode = Activity.RESULT_FIRST_USER 609 ) 610 } 611 appSnippet = getAppSnippet(context, newPackageInfo!!) 612 if (localLogv) { 613 Log.i(LOG_TAG, "Created snippet for " + appSnippet.label) 614 } 615 } 616 617 ContentResolver.SCHEME_FILE -> { 618 val sourceFile = packageUri.path?.let { File(it) } 619 newPackageInfo = sourceFile?.let { 620 getPackageInfo(context, it, PackageManager.GET_PERMISSIONS) 621 } 622 623 // Check for parse errors 624 if (newPackageInfo == null) { 625 Log.w( 626 LOG_TAG, "Parse error when parsing manifest. " + 627 "Discontinuing installation" 628 ) 629 return InstallAborted( 630 ABORT_REASON_INTERNAL_ERROR, 631 errorDialogType = DLG_PACKAGE_ERROR, 632 resultIntent = Intent().putExtra( 633 Intent.EXTRA_INSTALL_RESULT, 634 PackageManager.INSTALL_FAILED_INVALID_APK 635 ), 636 activityResultCode = Activity.RESULT_FIRST_USER 637 ) 638 } 639 if (localLogv) { 640 Log.i(LOG_TAG, "Creating snippet for local file $sourceFile") 641 } 642 appSnippet = getAppSnippet(context, newPackageInfo!!, sourceFile!!) 643 } 644 645 else -> { 646 Log.e(LOG_TAG, "Unexpected URI scheme $packageUri") 647 return InstallAborted(ABORT_REASON_INTERNAL_ERROR) 648 } 649 } 650 return InstallUserActionRequired( 651 USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!), 652 getUpdateMessage(newPackageInfo!!, userActionReason) 653 ) 654 } 655 656 /** 657 * Use the SessionInfo and set up the installer for pre-commit install session. 658 * 659 * @param sessionInfo The SessionInfo to compose 660 * @return [InstallUserActionRequired] 661 */ 662 private fun processSessionInfo(sessionInfo: SessionInfo, userActionReason: Int): InstallStage { 663 newPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName()) 664 appSnippet = getAppSnippet(context, sessionInfo) 665 666 return InstallUserActionRequired( 667 USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!), 668 getUpdateMessage(newPackageInfo!!, userActionReason) 669 670 ) 671 } 672 673 private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? { 674 if (isAppUpdating(pkgInfo)) { 675 val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo) 676 677 val originatingPackageName = 678 getPackageNameForUid(context, originatingUid, callingPackage) 679 val requestedUpdateOwnerLabel = getApplicationLabel(originatingPackageName) 680 681 if (!TextUtils.isEmpty(existingUpdateOwnerLabel) 682 && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP 683 ) { 684 return context.getString( 685 R.string.install_confirm_question_update_owner_reminder, 686 requestedUpdateOwnerLabel, existingUpdateOwnerLabel 687 ) 688 } 689 } 690 return null 691 } 692 693 private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? { 694 return getApplicationLabel(getExistingUpdateOwner(pkgInfo)) 695 } 696 697 private fun getExistingUpdateOwner(pkgInfo: PackageInfo): String? { 698 return try { 699 val packageName = pkgInfo.packageName 700 val sourceInfo = packageManager.getInstallSourceInfo(packageName) 701 sourceInfo.updateOwnerPackageName 702 } catch (e: PackageManager.NameNotFoundException) { 703 null 704 } 705 } 706 707 private fun getApplicationLabel(packageName: String?): CharSequence? { 708 return try { 709 val appInfo = packageName?.let { 710 packageManager.getApplicationInfo( 711 it, PackageManager.ApplicationInfoFlags.of(0) 712 ) 713 } 714 appInfo?.let { packageManager.getApplicationLabel(it) } 715 } catch (e: PackageManager.NameNotFoundException) { 716 null 717 } 718 } 719 720 private fun isAppUpdating(newPkgInfo: PackageInfo): Boolean { 721 var pkgName = newPkgInfo.packageName 722 // Check if there is already a package on the device with this name 723 // but it has been renamed to something else. 724 val oldName = packageManager.canonicalToCurrentPackageNames(arrayOf(pkgName)) 725 if (oldName != null && oldName.isNotEmpty() && oldName[0] != null) { 726 pkgName = oldName[0] 727 newPkgInfo.packageName = pkgName 728 newPkgInfo.applicationInfo?.packageName = pkgName 729 } 730 731 // Check if package is already installed. display confirmation dialog if replacing pkg 732 try { 733 // This is a little convoluted because we want to get all uninstalled 734 // apps, but this may include apps with just data, and if it is just 735 // data we still want to count it as "installed". 736 val appInfo = packageManager.getApplicationInfo( 737 pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES 738 ) 739 // If the package is archived, treat it as an update case. 740 if (!appInfo.isArchived && appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) { 741 return false 742 } 743 } catch (e: PackageManager.NameNotFoundException) { 744 return false 745 } 746 return true 747 } 748 749 /** 750 * Once the user returns from Settings related to installing from unknown sources, reattempt 751 * the installation if the source app is granted permission to install other apps. Abort the 752 * installation if the source app is still not granted installing permission. 753 * 754 * @return 755 * * [InstallUserActionRequired] containing data required to ask user confirmation 756 * to proceed with the install. 757 * * [InstallAborted] if there was an error while recomputing, or the source still 758 * doesn't have install permission. 759 */ 760 fun reattemptInstall(): InstallStage { 761 val unknownSourceStage = handleUnknownSources(appOpRequestInfo) 762 return when (unknownSourceStage.stageCode) { 763 InstallStage.STAGE_READY -> { 764 // Source app now has appOp granted. 765 generateConfirmationSnippet() 766 } 767 768 InstallStage.STAGE_ABORTED -> { 769 // There was some error in determining the AppOp code for the source app. 770 // Abort installation 771 unknownSourceStage 772 } 773 774 else -> { 775 // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was 776 // unexpected while reattempting the install. Let's abort it. 777 Log.e(LOG_TAG, "AppOp still not granted.") 778 InstallAborted(ABORT_REASON_INTERNAL_ERROR) 779 } 780 } 781 } 782 783 private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage { 784 if (requestInfo.originatingPackage == null) { 785 Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName) 786 return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE) 787 } 788 // Shouldn't use static constant directly, see b/65534401. 789 val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES) 790 val appOpMode = appOpsManager!!.noteOpNoThrow( 791 appOpStr!!, requestInfo.originatingUid, requestInfo.originatingPackage, 792 requestInfo.attributionTag, "Started package installation activity" 793 ) 794 if (localLogv) { 795 Log.i(LOG_TAG, "handleUnknownSources(): appMode=$appOpMode") 796 } 797 798 return when (appOpMode) { 799 AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> { 800 if (appOpMode == AppOpsManager.MODE_DEFAULT) { 801 appOpsManager.setMode( 802 appOpStr, requestInfo.originatingUid, requestInfo.originatingPackage, 803 AppOpsManager.MODE_ERRORED 804 ) 805 } 806 try { 807 val sourceInfo = 808 packageManager.getApplicationInfo(requestInfo.originatingPackage, 0) 809 val sourceAppSnippet = getAppSnippet(context, sourceInfo) 810 InstallUserActionRequired( 811 USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet, 812 sourceApp = requestInfo.originatingPackage 813 ) 814 } catch (e: PackageManager.NameNotFoundException) { 815 Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.originatingPackage) 816 InstallAborted(ABORT_REASON_INTERNAL_ERROR) 817 } 818 } 819 820 AppOpsManager.MODE_ALLOWED -> InstallReady() 821 822 else -> { 823 Log.e( 824 LOG_TAG, "Invalid app op mode $appOpMode for " + 825 "OP_REQUEST_INSTALL_PACKAGES found for uid $requestInfo.originatingUid" 826 ) 827 InstallAborted(ABORT_REASON_INTERNAL_ERROR) 828 } 829 } 830 } 831 832 /** 833 * Kick off the installation. Register a broadcast listener to get the result of the 834 * installation and commit the staged session here. If the installation was session based, 835 * signal the PackageInstaller that the user has granted permission to proceed with the install 836 */ 837 fun initiateInstall() { 838 if (sessionId > 0) { 839 packageInstaller.setPermissionsResult(sessionId, true) 840 if (localLogv) { 841 Log.i(LOG_TAG, "Install permission granted for session $sessionId") 842 } 843 _installResult.value = InstallAborted( 844 ABORT_REASON_DONE, activityResultCode = Activity.RESULT_OK 845 ) 846 return 847 } 848 val uri = intent.data 849 if (SCHEME_PACKAGE == uri?.scheme) { 850 try { 851 packageManager.installExistingPackage( 852 newPackageInfo!!.packageName, PackageManager.INSTALL_REASON_USER 853 ) 854 setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null) 855 } catch (e: PackageManager.NameNotFoundException) { 856 setStageBasedOnResult( 857 PackageInstaller.STATUS_FAILURE, 858 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, 859 null 860 ) 861 } 862 return 863 } 864 if (stagedSessionId <= 0) { 865 // How did we even land here? 866 Log.e(LOG_TAG, "Invalid local session and caller initiated session") 867 _installResult.value = InstallAborted(ABORT_REASON_INTERNAL_ERROR) 868 return 869 } 870 val installId: Int 871 try { 872 _installResult.value = InstallInstalling(appSnippet) 873 installId = InstallEventReceiver.addObserver( 874 context, EventResultPersister.GENERATE_NEW_ID 875 ) { statusCode: Int, legacyStatus: Int, message: String?, serviceId: Int -> 876 setStageBasedOnResult(statusCode, legacyStatus, message) 877 } 878 } catch (e: OutOfIdsException) { 879 setStageBasedOnResult( 880 PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null 881 ) 882 return 883 } 884 val broadcastIntent = Intent(BROADCAST_ACTION) 885 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND) 886 broadcastIntent.setPackage(context.packageName) 887 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId) 888 val pendingIntent = PendingIntent.getBroadcast( 889 context, installId, broadcastIntent, 890 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE 891 ) 892 try { 893 val session = packageInstaller.openSession(stagedSessionId) 894 session.commit(pendingIntent.intentSender) 895 } catch (e: Exception) { 896 Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e) 897 packageInstaller.abandonSession(stagedSessionId) 898 setStageBasedOnResult( 899 PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null 900 ) 901 } 902 } 903 904 private fun setStageBasedOnResult( 905 statusCode: Int, 906 legacyStatus: Int, 907 message: String?, 908 ) { 909 if (localLogv) { 910 Log.i( 911 LOG_TAG, "Status code: $statusCode\n" + 912 "legacy status: $legacyStatus\n" + 913 "message: $message" 914 ) 915 } 916 917 val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false) 918 919 if (statusCode == PackageInstaller.STATUS_SUCCESS) { 920 val resultIntent = if (shouldReturnResult) { 921 Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED) 922 } else { 923 val intent = packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName) 924 if (isLauncherActivityEnabled(intent)) intent else null 925 } 926 _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent)) 927 } else { 928 // TODO (b/346655018): Use INSTALL_FAILED_ABORTED legacyCode in the condition 929 // statusCode can be STATUS_FAILURE_ABORTED if: 930 // 1. GPP blocks an install. 931 // 2. User denies ownership update explicitly. 932 // InstallFailed dialog must not be shown only when the user denies ownership update. We 933 // must show this dialog for all other install failures. 934 935 val userDenied = 936 statusCode == PackageInstaller.STATUS_FAILURE_ABORTED && 937 legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT && 938 legacyStatus != PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE 939 940 if (shouldReturnResult) { 941 val resultIntent = Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus) 942 _installResult.setValue( 943 InstallFailed( 944 legacyCode = legacyStatus, 945 statusCode = statusCode, 946 shouldReturnResult = true, 947 resultIntent = resultIntent 948 ) 949 ) 950 } else if (userDenied) { 951 _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR)) 952 } else { 953 _installResult.setValue( 954 InstallFailed(appSnippet, legacyStatus, statusCode, message) 955 ) 956 } 957 } 958 } 959 960 private fun isLauncherActivityEnabled(intent: Intent?): Boolean { 961 if (intent == null || intent.component == null) { 962 return false 963 } 964 return (intent.component?.let { packageManager.getComponentEnabledSetting(it) } 965 != COMPONENT_ENABLED_STATE_DISABLED) 966 } 967 968 /** 969 * Cleanup the staged session. Also signal the packageinstaller that an install session is to 970 * be aborted 971 */ 972 fun cleanupInstall() { 973 if (sessionId > 0) { 974 packageInstaller.setPermissionsResult(sessionId, false) 975 } else if (stagedSessionId > 0) { 976 cleanupStagingSession() 977 } 978 } 979 980 /** 981 * When the identity of the install source could not be determined, user can skip checking the 982 * source and directly proceed with the install. 983 */ 984 fun forcedSkipSourceCheck(): InstallStage? { 985 return maybeDeferUserConfirmation() 986 } 987 988 val stagingProgress: LiveData<Int> 989 get() = sessionStager?.progress ?: MutableLiveData(0) 990 991 companion object { 992 const val EXTRA_STAGED_SESSION_ID = "com.android.packageinstaller.extra.STAGED_SESSION_ID" 993 const val SCHEME_PACKAGE = "package" 994 const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT" 995 private val LOG_TAG = InstallRepository::class.java.simpleName 996 } 997 998 data class CallerInfo(val packageName: String?, val uid: Int) 999 data class AppOpRequestInfo( 1000 val originatingPackage: String?, 1001 val originatingUid: Int, 1002 val attributionTag: String?, 1003 ) 1004 } 1005