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 android.app.Activity; 20 import android.app.admin.IDevicePolicyManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.IPackageDeleteObserver; 25 import android.content.pm.IPackageDeleteObserver2; 26 import android.content.pm.IPackageManager; 27 import android.content.pm.PackageInstaller; 28 import android.content.pm.PackageManager; 29 import android.content.pm.UserInfo; 30 import android.content.res.Configuration; 31 import android.graphics.Color; 32 import android.graphics.drawable.ColorDrawable; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.Message; 37 import android.os.RemoteException; 38 import android.os.ServiceManager; 39 import android.os.UserHandle; 40 import android.os.UserManager; 41 import android.provider.Settings; 42 import android.util.Log; 43 import android.util.TypedValue; 44 import android.view.KeyEvent; 45 import android.view.View; 46 import android.view.View.OnClickListener; 47 import android.widget.Button; 48 import android.widget.ProgressBar; 49 import android.widget.TextView; 50 import android.widget.Toast; 51 52 import java.util.List; 53 54 /** 55 * This activity corresponds to a download progress screen that is displayed 56 * when an application is uninstalled. The result of the application uninstall 57 * is indicated in the result code that gets set to 0 or 1. The application gets launched 58 * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects 59 * the application object of the application to uninstall. 60 */ 61 public class UninstallAppProgress extends Activity implements OnClickListener { 62 private final String TAG="UninstallAppProgress"; 63 64 private ApplicationInfo mAppInfo; 65 private boolean mAllUsers; 66 private UserHandle mUser; 67 private IBinder mCallback; 68 69 private Button mOkButton; 70 private Button mDeviceManagerButton; 71 private Button mUsersButton; 72 private volatile int mResultCode = -1; 73 74 /** 75 * If initView was called. We delay this call to not have to call it at all if the uninstall is 76 * quick 77 */ 78 private boolean mIsViewInitialized; 79 80 /** Amount of time to wait until we show the UI */ 81 private static final int QUICK_INSTALL_DELAY_MILLIS = 500; 82 83 private static final int UNINSTALL_COMPLETE = 1; 84 private static final int UNINSTALL_IS_SLOW = 2; 85 86 private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) { 87 if (userId == profileId) { 88 return true; 89 } 90 UserInfo parentUser = userManager.getProfileParent(profileId); 91 return parentUser != null && parentUser.id == userId; 92 } 93 94 private Handler mHandler = new Handler() { 95 public void handleMessage(Message msg) { 96 if (isFinishing() || isDestroyed()) { 97 return; 98 } 99 100 switch (msg.what) { 101 case UNINSTALL_IS_SLOW: 102 initView(); 103 break; 104 case UNINSTALL_COMPLETE: 105 mHandler.removeMessages(UNINSTALL_IS_SLOW); 106 107 if (msg.arg1 != PackageManager.DELETE_SUCCEEDED) { 108 initView(); 109 } 110 111 mResultCode = msg.arg1; 112 final String packageName = (String) msg.obj; 113 114 if (mCallback != null) { 115 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub 116 .asInterface(mCallback); 117 try { 118 observer.onPackageDeleted(mAppInfo.packageName, mResultCode, 119 packageName); 120 } catch (RemoteException ignored) { 121 } 122 finish(); 123 return; 124 } 125 126 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 127 Intent result = new Intent(); 128 result.putExtra(Intent.EXTRA_INSTALL_RESULT, mResultCode); 129 setResult(mResultCode == PackageManager.DELETE_SUCCEEDED 130 ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER, 131 result); 132 finish(); 133 return; 134 } 135 136 // Update the status text 137 final String statusText; 138 switch (msg.arg1) { 139 case PackageManager.DELETE_SUCCEEDED: 140 statusText = getString(R.string.uninstall_done); 141 // Show a Toast and finish the activity 142 Context ctx = getBaseContext(); 143 Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show(); 144 setResultAndFinish(mResultCode); 145 return; 146 case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: { 147 UserManager userManager = 148 (UserManager) getSystemService(Context.USER_SERVICE); 149 IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( 150 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); 151 // Find out if the package is an active admin for some non-current user. 152 int myUserId = UserHandle.myUserId(); 153 UserInfo otherBlockingUser = null; 154 for (UserInfo user : userManager.getUsers()) { 155 // We only catch the case when the user in question is neither the 156 // current user nor its profile. 157 if (isProfileOfOrSame(userManager, myUserId, user.id)) continue; 158 159 try { 160 if (dpm.packageHasActiveAdmins(packageName, user.id)) { 161 otherBlockingUser = user; 162 break; 163 } 164 } catch (RemoteException e) { 165 Log.e(TAG, "Failed to talk to package manager", e); 166 } 167 } 168 if (otherBlockingUser == null) { 169 Log.d(TAG, "Uninstall failed because " + packageName 170 + " is a device admin"); 171 mDeviceManagerButton.setVisibility(View.VISIBLE); 172 statusText = getString( 173 R.string.uninstall_failed_device_policy_manager); 174 } else { 175 Log.d(TAG, "Uninstall failed because " + packageName 176 + " is a device admin of user " + otherBlockingUser); 177 mDeviceManagerButton.setVisibility(View.GONE); 178 statusText = String.format( 179 getString(R.string.uninstall_failed_device_policy_manager_of_user), 180 otherBlockingUser.name); 181 } 182 break; 183 } 184 case PackageManager.DELETE_FAILED_OWNER_BLOCKED: { 185 UserManager userManager = 186 (UserManager) getSystemService(Context.USER_SERVICE); 187 IPackageManager packageManager = IPackageManager.Stub.asInterface( 188 ServiceManager.getService("package")); 189 List<UserInfo> users = userManager.getUsers(); 190 int blockingUserId = UserHandle.USER_NULL; 191 for (int i = 0; i < users.size(); ++i) { 192 final UserInfo user = users.get(i); 193 try { 194 if (packageManager.getBlockUninstallForUser(packageName, 195 user.id)) { 196 blockingUserId = user.id; 197 break; 198 } 199 } catch (RemoteException e) { 200 // Shouldn't happen. 201 Log.e(TAG, "Failed to talk to package manager", e); 202 } 203 } 204 int myUserId = UserHandle.myUserId(); 205 if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) { 206 mDeviceManagerButton.setVisibility(View.VISIBLE); 207 } else { 208 mDeviceManagerButton.setVisibility(View.GONE); 209 mUsersButton.setVisibility(View.VISIBLE); 210 } 211 // TODO: b/25442806 212 if (blockingUserId == UserHandle.USER_SYSTEM) { 213 statusText = getString(R.string.uninstall_blocked_device_owner); 214 } else if (blockingUserId == UserHandle.USER_NULL) { 215 Log.d(TAG, "Uninstall failed for " + packageName + " with code " 216 + msg.arg1 + " no blocking user"); 217 statusText = getString(R.string.uninstall_failed); 218 } else { 219 statusText = mAllUsers 220 ? getString(R.string.uninstall_all_blocked_profile_owner) : 221 getString(R.string.uninstall_blocked_profile_owner); 222 } 223 break; 224 } 225 default: 226 Log.d(TAG, "Uninstall failed for " + packageName + " with code " 227 + msg.arg1); 228 statusText = getString(R.string.uninstall_failed); 229 break; 230 } 231 findViewById(R.id.progress_view).setVisibility(View.GONE); 232 findViewById(R.id.status_view).setVisibility(View.VISIBLE); 233 ((TextView)findViewById(R.id.status_text)).setText(statusText); 234 findViewById(R.id.ok_panel).setVisibility(View.VISIBLE); 235 break; 236 default: 237 break; 238 } 239 } 240 }; 241 242 @Override 243 public void onCreate(Bundle icicle) { 244 super.onCreate(icicle); 245 246 Intent intent = getIntent(); 247 mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 248 mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK); 249 250 // This currently does not support going through a onDestroy->onCreate cycle. Hence if that 251 // happened, just fail the operation for mysterious reasons. 252 if (icicle != null) { 253 mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR; 254 255 if (mCallback != null) { 256 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub 257 .asInterface(mCallback); 258 try { 259 observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null); 260 } catch (RemoteException ignored) { 261 } 262 finish(); 263 } else { 264 setResultAndFinish(mResultCode); 265 } 266 267 return; 268 } 269 270 mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 271 if (mAllUsers && !UserManager.get(this).isAdminUser()) { 272 throw new SecurityException("Only admin user can request uninstall for all users"); 273 } 274 mUser = intent.getParcelableExtra(Intent.EXTRA_USER); 275 if (mUser == null) { 276 mUser = android.os.Process.myUserHandle(); 277 } else { 278 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 279 List<UserHandle> profiles = userManager.getUserProfiles(); 280 if (!profiles.contains(mUser)) { 281 throw new SecurityException("User " + android.os.Process.myUserHandle() + " can't " 282 + "request uninstall for user " + mUser); 283 } 284 } 285 286 PackageDeleteObserver observer = new PackageDeleteObserver(); 287 288 // Make window transparent until initView is called. In many cases we can avoid showing the 289 // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove 290 // it, it just looks like a flicker. 291 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 292 getWindow().setStatusBarColor(Color.TRANSPARENT); 293 getWindow().setNavigationBarColor(Color.TRANSPARENT); 294 295 getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer, 296 mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, mUser.getIdentifier()); 297 298 mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW), 299 QUICK_INSTALL_DELAY_MILLIS); 300 } 301 302 class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 303 public void packageDeleted(String packageName, int returnCode) { 304 Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE); 305 msg.arg1 = returnCode; 306 msg.obj = packageName; 307 mHandler.sendMessage(msg); 308 } 309 } 310 311 void setResultAndFinish(int retCode) { 312 setResult(retCode); 313 finish(); 314 } 315 316 public void initView() { 317 if (mIsViewInitialized) { 318 return; 319 } 320 mIsViewInitialized = true; 321 322 // We set the window background to translucent in constructor, revert this 323 TypedValue attribute = new TypedValue(); 324 getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true); 325 if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT && 326 attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) { 327 getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data)); 328 } else { 329 getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId, 330 getTheme())); 331 } 332 333 getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true); 334 getWindow().setNavigationBarColor(attribute.data); 335 336 getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true); 337 getWindow().setStatusBarColor(attribute.data); 338 339 boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); 340 setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title); 341 342 setContentView(R.layout.uninstall_progress); 343 // Initialize views 344 View snippetView = findViewById(R.id.app_snippet); 345 PackageUtil.initSnippetForInstalledApp(this, mAppInfo, snippetView); 346 mDeviceManagerButton = (Button) findViewById(R.id.device_manager_button); 347 mUsersButton = (Button) findViewById(R.id.users_button); 348 mDeviceManagerButton.setVisibility(View.GONE); 349 mDeviceManagerButton.setOnClickListener(new OnClickListener() { 350 @Override 351 public void onClick(View v) { 352 Intent intent = new Intent(); 353 intent.setClassName("com.android.settings", 354 "com.android.settings.Settings$DeviceAdminSettingsActivity"); 355 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); 356 startActivity(intent); 357 finish(); 358 } 359 }); 360 mUsersButton.setVisibility(View.GONE); 361 mUsersButton.setOnClickListener(new OnClickListener() { 362 @Override 363 public void onClick(View v) { 364 Intent intent = new Intent(Settings.ACTION_USER_SETTINGS); 365 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); 366 startActivity(intent); 367 finish(); 368 } 369 }); 370 // Hide button till progress is being displayed 371 mOkButton = (Button) findViewById(R.id.ok_button); 372 mOkButton.setOnClickListener(this); 373 } 374 375 public void onClick(View v) { 376 if(v == mOkButton) { 377 Log.i(TAG, "Finished uninstalling pkg: " + mAppInfo.packageName); 378 setResultAndFinish(mResultCode); 379 } 380 } 381 382 @Override 383 public boolean dispatchKeyEvent(KeyEvent ev) { 384 if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK) { 385 if (mResultCode == -1) { 386 // Ignore back key when installation is in progress 387 return true; 388 } else { 389 // If installation is done, just set the result code 390 setResult(mResultCode); 391 } 392 } 393 return super.dispatchKeyEvent(ev); 394 } 395 } 396