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