summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller/television/UninstallAppProgress.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/packageinstaller/television/UninstallAppProgress.java')
-rwxr-xr-xsrc/com/android/packageinstaller/television/UninstallAppProgress.java377
1 files changed, 377 insertions, 0 deletions
diff --git a/src/com/android/packageinstaller/television/UninstallAppProgress.java b/src/com/android/packageinstaller/television/UninstallAppProgress.java
new file mode 100755
index 00000000..a4f217c4
--- /dev/null
+++ b/src/com/android/packageinstaller/television/UninstallAppProgress.java
@@ -0,0 +1,377 @@
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package com.android.packageinstaller.television;
+
+import android.app.Activity;
+import android.app.admin.IDevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDeleteObserver2;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.widget.Toast;
+
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * This activity corresponds to a download progress screen that is displayed
+ * when an application is uninstalled. The result of the application uninstall
+ * is indicated in the result code that gets set to 0 or 1. The application gets launched
+ * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects
+ * the application object of the application to uninstall.
+ */
+public class UninstallAppProgress extends Activity {
+ private static final String TAG = "UninstallAppProgress";
+
+ private static final String FRAGMENT_TAG = "progress_fragment";
+
+ private ApplicationInfo mAppInfo;
+ private boolean mAllUsers;
+ private IBinder mCallback;
+
+ private volatile int mResultCode = -1;
+
+ /**
+ * If initView was called. We delay this call to not have to call it at all if the uninstall is
+ * quick
+ */
+ private boolean mIsViewInitialized;
+
+ /** Amount of time to wait until we show the UI */
+ private static final int QUICK_INSTALL_DELAY_MILLIS = 500;
+
+ private static final int UNINSTALL_COMPLETE = 1;
+ private static final int UNINSTALL_IS_SLOW = 2;
+
+ private Handler mHandler = new MessageHandler(this);
+
+ private static class MessageHandler extends Handler {
+ private final WeakReference<UninstallAppProgress> mActivity;
+
+ public MessageHandler(UninstallAppProgress activity) {
+ mActivity = new WeakReference<>(activity);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ UninstallAppProgress activity = mActivity.get();
+ if (activity != null) {
+ activity.handleMessage(msg);
+ }
+ }
+ }
+
+ private void handleMessage(Message msg) {
+ if (isFinishing() || isDestroyed()) {
+ return;
+ }
+
+ switch (msg.what) {
+ case UNINSTALL_IS_SLOW:
+ initView();
+ break;
+ case UNINSTALL_COMPLETE:
+ mHandler.removeMessages(UNINSTALL_IS_SLOW);
+
+ if (msg.arg1 != PackageManager.DELETE_SUCCEEDED) {
+ initView();
+ }
+
+ mResultCode = msg.arg1;
+ final String packageName = (String) msg.obj;
+
+ if (mCallback != null) {
+ final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
+ .asInterface(mCallback);
+ try {
+ observer.onPackageDeleted(mAppInfo.packageName, mResultCode,
+ packageName);
+ } catch (RemoteException ignored) {
+ }
+ finish();
+ return;
+ }
+
+ if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
+ Intent result = new Intent();
+ result.putExtra(Intent.EXTRA_INSTALL_RESULT, mResultCode);
+ setResult(mResultCode == PackageManager.DELETE_SUCCEEDED
+ ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
+ result);
+ finish();
+ return;
+ }
+
+ // Update the status text
+ final String statusText;
+ switch (msg.arg1) {
+ case PackageManager.DELETE_SUCCEEDED:
+ statusText = getString(R.string.uninstall_done);
+ // Show a Toast and finish the activity
+ Context ctx = getBaseContext();
+ Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show();
+ setResultAndFinish();
+ return;
+ case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
+ UserManager userManager =
+ (UserManager) getSystemService(Context.USER_SERVICE);
+ IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+ // Find out if the package is an active admin for some non-current user.
+ int myUserId = UserHandle.myUserId();
+ UserInfo otherBlockingUser = null;
+ for (UserInfo user : userManager.getUsers()) {
+ // We only catch the case when the user in question is neither the
+ // current user nor its profile.
+ if (isProfileOfOrSame(userManager, myUserId, user.id)) continue;
+
+ try {
+ if (dpm.packageHasActiveAdmins(packageName, user.id)) {
+ otherBlockingUser = user;
+ break;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to talk to package manager", e);
+ }
+ }
+ if (otherBlockingUser == null) {
+ Log.d(TAG, "Uninstall failed because " + packageName
+ + " is a device admin");
+ getProgressFragment().setDeviceManagerButtonVisible(true);
+ statusText = getString(
+ R.string.uninstall_failed_device_policy_manager);
+ } else {
+ Log.d(TAG, "Uninstall failed because " + packageName
+ + " is a device admin of user " + otherBlockingUser);
+ getProgressFragment().setDeviceManagerButtonVisible(false);
+ statusText = String.format(
+ getString(R.string.uninstall_failed_device_policy_manager_of_user),
+ otherBlockingUser.name);
+ }
+ break;
+ }
+ case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
+ UserManager userManager =
+ (UserManager) getSystemService(Context.USER_SERVICE);
+ IPackageManager packageManager = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ List<UserInfo> users = userManager.getUsers();
+ int blockingUserId = UserHandle.USER_NULL;
+ for (int i = 0; i < users.size(); ++i) {
+ final UserInfo user = users.get(i);
+ try {
+ if (packageManager.getBlockUninstallForUser(packageName,
+ user.id)) {
+ blockingUserId = user.id;
+ break;
+ }
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ Log.e(TAG, "Failed to talk to package manager", e);
+ }
+ }
+ int myUserId = UserHandle.myUserId();
+ if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+ getProgressFragment().setDeviceManagerButtonVisible(true);
+ } else {
+ getProgressFragment().setDeviceManagerButtonVisible(false);
+ getProgressFragment().setUsersButtonVisible(true);
+ }
+ // TODO: b/25442806
+ if (blockingUserId == UserHandle.USER_SYSTEM) {
+ statusText = getString(R.string.uninstall_blocked_device_owner);
+ } else if (blockingUserId == UserHandle.USER_NULL) {
+ Log.d(TAG, "Uninstall failed for " + packageName + " with code "
+ + msg.arg1 + " no blocking user");
+ statusText = getString(R.string.uninstall_failed);
+ } else {
+ statusText = mAllUsers
+ ? getString(R.string.uninstall_all_blocked_profile_owner) :
+ getString(R.string.uninstall_blocked_profile_owner);
+ }
+ break;
+ }
+ default:
+ Log.d(TAG, "Uninstall failed for " + packageName + " with code "
+ + msg.arg1);
+ statusText = getString(R.string.uninstall_failed);
+ break;
+ }
+ getProgressFragment().showCompletion(statusText);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
+ if (userId == profileId) {
+ return true;
+ }
+ UserInfo parentUser = userManager.getProfileParent(profileId);
+ return parentUser != null && parentUser.id == userId;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Intent intent = getIntent();
+ mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
+ mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+
+ // This currently does not support going through a onDestroy->onCreate cycle. Hence if that
+ // happened, just fail the operation for mysterious reasons.
+ if (icicle != null) {
+ mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
+
+ if (mCallback != null) {
+ final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
+ .asInterface(mCallback);
+ try {
+ observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
+ } catch (RemoteException ignored) {
+ }
+ finish();
+ } else {
+ setResultAndFinish();
+ }
+
+ return;
+ }
+
+ mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (user == null) {
+ user = android.os.Process.myUserHandle();
+ }
+
+ PackageDeleteObserver observer = new PackageDeleteObserver();
+
+ // Make window transparent until initView is called. In many cases we can avoid showing the
+ // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
+ // it, it just looks like a flicker.
+ getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
+ getWindow().setNavigationBarColor(Color.TRANSPARENT);
+
+ try {
+ getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
+ mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, user.getIdentifier());
+ } catch (IllegalArgumentException e) {
+ // Couldn't find the package, no need to call uninstall.
+ Log.w(TAG, "Could not find package, not deleting " + mAppInfo.packageName, e);
+ }
+
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
+ QUICK_INSTALL_DELAY_MILLIS);
+ }
+
+ public ApplicationInfo getAppInfo() {
+ return mAppInfo;
+ }
+
+ private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
+ public void packageDeleted(String packageName, int returnCode) {
+ Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
+ msg.arg1 = returnCode;
+ msg.obj = packageName;
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ public void setResultAndFinish() {
+ setResult(mResultCode);
+ finish();
+ }
+
+ private void initView() {
+ if (mIsViewInitialized) {
+ return;
+ }
+ mIsViewInitialized = true;
+
+ // We set the window background to translucent in constructor, revert this
+ TypedValue attribute = new TypedValue();
+ getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true);
+ if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data));
+ } else {
+ getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId,
+ getTheme()));
+ }
+
+ getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true);
+ getWindow().setNavigationBarColor(attribute.data);
+
+ getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true);
+ getWindow().setStatusBarColor(attribute.data);
+
+ boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+ setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title);
+
+ getFragmentManager().beginTransaction()
+ .add(android.R.id.content, new UninstallAppProgressFragment(), FRAGMENT_TAG)
+ .commitNowAllowingStateLoss();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent ev) {
+ if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (mResultCode == -1) {
+ // Ignore back key when installation is in progress
+ return true;
+ } else {
+ // If installation is done, just set the result code
+ setResult(mResultCode);
+ }
+ }
+ return super.dispatchKeyEvent(ev);
+ }
+
+ private ProgressFragment getProgressFragment() {
+ return (ProgressFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
+ }
+
+ public interface ProgressFragment {
+ void setUsersButtonVisible(boolean visible);
+ void setDeviceManagerButtonVisible(boolean visible);
+ void showCompletion(CharSequence statusText);
+ }
+}