• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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