1 /*
2  * 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  *      https://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.Notification
23 import android.app.NotificationChannel
24 import android.app.NotificationManager
25 import android.app.PendingIntent
26 import android.app.admin.DevicePolicyManager
27 import android.app.usage.StorageStatsManager
28 import android.content.ComponentName
29 import android.content.Context
30 import android.content.Intent
31 import android.content.pm.ActivityInfo
32 import android.content.pm.ApplicationInfo
33 import android.content.pm.PackageInstaller
34 import android.content.pm.PackageManager
35 import android.content.pm.VersionedPackage
36 import android.graphics.drawable.Icon
37 import android.os.Build
38 import android.os.Bundle
39 import android.os.Flags
40 import android.os.Process
41 import android.os.UserHandle
42 import android.os.UserManager
43 import android.provider.Settings
44 import android.util.Log
45 import androidx.lifecycle.MutableLiveData
46 import com.android.packageinstaller.R
47 import com.android.packageinstaller.common.EventResultPersister
48 import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
49 import com.android.packageinstaller.common.UninstallEventReceiver
50 import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid
51 import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
52 import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
53 import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame
54 
55 class UninstallRepository(private val context: Context) {
56 
57     private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
58     private val packageManager: PackageManager = context.packageManager
59     private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
60     private val notificationManager: NotificationManager? =
61         context.getSystemService(NotificationManager::class.java)
62     val uninstallResult = MutableLiveData<UninstallStage?>()
63     private var uninstalledUser: UserHandle? = null
64     private var callback: PackageManager.UninstallCompleteCallback? = null
65     private var targetAppInfo: ApplicationInfo? = null
66     private var targetActivityInfo: ActivityInfo? = null
67     private lateinit var intent: Intent
68     private lateinit var targetAppLabel: CharSequence
69     private var targetPackageName: String? = null
70     private var callingActivity: String? = null
71     private var uninstallFromAllUsers = false
72     private var isClonedApp = false
73     private var uninstallId = 0
74 
performPreUninstallChecksnull75     fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage {
76         this.intent = intent
77 
78         val callingUid = callerInfo.uid
79         callingActivity = callerInfo.activityName
80 
81         if (callingUid == Process.INVALID_UID) {
82             Log.e(LOG_TAG, "Could not determine the launching uid.")
83             return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
84             // TODO: should we give any indication to the user?
85         }
86 
87         val callingPackage = getPackageNameForUid(context, callingUid, null)
88         if (callingPackage == null) {
89             Log.e(LOG_TAG, "Package not found for originating uid $callingUid")
90             return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
91         } else {
92             if (appOpsManager!!.noteOpNoThrow(
93                     AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage
94                 ) != AppOpsManager.MODE_ALLOWED
95             ) {
96                 Log.e(LOG_TAG, "Install from uid $callingUid disallowed by AppOps")
97                 return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
98             }
99         }
100 
101         if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P &&
102             !isPermissionGranted(
103                 context, Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid
104             ) &&
105             !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
106         ) {
107             Log.e(
108                 LOG_TAG,
109                 "Uid " + callingUid + " does not have " +
110                     Manifest.permission.REQUEST_DELETE_PACKAGES + " or " +
111                     Manifest.permission.DELETE_PACKAGES
112             )
113             return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
114         }
115 
116         // Get intent information.
117         // We expect an intent with URI of the form package:<packageName>#<className>
118         // className is optional; if specified, it is the activity the user chose to uninstall
119         val packageUri = intent.data
120         if (packageUri == null) {
121             Log.e(LOG_TAG, "No package URI in intent")
122             return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
123         }
124         targetPackageName = packageUri.encodedSchemeSpecificPart
125         if (targetPackageName == null) {
126             Log.e(LOG_TAG, "Invalid package name in URI: $packageUri")
127             return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
128         }
129 
130         uninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false)
131         if (uninstallFromAllUsers && !userManager!!.isAdminUser) {
132             Log.e(LOG_TAG, "Only admin user can request uninstall for all users")
133             return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
134         }
135 
136         uninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
137         if (uninstalledUser == null) {
138             uninstalledUser = Process.myUserHandle()
139         } else {
140             val profiles = userManager!!.userProfiles
141             if (!profiles.contains(uninstalledUser)) {
142                 Log.e(
143                     LOG_TAG,
144                     "User " + Process.myUserHandle() + " can't request uninstall " +
145                         "for user " + uninstalledUser
146                 )
147                 return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
148             }
149         }
150 
151         callback = intent.getParcelableExtra(
152             PackageInstaller.EXTRA_CALLBACK, PackageManager.UninstallCompleteCallback::class.java
153         )
154 
155         try {
156             targetAppInfo = packageManager.getApplicationInfo(
157                 targetPackageName!!,
158                 PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong())
159             )
160         } catch (e: PackageManager.NameNotFoundException) {
161             Log.e(LOG_TAG, "Unable to get packageName")
162         }
163 
164         if (targetAppInfo == null) {
165             Log.e(LOG_TAG, "Invalid packageName: $targetPackageName")
166             return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
167         }
168 
169         // The class name may have been specified (e.g. when deleting an app from all apps)
170         val className = packageUri.fragment
171         if (className != null) {
172             try {
173                 targetActivityInfo = packageManager.getActivityInfo(
174                     ComponentName(targetPackageName!!, className),
175                     PackageManager.ComponentInfoFlags.of(0)
176                 )
177             } catch (e: PackageManager.NameNotFoundException) {
178                 Log.e(LOG_TAG, "Unable to get className")
179                 // Continue as the ActivityInfo isn't critical.
180             }
181         }
182 
183         return UninstallReady()
184     }
185 
generateUninstallDetailsnull186     fun generateUninstallDetails(): UninstallStage {
187         val messageBuilder = StringBuilder()
188 
189         targetAppLabel = targetAppInfo!!.loadSafeLabel(packageManager)
190 
191         // If the Activity label differs from the App label, then make sure the user
192         // knows the Activity belongs to the App being uninstalled.
193         if (targetActivityInfo != null) {
194             val activityLabel = targetActivityInfo!!.loadSafeLabel(packageManager)
195             if (!activityLabel.contentEquals(targetAppLabel)) {
196                 messageBuilder.append(
197                     context.getString(R.string.uninstall_activity_text, activityLabel)
198                 )
199                 messageBuilder.append(" ").append(targetAppLabel).append(".\n\n")
200             }
201         }
202 
203         val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
204         val myUserHandle = Process.myUserHandle()
205         val isSingleUser = isSingleUser()
206 
207         if (isUpdate) {
208             messageBuilder.append(
209                 context.getString(
210                     if (isSingleUser) {
211                         R.string.uninstall_update_text
212                     } else {
213                         R.string.uninstall_update_text_multiuser
214                     }
215                 )
216             )
217         } else if (uninstallFromAllUsers && !isSingleUser) {
218             messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users))
219         } else if (uninstalledUser != myUserHandle) {
220             // Uninstalling user is issuing uninstall for another user
221             val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
222                 .getSystemService(UserManager::class.java)
223             val userName = customUserManager!!.userName
224             var messageString = context.getString(
225                     R.string.uninstall_application_text_user,
226                 userName
227             )
228             if (userManager!!.isSameProfileGroup(myUserHandle, uninstalledUser!!)) {
229                 if (customUserManager.isManagedProfile) {
230                     messageString = context.getString(
231                             R.string.uninstall_application_text_current_user_work_profile, userName
232                     )
233                 } else if (customUserManager.isCloneProfile){
234                     isClonedApp = true
235                     messageString = context.getString(
236                             R.string.uninstall_application_text_current_user_clone_profile
237                     )
238                 } else if (Flags.allowPrivateProfile()
239                         && android.multiuser.Flags.enablePrivateSpaceFeatures()
240                         && customUserManager.isPrivateProfile
241                 ) {
242                     // TODO(b/324244123): Get these Strings from a User Property API.
243                     messageString = context.getString(
244                             R.string.uninstall_application_text_current_user_private_profile
245                     )
246                 }
247             }
248             messageBuilder.append(messageString)
249         } else if (isCloneProfile(uninstalledUser!!)) {
250             isClonedApp = true
251             messageBuilder.append(
252                 context.getString(
253                     R.string.uninstall_application_text_current_user_clone_profile
254                 )
255             )
256         } else if (myUserHandle == UserHandle.SYSTEM &&
257             hasClonedInstance(targetAppInfo!!.packageName)
258         ) {
259             messageBuilder.append(
260                 context.getString(
261                     R.string.uninstall_application_text_with_clone_instance,
262                     targetAppLabel
263                 )
264             )
265         } else {
266             messageBuilder.append(context.getString(R.string.uninstall_application_text))
267         }
268 
269         val message = messageBuilder.toString()
270 
271         val title = if (isClonedApp) {
272             context.getString(R.string.cloned_app_label, targetAppLabel)
273         } else {
274             targetAppLabel.toString()
275         }
276 
277         var suggestToKeepAppData = false
278         try {
279             val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0)
280             suggestToKeepAppData =
281                 pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData()
282         } catch (e: PackageManager.NameNotFoundException) {
283             Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e)
284         }
285 
286         var appDataSize: Long = 0
287         if (suggestToKeepAppData) {
288             appDataSize = getAppDataSize(
289                 targetPackageName!!,
290                 if (uninstallFromAllUsers) null else uninstalledUser
291             )
292         }
293 
294         return UninstallUserActionRequired(title, message, appDataSize)
295     }
296 
297     /**
298      * Returns whether there is only one "full" user on this device.
299      *
300      * **Note:** On devices that use [headless system user mode]
301      * [android.os.UserManager.isHeadlessSystemUserMode], the system user is not "full",
302      * so it's not be considered in the calculation.
303      */
isSingleUsernull304     private fun isSingleUser(): Boolean {
305         val userCount = userManager!!.userCount
306         return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2)
307     }
308 
hasClonedInstancenull309     private fun hasClonedInstance(packageName: String): Boolean {
310         // Check if clone user is present on the device.
311         var cloneUser: UserHandle? = null
312         val profiles = userManager!!.userProfiles
313 
314         for (userHandle in profiles) {
315             if (userHandle != UserHandle.SYSTEM && isCloneProfile(userHandle)) {
316                 cloneUser = userHandle
317                 break
318             }
319         }
320         // Check if another instance of given package exists in clone user profile.
321         return try {
322             cloneUser != null &&
323                 packageManager.getPackageUidAsUser(
324                 packageName, PackageManager.PackageInfoFlags.of(0), cloneUser.identifier
325                 ) > 0
326         } catch (e: PackageManager.NameNotFoundException) {
327             false
328         }
329     }
330 
isCloneProfilenull331     private fun isCloneProfile(userHandle: UserHandle): Boolean {
332         val customUserManager = context.createContextAsUser(userHandle, 0)
333             .getSystemService(UserManager::class.java)
334         return customUserManager!!.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)
335     }
336 
337     /**
338      * Get number of bytes of the app data of the package.
339      *
340      * @param pkg The package that might have app data.
341      * @param user The user the package belongs to or `null` if files of all users should
342      * be counted.
343      * @return The number of bytes.
344      */
getAppDataSizenull345     private fun getAppDataSize(pkg: String, user: UserHandle?): Long {
346         if (user != null) {
347             return getAppDataSizeForUser(pkg, user)
348         }
349         // We are uninstalling from all users. Get cumulative app data size for all users.
350         val userHandles = userManager!!.getUserHandles(true)
351         var totalAppDataSize: Long = 0
352         val numUsers = userHandles.size
353         for (i in 0 until numUsers) {
354             totalAppDataSize += getAppDataSizeForUser(pkg, userHandles[i])
355         }
356         return totalAppDataSize
357     }
358 
359     /**
360      * Get number of bytes of the app data of the package.
361      *
362      * @param pkg The package that might have app data.
363      * @param user The user the package belongs to
364      * @return The number of bytes.
365      */
getAppDataSizeForUsernull366     private fun getAppDataSizeForUser(pkg: String, user: UserHandle): Long {
367         val storageStatsManager = context.getSystemService(StorageStatsManager::class.java)
368         try {
369             val stats = storageStatsManager!!.queryStatsForPackage(
370                 packageManager.getApplicationInfo(pkg, 0).storageUuid,
371                 pkg,
372                 user
373             )
374             return stats.getDataBytes()
375         } catch (e: Exception) {
376             Log.e(LOG_TAG, "Cannot determine amount of app data for $pkg", e)
377         }
378         return 0
379     }
380 
initiateUninstallnull381     fun initiateUninstall(keepData: Boolean) {
382         // Get an uninstallId to track results and show a notification on non-TV devices.
383         uninstallId = try {
384             UninstallEventReceiver.addObserver(
385                 context, EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult
386             )
387         } catch (e: OutOfIdsException) {
388             Log.e(LOG_TAG, "Failed to start uninstall", e)
389             handleUninstallResult(
390                 PackageInstaller.STATUS_FAILURE,
391                 PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
392             )
393             return
394         }
395 
396         // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
397         uninstallResult.value = UninstallUninstalling(targetAppLabel, isClonedApp)
398 
399         val uninstallData = Bundle()
400         uninstallData.putInt(EXTRA_UNINSTALL_ID, uninstallId)
401         uninstallData.putString(EXTRA_PACKAGE_NAME, targetPackageName)
402         uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, uninstallFromAllUsers)
403         uninstallData.putCharSequence(EXTRA_APP_LABEL, targetAppLabel)
404         uninstallData.putBoolean(EXTRA_IS_CLONE_APP, isClonedApp)
405         uninstallData.putInt(EXTRA_TARGET_USER_ID, uninstalledUser!!.identifier)
406         Log.i(LOG_TAG, "Uninstalling extras = $uninstallData")
407 
408         // Get a PendingIntent for result broadcast and issue an uninstall request
409         val broadcastIntent = Intent(BROADCAST_ACTION)
410         broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
411         broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId)
412         broadcastIntent.setPackage(context.packageName)
413         val pendingIntent = PendingIntent.getBroadcast(
414             context,
415             uninstallId,
416             broadcastIntent,
417             PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
418         )
419         if (!startUninstall(
420                 targetPackageName!!,
421                 uninstalledUser!!,
422                 pendingIntent,
423                 uninstallFromAllUsers,
424                 keepData
425             )
426         ) {
427             handleUninstallResult(
428                 PackageInstaller.STATUS_FAILURE,
429                 PackageManager.DELETE_FAILED_INTERNAL_ERROR,
430                 null,
431                 0
432             )
433         }
434     }
435 
handleUninstallResultnull436     private fun handleUninstallResult(
437         status: Int,
438         legacyStatus: Int,
439         message: String?,
440         serviceId: Int
441     ) {
442         if (callback != null) {
443             // The caller will be informed about the result via a callback
444             callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message)
445 
446             // Since the caller already received the results, just finish the app at this point
447             uninstallResult.value = null
448             return
449         }
450         val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
451         if (returnResult || callingActivity != null) {
452             val intent = Intent()
453             intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
454             if (status == PackageInstaller.STATUS_SUCCESS) {
455                 uninstallResult.setValue(
456                     UninstallSuccess(resultIntent = intent, activityResultCode = Activity.RESULT_OK)
457                 )
458             } else {
459                 uninstallResult.setValue(
460                     UninstallFailed(
461                         returnResult = true,
462                         resultIntent = intent,
463                         activityResultCode = Activity.RESULT_FIRST_USER
464                     )
465                 )
466             }
467             return
468         }
469 
470         // Caller did not want the result back. So, we either show a Toast, or a Notification.
471         if (status == PackageInstaller.STATUS_SUCCESS) {
472             val statusMessage = if (isClonedApp) {
473                 context.getString(
474                 R.string.uninstall_done_clone_app,
475                     targetAppLabel
476             )
477             } else {
478                 context.getString(R.string.uninstall_done_app, targetAppLabel)
479             }
480             uninstallResult.setValue(
481                 UninstallSuccess(activityResultCode = legacyStatus, message = statusMessage)
482             )
483         } else {
484             val uninstallFailureChannel = NotificationChannel(
485                 UNINSTALL_FAILURE_CHANNEL,
486                 context.getString(R.string.uninstall_failure_notification_channel),
487                 NotificationManager.IMPORTANCE_DEFAULT
488             )
489             notificationManager!!.createNotificationChannel(uninstallFailureChannel)
490 
491             val uninstallFailedNotification: Notification.Builder =
492                 Notification.Builder(context, UNINSTALL_FAILURE_CHANNEL)
493 
494             val myUserHandle = Process.myUserHandle()
495             when (legacyStatus) {
496                 PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
497                     // Find out if the package is an active admin for some non-current user.
498                     val otherBlockingUserHandle =
499                         findUserOfDeviceAdmin(myUserHandle, targetPackageName!!)
500                     if (otherBlockingUserHandle == null) {
501                         Log.d(
502                             LOG_TAG,
503                             "Uninstall failed because $targetPackageName" +
504                                 " is a device admin"
505                         )
506                         addDeviceManagerButton(context, uninstallFailedNotification)
507                         setBigText(
508                             uninstallFailedNotification,
509                             context.getString(
510                                 R.string.uninstall_failed_device_policy_manager
511                             )
512                         )
513                     } else {
514                         Log.d(
515                             LOG_TAG,
516                             "Uninstall failed because $targetPackageName" +
517                                 " is a device admin of user $otherBlockingUserHandle"
518                         )
519                         val userName = context.createContextAsUser(otherBlockingUserHandle, 0)
520                             .getSystemService(UserManager::class.java)!!.userName
521                         setBigText(
522                             uninstallFailedNotification,
523                             String.format(
524                                 context.getString(
525                                     R.string.uninstall_failed_device_policy_manager_of_user
526                                 ),
527                                 userName
528                             )
529                         )
530                     }
531                 }
532 
533                 PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
534                     val otherBlockingUserHandle = findBlockingUser(targetPackageName!!)
535                     val isProfileOfOrSame = isProfileOfOrSame(
536                         userManager!!,
537                         myUserHandle,
538                         otherBlockingUserHandle
539                     )
540                     if (isProfileOfOrSame) {
541                         addDeviceManagerButton(context, uninstallFailedNotification)
542                     } else {
543                         addManageUsersButton(context, uninstallFailedNotification)
544                     }
545                     var bigText: String? = null
546                     if (otherBlockingUserHandle == null) {
547                         Log.d(
548                             LOG_TAG,
549                             "Uninstall failed for $targetPackageName " +
550                                 "with code $status no blocking user"
551                         )
552                     } else if (otherBlockingUserHandle === UserHandle.SYSTEM) {
553                         bigText = context.getString(R.string.uninstall_blocked_device_owner)
554                     } else {
555                         bigText = context.getString(
556                             if (uninstallFromAllUsers) {
557                                 R.string.uninstall_all_blocked_profile_owner
558                             } else {
559                                 R.string.uninstall_blocked_profile_owner
560                             }
561                         )
562                     }
563                     bigText?.let { setBigText(uninstallFailedNotification, it) }
564                 }
565 
566                 else -> {
567                     Log.d(
568                         LOG_TAG,
569                         "Uninstall blocked for $targetPackageName" +
570                             " with legacy code $legacyStatus"
571                     )
572                 }
573             }
574             uninstallFailedNotification.setContentTitle(
575                 context.getString(R.string.uninstall_failed_app, targetAppLabel)
576             )
577             uninstallFailedNotification.setOngoing(false)
578             uninstallFailedNotification.setSmallIcon(R.drawable.ic_error)
579 
580             uninstallResult.setValue(
581                 UninstallFailed(
582                     returnResult = false,
583                     uninstallNotificationId = uninstallId,
584                     uninstallNotification = uninstallFailedNotification.build()
585                 )
586             )
587         }
588     }
589 
590     /**
591      * @param myUserHandle [UserHandle] of the current user.
592      * @param packageName Name of the package being uninstalled.
593      * @return the [UserHandle] of the user in which a package is a device admin.
594      */
findUserOfDeviceAdminnull595     private fun findUserOfDeviceAdmin(myUserHandle: UserHandle, packageName: String): UserHandle? {
596         for (otherUserHandle in userManager!!.getUserHandles(true)) {
597             // We only catch the case when the user in question is neither the
598             // current user nor its profile.
599             if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
600                 continue
601             }
602             val dpm = context.createContextAsUser(otherUserHandle, 0)
603                 .getSystemService(DevicePolicyManager::class.java)
604             if (dpm!!.packageHasActiveAdmins(packageName)) {
605                 return otherUserHandle
606             }
607         }
608         return null
609     }
610 
611     /**
612      *
613      * @param packageName Name of the package being uninstalled.
614      * @return [UserHandle] of the user in which a package is blocked from being uninstalled.
615      */
findBlockingUsernull616     private fun findBlockingUser(packageName: String): UserHandle? {
617         for (otherUserHandle in userManager!!.getUserHandles(true)) {
618             // TODO (b/307399586): Add a negation when the logic of the method is fixed
619             if (packageManager.canUserUninstall(packageName, otherUserHandle)) {
620                 return otherUserHandle
621             }
622         }
623         return null
624     }
625 
626     /**
627      * Set big text for the notification.
628      *
629      * @param builder The builder of the notification
630      * @param text The text to set.
631      */
setBigTextnull632     private fun setBigText(
633         builder: Notification.Builder,
634         text: CharSequence
635     ) {
636         builder.setStyle(Notification.BigTextStyle().bigText(text))
637     }
638 
639     /**
640      * Add a button to the notification that links to the user management.
641      *
642      * @param context The context the notification is created in
643      * @param builder The builder of the notification
644      */
addManageUsersButtonnull645     private fun addManageUsersButton(
646         context: Context,
647         builder: Notification.Builder
648     ) {
649         builder.addAction(
650             Notification.Action.Builder(
651                 Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
652                 context.getString(R.string.manage_users),
653                 PendingIntent.getActivity(
654                     context,
655                     0,
656                     getUserSettingsIntent(),
657                     PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
658                 )
659             )
660                 .build()
661         )
662     }
663 
getUserSettingsIntentnull664     private fun getUserSettingsIntent(): Intent {
665         val intent = Intent(Settings.ACTION_USER_SETTINGS)
666         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
667         return intent
668     }
669 
670     /**
671      * Add a button to the notification that links to the device policy management.
672      *
673      * @param context The context the notification is created in
674      * @param builder The builder of the notification
675      */
addDeviceManagerButtonnull676     private fun addDeviceManagerButton(
677         context: Context,
678         builder: Notification.Builder
679     ) {
680         builder.addAction(
681             Notification.Action.Builder(
682                 Icon.createWithResource(context, R.drawable.ic_lock),
683                 context.getString(R.string.manage_device_administrators),
684                 PendingIntent.getActivity(
685                     context,
686                     0,
687                     getDeviceManagerIntent(),
688                     PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
689                 )
690             )
691                 .build()
692         )
693     }
694 
getDeviceManagerIntentnull695     private fun getDeviceManagerIntent(): Intent {
696         val intent = Intent()
697         intent.setClassName(
698             "com.android.settings",
699             "com.android.settings.Settings\$DeviceAdminSettingsActivity"
700         )
701         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
702         return intent
703     }
704 
705     /**
706      * Starts an uninstall for the given package.
707      *
708      * @return `true` if there was no exception while uninstalling. This does not represent
709      * the result of the uninstall. Result will be made available in [handleUninstallResult]
710      */
startUninstallnull711     private fun startUninstall(
712         packageName: String,
713         targetUser: UserHandle,
714         pendingIntent: PendingIntent,
715         uninstallFromAllUsers: Boolean,
716         keepData: Boolean
717     ): Boolean {
718         var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0
719         flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0
720 
721         return try {
722             context.createContextAsUser(targetUser, 0)
723                 .packageManager.packageInstaller.uninstall(
724                     VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
725                     flags,
726                     pendingIntent.intentSender
727                 )
728             true
729         } catch (e: IllegalArgumentException) {
730             Log.e(LOG_TAG, "Failed to uninstall", e)
731             false
732         }
733     }
734 
cancelUninstallnull735     fun cancelUninstall() {
736         if (callback != null) {
737             callback!!.onUninstallComplete(
738                 targetPackageName!!,
739                 PackageManager.DELETE_FAILED_ABORTED,
740                 "Cancelled by user"
741             )
742         }
743     }
744 
745     companion object {
746         private val LOG_TAG = UninstallRepository::class.java.simpleName
747         private const val UNINSTALL_FAILURE_CHANNEL = "uninstall_failure"
748         private const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"
749         private const val EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"
750         private const val EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"
751         private const val EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP"
752         private const val EXTRA_PACKAGE_NAME =
753             "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME"
754         private const val EXTRA_TARGET_USER_ID = "EXTRA_TARGET_USER_ID"
755     }
756 
757     class CallerInfo(val activityName: String?, val uid: Int)
758 }
759