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