1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.packageinstaller; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.app.DialogFragment; 23 import android.app.Fragment; 24 import android.app.FragmentTransaction; 25 import android.app.PendingIntent; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageInstaller; 29 import android.content.pm.PackageManager; 30 import android.content.pm.VersionedPackage; 31 import android.os.Bundle; 32 import android.os.Process; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.util.Log; 36 import android.widget.Toast; 37 38 import androidx.annotation.Nullable; 39 40 /** 41 * Start an uninstallation, show a dialog while uninstalling and return result to the caller. 42 */ 43 public class UninstallUninstalling extends Activity implements 44 EventResultPersister.EventResultObserver { 45 private static final String LOG_TAG = UninstallUninstalling.class.getSimpleName(); 46 47 private static final String UNINSTALL_ID = "com.android.packageinstaller.UNINSTALL_ID"; 48 private static final String BROADCAST_ACTION = 49 "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"; 50 51 static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"; 52 static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA"; 53 public static final String EXTRA_IS_CLONE_USER = "isCloneUser"; 54 55 private int mUninstallId; 56 private ApplicationInfo mAppInfo; 57 private PackageManager.UninstallCompleteCallback mCallback; 58 private boolean mReturnResult; 59 private String mLabel; 60 61 @Override onCreate(@ullable Bundle savedInstanceState)62 protected void onCreate(@Nullable Bundle savedInstanceState) { 63 super.onCreate(savedInstanceState); 64 65 setFinishOnTouchOutside(false); 66 67 mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 68 mCallback = getIntent().getParcelableExtra(PackageInstaller.EXTRA_CALLBACK, 69 PackageManager.UninstallCompleteCallback.class); 70 mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); 71 mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL); 72 73 try { 74 if (savedInstanceState == null) { 75 boolean allUsers = getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, 76 false); 77 boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false); 78 UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER); 79 80 boolean isCloneUser = false; 81 if (user == null) { 82 user = Process.myUserHandle(); 83 } 84 85 UserManager customUserManager = UninstallUninstalling.this 86 .createContextAsUser(user, 0) 87 .getSystemService(UserManager.class); 88 if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) { 89 isCloneUser = true; 90 } 91 92 // Show dialog, which is the whole UI 93 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 94 Fragment prev = getFragmentManager().findFragmentByTag("dialog"); 95 if (prev != null) { 96 transaction.remove(prev); 97 } 98 DialogFragment dialog = new UninstallUninstallingFragment(); 99 Bundle args = new Bundle(); 100 args.putBoolean(EXTRA_IS_CLONE_USER, isCloneUser); 101 dialog.setArguments(args); 102 dialog.setCancelable(false); 103 dialog.show(transaction, "dialog"); 104 105 mUninstallId = UninstallEventReceiver.addObserver(this, 106 EventResultPersister.GENERATE_NEW_ID, this); 107 108 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 109 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 110 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId); 111 broadcastIntent.setPackage(getPackageName()); 112 113 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId, 114 broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT 115 | PendingIntent.FLAG_MUTABLE); 116 117 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0; 118 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0; 119 120 createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall( 121 new VersionedPackage(mAppInfo.packageName, 122 PackageManager.VERSION_CODE_HIGHEST), 123 flags, pendingIntent.getIntentSender()); 124 } else { 125 mUninstallId = savedInstanceState.getInt(UNINSTALL_ID); 126 UninstallEventReceiver.addObserver(this, mUninstallId, this); 127 } 128 } catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) { 129 Log.e(LOG_TAG, "Fails to start uninstall", e); 130 onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR, 131 null, 0); 132 } 133 } 134 135 @Override onSaveInstanceState(Bundle outState)136 protected void onSaveInstanceState(Bundle outState) { 137 super.onSaveInstanceState(outState); 138 139 outState.putInt(UNINSTALL_ID, mUninstallId); 140 } 141 142 @Override onBackPressed()143 public void onBackPressed() { 144 // do nothing 145 } 146 147 @Override onResult(int status, int legacyStatus, @Nullable String message, int serviceId)148 public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) { 149 if (mCallback != null) { 150 // The caller will be informed about the result via a callback 151 mCallback.onUninstallComplete(mAppInfo.packageName, legacyStatus, message); 152 } else if (mReturnResult) { 153 // The caller will be informed about the result and might decide to display it 154 Intent result = new Intent(); 155 156 result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus); 157 setResult(status == PackageInstaller.STATUS_SUCCESS ? Activity.RESULT_OK 158 : Activity.RESULT_FIRST_USER, result); 159 } else { 160 // This is the rare case that the caller did not ask for the result, but wanted to be 161 // notified via onActivityResult when the installation finishes 162 if (status != PackageInstaller.STATUS_SUCCESS) { 163 Toast.makeText(this, getString(R.string.uninstall_failed_app, mLabel), 164 Toast.LENGTH_LONG).show(); 165 } 166 } 167 finish(); 168 } 169 170 @Override onDestroy()171 protected void onDestroy() { 172 UninstallEventReceiver.removeObserver(this, mUninstallId); 173 174 super.onDestroy(); 175 } 176 177 /** 178 * Dialog that shows that the app is uninstalling. 179 */ 180 public static class UninstallUninstallingFragment extends DialogFragment { 181 @Override onCreateDialog(Bundle savedInstanceState)182 public Dialog onCreateDialog(Bundle savedInstanceState) { 183 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); 184 185 Bundle bundle = getArguments(); 186 boolean isCloneUser = false; 187 if (bundle != null) { 188 isCloneUser = bundle.getBoolean(EXTRA_IS_CLONE_USER); 189 } 190 191 dialogBuilder.setCancelable(false); 192 if (isCloneUser) { 193 dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_cloned_app, 194 ((UninstallUninstalling) getActivity()).mLabel)); 195 } else { 196 dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app, 197 ((UninstallUninstalling) getActivity()).mLabel)); 198 } 199 200 Dialog dialog = dialogBuilder.create(); 201 dialog.setCanceledOnTouchOutside(false); 202 203 return dialog; 204 } 205 } 206 } 207