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