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 import androidx.annotation.Nullable; 38 import com.android.packageinstaller.common.EventResultPersister; 39 import com.android.packageinstaller.common.UninstallEventReceiver; 40 41 /** 42 * Start an uninstallation, show a dialog while uninstalling and return result to the caller. 43 */ 44 public class UninstallUninstalling extends Activity implements 45 EventResultPersister.EventResultObserver { 46 private static final String LOG_TAG = UninstallUninstalling.class.getSimpleName(); 47 48 private static final String UNINSTALL_ID = "com.android.packageinstaller.UNINSTALL_ID"; 49 private static final String BROADCAST_ACTION = 50 "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"; 51 52 static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"; 53 static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA"; 54 public static final String EXTRA_IS_CLONE_USER = "isCloneUser"; 55 56 private int mUninstallId; 57 private ApplicationInfo mAppInfo; 58 private PackageManager.UninstallCompleteCallback mCallback; 59 private boolean mReturnResult; 60 private String mLabel; 61 62 @Override onCreate(@ullable Bundle savedInstanceState)63 protected void onCreate(@Nullable Bundle savedInstanceState) { 64 super.onCreate(savedInstanceState); 65 66 setFinishOnTouchOutside(false); 67 68 mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 69 mCallback = getIntent().getParcelableExtra(PackageInstaller.EXTRA_CALLBACK, 70 PackageManager.UninstallCompleteCallback.class); 71 mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); 72 mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL); 73 74 try { 75 if (savedInstanceState == null) { 76 boolean allUsers = getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, 77 false); 78 boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false); 79 UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER); 80 81 boolean isCloneUser = false; 82 if (user == null) { 83 user = Process.myUserHandle(); 84 } 85 86 UserManager customUserManager = UninstallUninstalling.this 87 .createContextAsUser(user, 0) 88 .getSystemService(UserManager.class); 89 if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) { 90 isCloneUser = true; 91 } 92 93 // Show dialog, which is the whole UI 94 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 95 Fragment prev = getFragmentManager().findFragmentByTag("dialog"); 96 if (prev != null) { 97 transaction.remove(prev); 98 } 99 DialogFragment dialog = new UninstallUninstallingFragment(); 100 Bundle args = new Bundle(); 101 args.putBoolean(EXTRA_IS_CLONE_USER, isCloneUser); 102 dialog.setArguments(args); 103 dialog.setCancelable(false); 104 dialog.show(transaction, "dialog"); 105 106 mUninstallId = UninstallEventReceiver.addObserver(this, 107 EventResultPersister.GENERATE_NEW_ID, this); 108 109 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 110 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 111 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId); 112 broadcastIntent.setPackage(getPackageName()); 113 114 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId, 115 broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT 116 | PendingIntent.FLAG_MUTABLE); 117 118 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0; 119 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0; 120 flags |= getIntent().getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0); 121 122 createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall( 123 new VersionedPackage(mAppInfo.packageName, 124 PackageManager.VERSION_CODE_HIGHEST), 125 flags, pendingIntent.getIntentSender()); 126 } else { 127 mUninstallId = savedInstanceState.getInt(UNINSTALL_ID); 128 UninstallEventReceiver.addObserver(this, mUninstallId, this); 129 } 130 } catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) { 131 Log.e(LOG_TAG, "Fails to start uninstall", e); 132 onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR, 133 null, 0); 134 } 135 } 136 137 @Override onSaveInstanceState(Bundle outState)138 protected void onSaveInstanceState(Bundle outState) { 139 super.onSaveInstanceState(outState); 140 141 outState.putInt(UNINSTALL_ID, mUninstallId); 142 } 143 144 @Override onBackPressed()145 public void onBackPressed() { 146 // do nothing 147 } 148 149 @Override onResult(int status, int legacyStatus, @Nullable String message, int serviceId)150 public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) { 151 if (mCallback != null) { 152 // The caller will be informed about the result via a callback 153 mCallback.onUninstallComplete(mAppInfo.packageName, legacyStatus, message); 154 } else if (mReturnResult) { 155 // The caller will be informed about the result and might decide to display it 156 Intent result = new Intent(); 157 158 result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus); 159 setResult(status == PackageInstaller.STATUS_SUCCESS ? Activity.RESULT_OK 160 : Activity.RESULT_FIRST_USER, result); 161 } else { 162 // This is the rare case that the caller did not ask for the result, but wanted to be 163 // notified via onActivityResult when the installation finishes 164 if (status != PackageInstaller.STATUS_SUCCESS) { 165 Toast.makeText(this, getString(R.string.uninstall_failed_app, mLabel), 166 Toast.LENGTH_LONG).show(); 167 } 168 } 169 finish(); 170 } 171 172 @Override onDestroy()173 protected void onDestroy() { 174 UninstallEventReceiver.removeObserver(this, mUninstallId); 175 176 super.onDestroy(); 177 } 178 179 /** 180 * Dialog that shows that the app is uninstalling. 181 */ 182 public static class UninstallUninstallingFragment extends DialogFragment { 183 @Override onCreateDialog(Bundle savedInstanceState)184 public Dialog onCreateDialog(Bundle savedInstanceState) { 185 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); 186 187 Bundle bundle = getArguments(); 188 boolean isCloneUser = false; 189 if (bundle != null) { 190 isCloneUser = bundle.getBoolean(EXTRA_IS_CLONE_USER); 191 } 192 193 dialogBuilder.setCancelable(false); 194 if (isCloneUser) { 195 dialogBuilder.setTitle(getActivity().getString(R.string.uninstalling_cloned_app, 196 ((UninstallUninstalling) getActivity()).mLabel)); 197 } else { 198 dialogBuilder.setTitle(getActivity().getString(R.string.uninstalling_app, 199 ((UninstallUninstalling) getActivity()).mLabel)); 200 } 201 202 Dialog dialog = dialogBuilder.create(); 203 dialog.setCanceledOnTouchOutside(false); 204 205 return dialog; 206 } 207 } 208 } 209