1 /* 2 ** 3 ** Copyright 2007, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 package com.android.packageinstaller; 18 19 import static android.app.AppOpsManager.MODE_ALLOWED; 20 import static android.content.pm.Flags.usePiaV2; 21 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 22 23 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; 24 25 import android.Manifest; 26 import android.app.Activity; 27 import android.app.AppOpsManager; 28 import android.app.DialogFragment; 29 import android.app.Fragment; 30 import android.app.FragmentTransaction; 31 import android.app.Notification; 32 import android.app.NotificationChannel; 33 import android.app.NotificationManager; 34 import android.app.PendingIntent; 35 import android.content.ComponentName; 36 import android.content.Intent; 37 import android.content.pm.ActivityInfo; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.PackageInstaller; 40 import android.content.pm.PackageManager; 41 import android.content.pm.VersionedPackage; 42 import android.content.res.Configuration; 43 import android.net.Uri; 44 import android.os.Build; 45 import android.os.Bundle; 46 import android.os.Process; 47 import android.os.UserHandle; 48 import android.os.UserManager; 49 import android.util.Log; 50 import androidx.annotation.NonNull; 51 import androidx.annotation.StringRes; 52 import com.android.packageinstaller.handheld.ErrorDialogFragment; 53 import com.android.packageinstaller.handheld.UninstallAlertDialogFragment; 54 import com.android.packageinstaller.television.ErrorFragment; 55 import com.android.packageinstaller.television.UninstallAlertFragment; 56 import com.android.packageinstaller.television.UninstallAppProgress; 57 import com.android.packageinstaller.common.EventResultPersister; 58 import com.android.packageinstaller.common.UninstallEventReceiver; 59 import com.android.packageinstaller.v2.ui.UninstallLaunch; 60 61 import java.util.List; 62 63 /* 64 * This activity presents UI to uninstall an application. Usually launched with intent 65 * Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute 66 * com.android.packageinstaller.PackageName set to the application package name 67 */ 68 public class UninstallerActivity extends Activity { 69 private static final String TAG = "UninstallerActivity"; 70 71 private static final String UNINSTALLING_CHANNEL = "uninstalling"; 72 private boolean mIsClonedApp; 73 74 public static class DialogInfo { 75 public ApplicationInfo appInfo; 76 public ActivityInfo activityInfo; 77 public boolean allUsers; 78 public UserHandle user; 79 public PackageManager.UninstallCompleteCallback callback; 80 public int deleteFlags; 81 } 82 83 private String mPackageName; 84 private DialogInfo mDialogInfo; 85 86 @Override onCreate(Bundle icicle)87 public void onCreate(Bundle icicle) { 88 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 89 90 // Never restore any state, esp. never create any fragments. The data in the fragment might 91 // be stale, if e.g. the app was uninstalled while the activity was destroyed. 92 super.onCreate(null); 93 94 // TODO(b/318521110) Enable PIA v2 for archive dialog. 95 if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) { 96 Log.i(TAG, "Using Pia V2"); 97 98 boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); 99 Intent piaV2 = new Intent(getIntent()); 100 piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid()); 101 piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_ACTIVITY_NAME, getCallingActivity()); 102 if (returnResult) { 103 piaV2.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 104 } 105 piaV2.setClass(this, UninstallLaunch.class); 106 startActivity(piaV2); 107 finish(); 108 return; 109 } 110 111 int callingUid = getLaunchedFromUid(); 112 if (callingUid == Process.INVALID_UID) { 113 // Cannot reach Package/ActivityManager. Aborting uninstall. 114 Log.e(TAG, "Could not determine the launching uid."); 115 116 setResult(Activity.RESULT_FIRST_USER); 117 finish(); 118 return; 119 } 120 121 String callingPackage = getPackageNameForUid(callingUid); 122 if (callingPackage == null) { 123 Log.e(TAG, "Package not found for originating uid " + callingUid); 124 setResult(Activity.RESULT_FIRST_USER); 125 finish(); 126 return; 127 } else { 128 AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); 129 if (appOpsManager.noteOpNoThrow( 130 AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage) 131 != MODE_ALLOWED) { 132 Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps"); 133 setResult(Activity.RESULT_FIRST_USER); 134 finish(); 135 return; 136 } 137 } 138 139 if (getMaxTargetSdkVersionForUid(this, callingUid) >= Build.VERSION_CODES.P 140 && getBaseContext().checkPermission(Manifest.permission.REQUEST_DELETE_PACKAGES, 141 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED 142 && getBaseContext().checkPermission(Manifest.permission.DELETE_PACKAGES, 143 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED) { 144 Log.e(TAG, "Uid " + callingUid + " does not have " 145 + Manifest.permission.REQUEST_DELETE_PACKAGES + " or " 146 + Manifest.permission.DELETE_PACKAGES); 147 148 setResult(Activity.RESULT_FIRST_USER); 149 finish(); 150 return; 151 } 152 153 // Get intent information. 154 // We expect an intent with URI of the form package://<packageName>#<className> 155 // className is optional; if specified, it is the activity the user chose to uninstall 156 final Intent intent = getIntent(); 157 final Uri packageUri = intent.getData(); 158 if (packageUri == null) { 159 Log.e(TAG, "No package URI in intent"); 160 showAppNotFound(); 161 return; 162 } 163 mPackageName = packageUri.getEncodedSchemeSpecificPart(); 164 if (mPackageName == null) { 165 Log.e(TAG, "Invalid package name in URI: " + packageUri); 166 showAppNotFound(); 167 return; 168 } 169 170 PackageManager pm = getPackageManager(); 171 UserManager userManager = getBaseContext().getSystemService(UserManager.class); 172 173 mDialogInfo = new DialogInfo(); 174 175 mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 176 if (mDialogInfo.allUsers && !userManager.isAdminUser()) { 177 Log.e(TAG, "Only admin user can request uninstall for all users"); 178 showUserIsNotAllowed(); 179 return; 180 } 181 mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER); 182 if (mDialogInfo.user == null) { 183 mDialogInfo.user = Process.myUserHandle(); 184 } else { 185 List<UserHandle> profiles = userManager.getUserProfiles(); 186 if (!profiles.contains(mDialogInfo.user)) { 187 Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall " 188 + "for user " + mDialogInfo.user); 189 showUserIsNotAllowed(); 190 return; 191 } 192 } 193 194 mDialogInfo.callback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK, 195 PackageManager.UninstallCompleteCallback.class); 196 197 try { 198 mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName, 199 PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER 200 | PackageManager.MATCH_ARCHIVED_PACKAGES)); 201 } catch (PackageManager.NameNotFoundException e) { 202 Log.e(TAG, "Unable to get packageName. Package manager is dead?"); 203 } 204 205 if (mDialogInfo.appInfo == null) { 206 Log.e(TAG, "Invalid packageName: " + mPackageName); 207 showAppNotFound(); 208 return; 209 } 210 211 // The class name may have been specified (e.g. when deleting an app from all apps) 212 final String className = packageUri.getFragment(); 213 if (className != null) { 214 try { 215 mDialogInfo.activityInfo = pm.getActivityInfo( 216 new ComponentName(mPackageName, className), 217 PackageManager.ComponentInfoFlags.of(0)); 218 } catch (PackageManager.NameNotFoundException e) { 219 Log.e(TAG, "Unable to get className. Package manager is dead?"); 220 // Continue as the ActivityInfo isn't critical. 221 } 222 } 223 parseDeleteFlags(intent); 224 225 showConfirmationDialog(); 226 } 227 isArchiveDialog(Intent intent)228 private boolean isArchiveDialog(Intent intent) { 229 return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0) 230 & PackageManager.DELETE_ARCHIVE) != 0; 231 } 232 233 /** 234 * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent} 235 * to archive an app if requested. 236 * 237 * Do not parse any flags because developers might pass here any flags which might cause 238 * unintended behaviour. 239 * For more context {@link com.android.server.pm.PackageArchiver#requestArchive}. 240 */ parseDeleteFlags(Intent intent)241 private void parseDeleteFlags(Intent intent) { 242 int deleteFlags = intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0); 243 int archive = deleteFlags & PackageManager.DELETE_ARCHIVE; 244 int keepData = deleteFlags & PackageManager.DELETE_KEEP_DATA; 245 mDialogInfo.deleteFlags = archive | keepData; 246 } 247 getDialogInfo()248 public DialogInfo getDialogInfo() { 249 return mDialogInfo; 250 } 251 showConfirmationDialog()252 private void showConfirmationDialog() { 253 if (isTv()) { 254 showContentFragment(new UninstallAlertFragment(), 0, 0); 255 } else { 256 showDialogFragment(new UninstallAlertDialogFragment(), 0, 0); 257 } 258 } 259 showAppNotFound()260 private void showAppNotFound() { 261 if (isTv()) { 262 showContentFragment(new ErrorFragment(), R.string.app_not_found_dlg_title, 263 R.string.app_not_found_dlg_text); 264 } else { 265 showDialogFragment(new ErrorDialogFragment(), R.string.app_not_found_dlg_title, 266 R.string.app_not_found_dlg_text); 267 } 268 } 269 showUserIsNotAllowed()270 private void showUserIsNotAllowed() { 271 if (isTv()) { 272 showContentFragment(new ErrorFragment(), 273 R.string.user_is_not_allowed_dlg_title, R.string.user_is_not_allowed_dlg_text); 274 } else { 275 showDialogFragment(new ErrorDialogFragment(), 0, R.string.user_is_not_allowed_dlg_text); 276 } 277 } 278 showGenericError()279 private void showGenericError() { 280 if (isTv()) { 281 showContentFragment(new ErrorFragment(), 282 R.string.generic_error_dlg_title, R.string.generic_error_dlg_text); 283 } else { 284 showDialogFragment(new ErrorDialogFragment(), 0, R.string.generic_error_dlg_text); 285 } 286 } 287 isTv()288 private boolean isTv() { 289 return (getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) 290 == Configuration.UI_MODE_TYPE_TELEVISION; 291 } 292 showContentFragment(@onNull Fragment fragment, @StringRes int title, @StringRes int text)293 private void showContentFragment(@NonNull Fragment fragment, @StringRes int title, 294 @StringRes int text) { 295 Bundle args = new Bundle(); 296 args.putInt(ErrorFragment.TITLE, title); 297 args.putInt(ErrorFragment.TEXT, text); 298 fragment.setArguments(args); 299 300 getFragmentManager().beginTransaction() 301 .replace(android.R.id.content, fragment) 302 .commit(); 303 } 304 showDialogFragment(@onNull DialogFragment fragment, @StringRes int title, @StringRes int text)305 private void showDialogFragment(@NonNull DialogFragment fragment, 306 @StringRes int title, @StringRes int text) { 307 FragmentTransaction ft = getFragmentManager().beginTransaction(); 308 Fragment prev = getFragmentManager().findFragmentByTag("dialog"); 309 if (prev != null) { 310 ft.remove(prev); 311 } 312 313 Bundle args = new Bundle(); 314 if (title != 0) { 315 args.putInt(ErrorDialogFragment.TITLE, title); 316 } 317 args.putInt(ErrorDialogFragment.TEXT, text); 318 319 fragment.setArguments(args); 320 fragment.show(ft, "dialog"); 321 } 322 323 /** 324 * Starts uninstall of app. 325 */ startUninstallProgress(boolean keepData, boolean isClonedApp)326 public void startUninstallProgress(boolean keepData, boolean isClonedApp) { 327 mIsClonedApp = isClonedApp; 328 startUninstallProgress(keepData); 329 } 330 startUninstallProgress(boolean keepData)331 public void startUninstallProgress(boolean keepData) { 332 boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); 333 CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager()); 334 335 if (isTv()) { 336 Intent newIntent = new Intent(Intent.ACTION_VIEW); 337 newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user); 338 newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); 339 newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); 340 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); 341 342 if (returnResult) { 343 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 344 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 345 } 346 347 newIntent.setClass(this, UninstallAppProgress.class); 348 startActivity(newIntent); 349 } else if (returnResult || mDialogInfo.callback != null || getCallingActivity() != null) { 350 Intent newIntent = new Intent(this, UninstallUninstalling.class); 351 352 newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user); 353 newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); 354 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); 355 newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label); 356 newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData); 357 newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); 358 if (returnResult) { 359 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 360 } 361 362 if (returnResult || getCallingActivity() != null) { 363 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 364 } 365 if (mDialogInfo.deleteFlags != 0) { 366 newIntent.putExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 367 mDialogInfo.deleteFlags); 368 } 369 startActivity(newIntent); 370 } else { 371 int uninstallId; 372 try { 373 uninstallId = UninstallEventReceiver.getNewId(this); 374 } catch (EventResultPersister.OutOfIdsException e) { 375 showGenericError(); 376 return; 377 } 378 379 Intent broadcastIntent = new Intent(this, UninstallFinish.class); 380 381 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 382 broadcastIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); 383 broadcastIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); 384 broadcastIntent.putExtra(UninstallFinish.EXTRA_APP_LABEL, label); 385 broadcastIntent.putExtra(UninstallFinish.EXTRA_UNINSTALL_ID, uninstallId); 386 broadcastIntent.putExtra(UninstallFinish.EXTRA_IS_CLONE_APP, mIsClonedApp); 387 388 PendingIntent pendingIntent = 389 PendingIntent.getBroadcast(this, uninstallId, broadcastIntent, 390 PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); 391 392 NotificationManager notificationManager = getSystemService(NotificationManager.class); 393 NotificationChannel uninstallingChannel = new NotificationChannel(UNINSTALLING_CHANNEL, 394 getString(R.string.uninstalling_notification_channel), 395 NotificationManager.IMPORTANCE_MIN); 396 notificationManager.createNotificationChannel(uninstallingChannel); 397 398 Notification uninstallingNotification = 399 (new Notification.Builder(this, UNINSTALLING_CHANNEL)) 400 .setSmallIcon(R.drawable.ic_remove).setProgress(0, 1, true) 401 .setContentTitle(mIsClonedApp 402 ? getString(R.string.uninstalling_cloned_app, label) 403 : getString(R.string.uninstalling_app, label)) 404 .setOngoing(true) 405 .build(); 406 407 notificationManager.notify(uninstallId, uninstallingNotification); 408 409 try { 410 Log.i(TAG, "Uninstalling extras=" + broadcastIntent.getExtras()); 411 412 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0; 413 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0; 414 flags |= mDialogInfo.deleteFlags; 415 416 createContextAsUser(mDialogInfo.user, 0).getPackageManager().getPackageInstaller() 417 .uninstall(new VersionedPackage(mDialogInfo.appInfo.packageName, 418 PackageManager.VERSION_CODE_HIGHEST), flags, 419 pendingIntent.getIntentSender()); 420 } catch (Exception e) { 421 notificationManager.cancel(uninstallId); 422 423 Log.e(TAG, "Cannot start uninstall", e); 424 showGenericError(); 425 } 426 } 427 } 428 dispatchAborted()429 public void dispatchAborted() { 430 if (mDialogInfo != null && mDialogInfo.callback != null) { 431 mDialogInfo.callback.onUninstallComplete(mPackageName, 432 PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"); 433 } 434 } 435 getPackageNameForUid(int sourceUid)436 private String getPackageNameForUid(int sourceUid) { 437 String[] packagesForUid = getPackageManager().getPackagesForUid(sourceUid); 438 if (packagesForUid == null) { 439 return null; 440 } 441 return packagesForUid[0]; 442 } 443 } 444