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