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