• Home
  • History
  • Annotate
  • 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     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