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