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.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.graphics.Color; 31 import android.graphics.drawable.ColorDrawable; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Message; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.util.Log; 41 import android.util.TypedValue; 42 import android.view.KeyEvent; 43 import android.widget.Toast; 44 45 import com.android.packageinstaller.PackageUtil; 46 import com.android.packageinstaller.R; 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 { 59 private static final String TAG = "UninstallAppProgress"; 60 61 private static final String FRAGMENT_TAG = "progress_fragment"; 62 63 private ApplicationInfo mAppInfo; 64 private boolean mAllUsers; 65 private IBinder 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 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub 120 .asInterface(mCallback); 121 try { 122 observer.onPackageDeleted(mAppInfo.packageName, mResultCode, 123 packageName); 124 } catch (RemoteException ignored) { 125 } 126 finish(); 127 return; 128 } 129 130 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 131 Intent result = new Intent(); 132 result.putExtra(Intent.EXTRA_INSTALL_RESULT, mResultCode); 133 setResult(mResultCode == PackageManager.DELETE_SUCCEEDED 134 ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER, 135 result); 136 finish(); 137 return; 138 } 139 140 // Update the status text 141 final String statusText; 142 switch (msg.arg1) { 143 case PackageManager.DELETE_SUCCEEDED: 144 statusText = getString(R.string.uninstall_done); 145 // Show a Toast and finish the activity 146 Context ctx = getBaseContext(); 147 Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show(); 148 setResultAndFinish(); 149 return; 150 case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: { 151 UserManager userManager = 152 (UserManager) getSystemService(Context.USER_SERVICE); 153 IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( 154 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); 155 // Find out if the package is an active admin for some non-current user. 156 int myUserId = UserHandle.myUserId(); 157 UserInfo otherBlockingUser = null; 158 for (UserInfo user : userManager.getUsers()) { 159 // We only catch the case when the user in question is neither the 160 // current user nor its profile. 161 if (isProfileOfOrSame(userManager, myUserId, user.id)) continue; 162 163 try { 164 if (dpm.packageHasActiveAdmins(packageName, user.id)) { 165 otherBlockingUser = user; 166 break; 167 } 168 } catch (RemoteException e) { 169 Log.e(TAG, "Failed to talk to package manager", e); 170 } 171 } 172 if (otherBlockingUser == null) { 173 Log.d(TAG, "Uninstall failed because " + packageName 174 + " is a device admin"); 175 getProgressFragment().setDeviceManagerButtonVisible(true); 176 statusText = getString( 177 R.string.uninstall_failed_device_policy_manager); 178 } else { 179 Log.d(TAG, "Uninstall failed because " + packageName 180 + " is a device admin of user " + otherBlockingUser); 181 getProgressFragment().setDeviceManagerButtonVisible(false); 182 statusText = String.format( 183 getString(R.string.uninstall_failed_device_policy_manager_of_user), 184 otherBlockingUser.name); 185 } 186 break; 187 } 188 case PackageManager.DELETE_FAILED_OWNER_BLOCKED: { 189 UserManager userManager = 190 (UserManager) getSystemService(Context.USER_SERVICE); 191 IPackageManager packageManager = IPackageManager.Stub.asInterface( 192 ServiceManager.getService("package")); 193 List<UserInfo> users = userManager.getUsers(); 194 int blockingUserId = UserHandle.USER_NULL; 195 for (int i = 0; i < users.size(); ++i) { 196 final UserInfo user = users.get(i); 197 try { 198 if (packageManager.getBlockUninstallForUser(packageName, 199 user.id)) { 200 blockingUserId = user.id; 201 break; 202 } 203 } catch (RemoteException e) { 204 // Shouldn't happen. 205 Log.e(TAG, "Failed to talk to package manager", e); 206 } 207 } 208 int myUserId = UserHandle.myUserId(); 209 if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) { 210 getProgressFragment().setDeviceManagerButtonVisible(true); 211 } else { 212 getProgressFragment().setDeviceManagerButtonVisible(false); 213 getProgressFragment().setUsersButtonVisible(true); 214 } 215 // TODO: b/25442806 216 if (blockingUserId == UserHandle.USER_SYSTEM) { 217 statusText = getString(R.string.uninstall_blocked_device_owner); 218 } else if (blockingUserId == UserHandle.USER_NULL) { 219 Log.d(TAG, "Uninstall failed for " + packageName + " with code " 220 + msg.arg1 + " no blocking user"); 221 statusText = getString(R.string.uninstall_failed); 222 } else { 223 statusText = mAllUsers 224 ? getString(R.string.uninstall_all_blocked_profile_owner) : 225 getString(R.string.uninstall_blocked_profile_owner); 226 } 227 break; 228 } 229 default: 230 Log.d(TAG, "Uninstall failed for " + packageName + " with code " 231 + msg.arg1); 232 statusText = getString(R.string.uninstall_failed); 233 break; 234 } 235 getProgressFragment().showCompletion(statusText); 236 break; 237 default: 238 break; 239 } 240 } 241 isProfileOfOrSame(UserManager userManager, int userId, int profileId)242 private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) { 243 if (userId == profileId) { 244 return true; 245 } 246 UserInfo parentUser = userManager.getProfileParent(profileId); 247 return parentUser != null && parentUser.id == userId; 248 } 249 250 @Override onCreate(Bundle icicle)251 public void onCreate(Bundle icicle) { 252 super.onCreate(icicle); 253 254 Intent intent = getIntent(); 255 mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 256 mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK); 257 258 // This currently does not support going through a onDestroy->onCreate cycle. Hence if that 259 // happened, just fail the operation for mysterious reasons. 260 if (icicle != null) { 261 mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR; 262 263 if (mCallback != null) { 264 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub 265 .asInterface(mCallback); 266 try { 267 observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null); 268 } catch (RemoteException ignored) { 269 } 270 finish(); 271 } else { 272 setResultAndFinish(); 273 } 274 275 return; 276 } 277 278 mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 279 UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); 280 if (user == null) { 281 user = android.os.Process.myUserHandle(); 282 } 283 284 PackageDeleteObserver observer = new PackageDeleteObserver(); 285 286 // Make window transparent until initView is called. In many cases we can avoid showing the 287 // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove 288 // it, it just looks like a flicker. 289 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 290 getWindow().setStatusBarColor(Color.TRANSPARENT); 291 getWindow().setNavigationBarColor(Color.TRANSPARENT); 292 293 try { 294 getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer, 295 mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, user.getIdentifier()); 296 } catch (IllegalArgumentException e) { 297 // Couldn't find the package, no need to call uninstall. 298 Log.w(TAG, "Could not find package, not deleting " + mAppInfo.packageName, e); 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 private class PackageDeleteObserver extends IPackageDeleteObserver.Stub { packageDeleted(String packageName, int returnCode)310 public void packageDeleted(String packageName, int returnCode) { 311 Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE); 312 msg.arg1 = returnCode; 313 msg.obj = packageName; 314 mHandler.sendMessage(msg); 315 } 316 } 317 setResultAndFinish()318 public void setResultAndFinish() { 319 setResult(mResultCode); 320 finish(); 321 } 322 initView()323 private void initView() { 324 if (mIsViewInitialized) { 325 return; 326 } 327 mIsViewInitialized = true; 328 329 // We set the window background to translucent in constructor, revert this 330 TypedValue attribute = new TypedValue(); 331 getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true); 332 if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT && 333 attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) { 334 getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data)); 335 } else { 336 getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId, 337 getTheme())); 338 } 339 340 getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true); 341 getWindow().setNavigationBarColor(attribute.data); 342 343 getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true); 344 getWindow().setStatusBarColor(attribute.data); 345 346 boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); 347 setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title); 348 349 getFragmentManager().beginTransaction() 350 .add(android.R.id.content, new UninstallAppProgressFragment(), FRAGMENT_TAG) 351 .commitNowAllowingStateLoss(); 352 } 353 354 @Override dispatchKeyEvent(KeyEvent ev)355 public boolean dispatchKeyEvent(KeyEvent ev) { 356 if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK) { 357 if (mResultCode == -1) { 358 // Ignore back key when installation is in progress 359 return true; 360 } else { 361 // If installation is done, just set the result code 362 setResult(mResultCode); 363 } 364 } 365 return super.dispatchKeyEvent(ev); 366 } 367 getProgressFragment()368 private ProgressFragment getProgressFragment() { 369 return (ProgressFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG); 370 } 371 372 public interface ProgressFragment { setUsersButtonVisible(boolean visible)373 void setUsersButtonVisible(boolean visible); setDeviceManagerButtonVisible(boolean visible)374 void setDeviceManagerButtonVisible(boolean visible); showCompletion(CharSequence statusText)375 void showCompletion(CharSequence statusText); 376 } 377 } 378