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