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.television; 18 19 import android.app.Activity; 20 import android.app.PendingIntent; 21 import android.app.admin.DevicePolicyManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageInstaller; 26 import android.content.pm.PackageManager; 27 import android.content.pm.VersionedPackage; 28 import android.graphics.Color; 29 import android.graphics.drawable.ColorDrawable; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.os.Process; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.util.Log; 37 import android.util.TypedValue; 38 import android.view.KeyEvent; 39 import android.widget.Toast; 40 import androidx.annotation.Nullable; 41 import com.android.packageinstaller.PackageUtil; 42 import com.android.packageinstaller.R; 43 import com.android.packageinstaller.common.EventResultPersister; 44 import com.android.packageinstaller.common.UninstallEventReceiver; 45 import java.lang.ref.WeakReference; 46 import java.util.List; 47 48 /** 49 * This activity corresponds to a download progress screen that is displayed 50 * when an application is uninstalled. The result of the application uninstall 51 * is indicated in the result code that gets set to 0 or 1. The application gets launched 52 * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects 53 * the application object of the application to uninstall. 54 */ 55 public class UninstallAppProgress extends Activity implements 56 EventResultPersister.EventResultObserver { 57 private static final String TAG = "UninstallAppProgress"; 58 59 private static final String FRAGMENT_TAG = "progress_fragment"; 60 private static final String BROADCAST_ACTION = 61 "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"; 62 63 private ApplicationInfo mAppInfo; 64 private boolean mAllUsers; 65 private PackageManager.UninstallCompleteCallback mCallback; 66 67 private volatile int mResultCode = -1; 68 69 /** 70 * If initView was called. We delay this call to not have to call it at all if the uninstall is 71 * quick 72 */ 73 private boolean mIsViewInitialized; 74 75 /** Amount of time to wait until we show the UI */ 76 private static final int QUICK_INSTALL_DELAY_MILLIS = 500; 77 78 private static final int UNINSTALL_COMPLETE = 1; 79 private static final int UNINSTALL_IS_SLOW = 2; 80 81 private Handler mHandler = new MessageHandler(this); 82 83 private static class MessageHandler extends Handler { 84 private final WeakReference<UninstallAppProgress> mActivity; 85 MessageHandler(UninstallAppProgress activity)86 public MessageHandler(UninstallAppProgress activity) { 87 mActivity = new WeakReference<>(activity); 88 } 89 90 @Override handleMessage(Message msg)91 public void handleMessage(Message msg) { 92 UninstallAppProgress activity = mActivity.get(); 93 if (activity != null) { 94 activity.handleMessage(msg); 95 } 96 } 97 } 98 handleMessage(Message msg)99 private void handleMessage(Message msg) { 100 if (isFinishing() || isDestroyed()) { 101 return; 102 } 103 104 switch (msg.what) { 105 case UNINSTALL_IS_SLOW: 106 initView(); 107 break; 108 case UNINSTALL_COMPLETE: 109 mHandler.removeMessages(UNINSTALL_IS_SLOW); 110 111 if (msg.arg1 != PackageManager.DELETE_SUCCEEDED) { 112 initView(); 113 } 114 115 mResultCode = msg.arg1; 116 final String packageName = (String) msg.obj; 117 118 if (mCallback != null) { 119 mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, packageName); 120 finish(); 121 return; 122 } 123 124 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 125 Intent result = new Intent(); 126 result.putExtra(Intent.EXTRA_INSTALL_RESULT, mResultCode); 127 setResult(mResultCode == PackageManager.DELETE_SUCCEEDED 128 ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER, 129 result); 130 finish(); 131 return; 132 } 133 134 // Update the status text 135 final String statusText; 136 Context ctx = getBaseContext(); 137 switch (msg.arg1) { 138 case PackageManager.DELETE_SUCCEEDED: 139 statusText = getString(R.string.uninstall_done); 140 // Show a Toast and finish the activity 141 Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show(); 142 setResultAndFinish(); 143 return; 144 case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: { 145 UserManager userManager = 146 (UserManager) getSystemService(Context.USER_SERVICE); 147 // Find out if the package is an active admin for some non-current user. 148 UserHandle myUserHandle = Process.myUserHandle(); 149 UserHandle otherBlockingUserHandle = null; 150 for (UserHandle otherUserHandle : userManager.getUserHandles(true)) { 151 // We only catch the case when the user in question is neither the 152 // current user nor its profile. 153 if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) { 154 continue; 155 } 156 DevicePolicyManager dpm = ctx.createContextAsUser(otherUserHandle, 0) 157 .getSystemService(DevicePolicyManager.class); 158 if (dpm.packageHasActiveAdmins(packageName)) { 159 otherBlockingUserHandle = otherUserHandle; 160 break; 161 } 162 } 163 if (otherBlockingUserHandle == null) { 164 Log.d(TAG, "Uninstall failed because " + packageName 165 + " is a device admin"); 166 getProgressFragment().setDeviceManagerButtonVisible(true); 167 statusText = getString( 168 R.string.uninstall_failed_device_policy_manager); 169 } else { 170 Log.d(TAG, "Uninstall failed because " + packageName 171 + " is a device admin of user " + otherBlockingUserHandle); 172 getProgressFragment().setDeviceManagerButtonVisible(false); 173 String userName = ctx.createContextAsUser(otherBlockingUserHandle, 0) 174 .getSystemService(UserManager.class).getUserName(); 175 statusText = String.format( 176 getString(R.string.uninstall_failed_device_policy_manager_of_user), 177 userName); 178 } 179 break; 180 } 181 case PackageManager.DELETE_FAILED_OWNER_BLOCKED: { 182 UserManager userManager = 183 (UserManager) getSystemService(Context.USER_SERVICE); 184 PackageManager packageManager = ctx.getPackageManager(); 185 List<UserHandle> userHandles = userManager.getUserHandles(true); 186 UserHandle otherBlockingUserHandle = null; 187 for (int i = 0; i < userHandles.size(); ++i) { 188 final UserHandle handle = userHandles.get(i); 189 if (packageManager.canUserUninstall(packageName, handle)) { 190 otherBlockingUserHandle = handle; 191 break; 192 } 193 } 194 UserHandle myUserHandle = Process.myUserHandle(); 195 if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) { 196 getProgressFragment().setDeviceManagerButtonVisible(true); 197 } else { 198 getProgressFragment().setDeviceManagerButtonVisible(false); 199 getProgressFragment().setUsersButtonVisible(true); 200 } 201 // TODO: b/25442806 202 if (otherBlockingUserHandle == UserHandle.SYSTEM) { 203 statusText = getString(R.string.uninstall_blocked_device_owner); 204 } else if (otherBlockingUserHandle == null) { 205 Log.d(TAG, "Uninstall failed for " + packageName + " with code " 206 + msg.arg1 + " no blocking user"); 207 statusText = getString(R.string.uninstall_failed); 208 } else { 209 statusText = mAllUsers 210 ? getString(R.string.uninstall_all_blocked_profile_owner) : 211 getString(R.string.uninstall_blocked_profile_owner); 212 } 213 break; 214 } 215 default: 216 Log.d(TAG, "Uninstall failed for " + packageName + " with code " 217 + msg.arg1); 218 statusText = getString(R.string.uninstall_failed); 219 break; 220 } 221 getProgressFragment().showCompletion(statusText); 222 break; 223 default: 224 break; 225 } 226 } 227 isProfileOfOrSame(UserManager userManager, UserHandle userHandle, UserHandle profileHandle)228 private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle, 229 UserHandle profileHandle) { 230 if (userHandle.equals(profileHandle)) { 231 return true; 232 } 233 return userManager.getProfileParent(profileHandle) != null 234 && userManager.getProfileParent(profileHandle).equals(userHandle); 235 } 236 237 @Override onCreate(Bundle icicle)238 public void onCreate(Bundle icicle) { 239 super.onCreate(icicle); 240 241 Intent intent = getIntent(); 242 mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 243 mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK, 244 PackageManager.UninstallCompleteCallback.class); 245 246 // This currently does not support going through a onDestroy->onCreate cycle. Hence if that 247 // happened, just fail the operation for mysterious reasons. 248 if (icicle != null) { 249 mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR; 250 251 if (mCallback != null) { 252 mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, null); 253 finish(); 254 } else { 255 setResultAndFinish(); 256 } 257 258 return; 259 } 260 261 mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 262 UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); 263 if (user == null) { 264 user = Process.myUserHandle(); 265 } 266 267 268 // Make window transparent until initView is called. In many cases we can avoid showing the 269 // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove 270 // it, it just looks like a flicker. 271 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 272 getWindow().setStatusBarColor(Color.TRANSPARENT); 273 getWindow().setNavigationBarColor(Color.TRANSPARENT); 274 275 try { 276 int uninstallId = UninstallEventReceiver.addObserver(this, 277 EventResultPersister.GENERATE_NEW_ID, this); 278 279 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 280 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 281 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId); 282 broadcastIntent.setPackage(getPackageName()); 283 284 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId, 285 broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT 286 | PendingIntent.FLAG_MUTABLE); 287 288 createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall( 289 new VersionedPackage(mAppInfo.packageName, PackageManager.VERSION_CODE_HIGHEST), 290 mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, 291 pendingIntent.getIntentSender()); 292 } catch (IllegalArgumentException e) { 293 // Couldn't find the package, no need to call uninstall. 294 Log.w(TAG, "Could not find package, not deleting " + mAppInfo.packageName, e); 295 } catch (EventResultPersister.OutOfIdsException e) { 296 Log.e(TAG, "Fails to start uninstall", e); 297 onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR, 298 null, 0); 299 } 300 301 mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW), 302 QUICK_INSTALL_DELAY_MILLIS); 303 } 304 getAppInfo()305 public ApplicationInfo getAppInfo() { 306 return mAppInfo; 307 } 308 309 @Override onResult(int status, int legacyStatus, @Nullable String message, int serviceId)310 public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) { 311 Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE); 312 msg.arg1 = legacyStatus; 313 msg.obj = mAppInfo.packageName; 314 mHandler.sendMessage(msg); 315 } 316 setResultAndFinish()317 public void setResultAndFinish() { 318 setResult(mResultCode); 319 finish(); 320 } 321 initView()322 private void initView() { 323 if (mIsViewInitialized) { 324 return; 325 } 326 mIsViewInitialized = true; 327 328 // We set the window background to translucent in constructor, revert this 329 TypedValue attribute = new TypedValue(); 330 getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true); 331 if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT && 332 attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) { 333 getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data)); 334 } else { 335 getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId, 336 getTheme())); 337 } 338 339 getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true); 340 getWindow().setNavigationBarColor(attribute.data); 341 342 getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true); 343 getWindow().setStatusBarColor(attribute.data); 344 345 boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); 346 setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title); 347 348 getFragmentManager().beginTransaction() 349 .add(android.R.id.content, new UninstallAppProgressFragment(), FRAGMENT_TAG) 350 .commitNowAllowingStateLoss(); 351 } 352 353 @Override dispatchKeyEvent(KeyEvent ev)354 public boolean dispatchKeyEvent(KeyEvent ev) { 355 if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK) { 356 if (mResultCode == -1) { 357 // Ignore back key when installation is in progress 358 return true; 359 } else { 360 // If installation is done, just set the result code 361 setResult(mResultCode); 362 } 363 } 364 return super.dispatchKeyEvent(ev); 365 } 366 getProgressFragment()367 private ProgressFragment getProgressFragment() { 368 return (ProgressFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG); 369 } 370 371 public interface ProgressFragment { setUsersButtonVisible(boolean visible)372 void setUsersButtonVisible(boolean visible); setDeviceManagerButtonVisible(boolean visible)373 void setDeviceManagerButtonVisible(boolean visible); showCompletion(CharSequence statusText)374 void showCompletion(CharSequence statusText); 375 } 376 } 377