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;
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.content.res.Configuration;
31 import android.graphics.Color;
32 import android.graphics.drawable.ColorDrawable;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.provider.Settings;
42 import android.util.Log;
43 import android.util.TypedValue;
44 import android.view.KeyEvent;
45 import android.view.View;
46 import android.view.View.OnClickListener;
47 import android.widget.Button;
48 import android.widget.ProgressBar;
49 import android.widget.TextView;
50 import android.widget.Toast;
51 
52 import java.util.List;
53 
54 /**
55  * This activity corresponds to a download progress screen that is displayed
56  * when an application is uninstalled. The result of the application uninstall
57  * is indicated in the result code that gets set to 0 or 1. The application gets launched
58  * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects
59  * the application object of the application to uninstall.
60  */
61 public class UninstallAppProgress extends Activity implements OnClickListener {
62     private final String TAG="UninstallAppProgress";
63 
64     private ApplicationInfo mAppInfo;
65     private boolean mAllUsers;
66     private UserHandle mUser;
67     private IBinder mCallback;
68 
69     private Button mOkButton;
70     private Button mDeviceManagerButton;
71     private Button mUsersButton;
72     private volatile int mResultCode = -1;
73 
74     /**
75      * If initView was called. We delay this call to not have to call it at all if the uninstall is
76      * quick
77      */
78     private boolean mIsViewInitialized;
79 
80     /** Amount of time to wait until we show the UI */
81     private static final int QUICK_INSTALL_DELAY_MILLIS = 500;
82 
83     private static final int UNINSTALL_COMPLETE = 1;
84     private static final int UNINSTALL_IS_SLOW = 2;
85 
isProfileOfOrSame(UserManager userManager, int userId, int profileId)86     private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
87         if (userId == profileId) {
88             return true;
89         }
90         UserInfo parentUser = userManager.getProfileParent(profileId);
91         return parentUser != null && parentUser.id == userId;
92     }
93 
94     private Handler mHandler = new Handler() {
95         public void handleMessage(Message msg) {
96             if (isFinishing() || isDestroyed()) {
97                 return;
98             }
99 
100             switch (msg.what) {
101                 case UNINSTALL_IS_SLOW:
102                     initView();
103                     break;
104                 case UNINSTALL_COMPLETE:
105                     mHandler.removeMessages(UNINSTALL_IS_SLOW);
106 
107                     if (msg.arg1 != PackageManager.DELETE_SUCCEEDED) {
108                         initView();
109                     }
110 
111                     mResultCode = msg.arg1;
112                     final String packageName = (String) msg.obj;
113 
114                     if (mCallback != null) {
115                         final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
116                                 .asInterface(mCallback);
117                         try {
118                             observer.onPackageDeleted(mAppInfo.packageName, mResultCode,
119                                     packageName);
120                         } catch (RemoteException ignored) {
121                         }
122                         finish();
123                         return;
124                     }
125 
126                     if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
127                         Intent result = new Intent();
128                         result.putExtra(Intent.EXTRA_INSTALL_RESULT, mResultCode);
129                         setResult(mResultCode == PackageManager.DELETE_SUCCEEDED
130                                 ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
131                                         result);
132                         finish();
133                         return;
134                     }
135 
136                     // Update the status text
137                     final String statusText;
138                     switch (msg.arg1) {
139                         case PackageManager.DELETE_SUCCEEDED:
140                             statusText = getString(R.string.uninstall_done);
141                             // Show a Toast and finish the activity
142                             Context ctx = getBaseContext();
143                             Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show();
144                             setResultAndFinish(mResultCode);
145                             return;
146                         case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
147                             UserManager userManager =
148                                     (UserManager) getSystemService(Context.USER_SERVICE);
149                             IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
150                                     ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
151                             // Find out if the package is an active admin for some non-current user.
152                             int myUserId = UserHandle.myUserId();
153                             UserInfo otherBlockingUser = null;
154                             for (UserInfo user : userManager.getUsers()) {
155                                 // We only catch the case when the user in question is neither the
156                                 // current user nor its profile.
157                                 if (isProfileOfOrSame(userManager, myUserId, user.id)) continue;
158 
159                                 try {
160                                     if (dpm.packageHasActiveAdmins(packageName, user.id)) {
161                                         otherBlockingUser = user;
162                                         break;
163                                     }
164                                 } catch (RemoteException e) {
165                                     Log.e(TAG, "Failed to talk to package manager", e);
166                                 }
167                             }
168                             if (otherBlockingUser == null) {
169                                 Log.d(TAG, "Uninstall failed because " + packageName
170                                         + " is a device admin");
171                                 mDeviceManagerButton.setVisibility(View.VISIBLE);
172                                 statusText = getString(
173                                         R.string.uninstall_failed_device_policy_manager);
174                             } else {
175                                 Log.d(TAG, "Uninstall failed because " + packageName
176                                         + " is a device admin of user " + otherBlockingUser);
177                                 mDeviceManagerButton.setVisibility(View.GONE);
178                                 statusText = String.format(
179                                         getString(R.string.uninstall_failed_device_policy_manager_of_user),
180                                         otherBlockingUser.name);
181                             }
182                             break;
183                         }
184                         case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
185                             UserManager userManager =
186                                     (UserManager) getSystemService(Context.USER_SERVICE);
187                             IPackageManager packageManager = IPackageManager.Stub.asInterface(
188                                     ServiceManager.getService("package"));
189                             List<UserInfo> users = userManager.getUsers();
190                             int blockingUserId = UserHandle.USER_NULL;
191                             for (int i = 0; i < users.size(); ++i) {
192                                 final UserInfo user = users.get(i);
193                                 try {
194                                     if (packageManager.getBlockUninstallForUser(packageName,
195                                             user.id)) {
196                                         blockingUserId = user.id;
197                                         break;
198                                     }
199                                 } catch (RemoteException e) {
200                                     // Shouldn't happen.
201                                     Log.e(TAG, "Failed to talk to package manager", e);
202                                 }
203                             }
204                             int myUserId = UserHandle.myUserId();
205                             if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
206                                 mDeviceManagerButton.setVisibility(View.VISIBLE);
207                             } else {
208                                 mDeviceManagerButton.setVisibility(View.GONE);
209                                 mUsersButton.setVisibility(View.VISIBLE);
210                             }
211                             // TODO: b/25442806
212                             if (blockingUserId == UserHandle.USER_SYSTEM) {
213                                 statusText = getString(R.string.uninstall_blocked_device_owner);
214                             } else if (blockingUserId == UserHandle.USER_NULL) {
215                                 Log.d(TAG, "Uninstall failed for " + packageName + " with code "
216                                         + msg.arg1 + " no blocking user");
217                                 statusText = getString(R.string.uninstall_failed);
218                             } else {
219                                 statusText = mAllUsers
220                                         ? getString(R.string.uninstall_all_blocked_profile_owner) :
221                                         getString(R.string.uninstall_blocked_profile_owner);
222                             }
223                             break;
224                         }
225                         default:
226                             Log.d(TAG, "Uninstall failed for " + packageName + " with code "
227                                     + msg.arg1);
228                             statusText = getString(R.string.uninstall_failed);
229                             break;
230                     }
231                     findViewById(R.id.progress_view).setVisibility(View.GONE);
232                     findViewById(R.id.status_view).setVisibility(View.VISIBLE);
233                     ((TextView)findViewById(R.id.status_text)).setText(statusText);
234                     findViewById(R.id.ok_panel).setVisibility(View.VISIBLE);
235                     break;
236                 default:
237                     break;
238             }
239         }
240     };
241 
242     @Override
onCreate(Bundle icicle)243     public void onCreate(Bundle icicle) {
244         super.onCreate(icicle);
245 
246         Intent intent = getIntent();
247         mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
248         mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
249 
250         // This currently does not support going through a onDestroy->onCreate cycle. Hence if that
251         // happened, just fail the operation for mysterious reasons.
252         if (icicle != null) {
253             mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
254 
255             if (mCallback != null) {
256                 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
257                         .asInterface(mCallback);
258                 try {
259                     observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
260                 } catch (RemoteException ignored) {
261                 }
262                 finish();
263             } else {
264                 setResultAndFinish(mResultCode);
265             }
266 
267             return;
268         }
269 
270         mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
271         if (mAllUsers && !UserManager.get(this).isAdminUser()) {
272             throw new SecurityException("Only admin user can request uninstall for all users");
273         }
274         mUser = intent.getParcelableExtra(Intent.EXTRA_USER);
275         if (mUser == null) {
276             mUser = android.os.Process.myUserHandle();
277         } else {
278             UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
279             List<UserHandle> profiles = userManager.getUserProfiles();
280             if (!profiles.contains(mUser)) {
281                 throw new SecurityException("User " + android.os.Process.myUserHandle() + " can't "
282                         + "request uninstall for user " + mUser);
283             }
284         }
285 
286         PackageDeleteObserver observer = new PackageDeleteObserver();
287 
288         // Make window transparent until initView is called. In many cases we can avoid showing the
289         // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
290         // it, it just looks like a flicker.
291         getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
292         getWindow().setStatusBarColor(Color.TRANSPARENT);
293         getWindow().setNavigationBarColor(Color.TRANSPARENT);
294 
295         getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
296                 mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, mUser.getIdentifier());
297 
298         mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
299                 QUICK_INSTALL_DELAY_MILLIS);
300     }
301 
302     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
packageDeleted(String packageName, int returnCode)303         public void packageDeleted(String packageName, int returnCode) {
304             Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
305             msg.arg1 = returnCode;
306             msg.obj = packageName;
307             mHandler.sendMessage(msg);
308         }
309     }
310 
setResultAndFinish(int retCode)311     void setResultAndFinish(int retCode) {
312         setResult(retCode);
313         finish();
314     }
315 
initView()316     public void initView() {
317         if (mIsViewInitialized) {
318             return;
319         }
320         mIsViewInitialized = true;
321 
322         // We set the window background to translucent in constructor, revert this
323         TypedValue attribute = new TypedValue();
324         getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true);
325         if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
326                 attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) {
327             getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data));
328         } else {
329             getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId,
330                     getTheme()));
331         }
332 
333         getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true);
334         getWindow().setNavigationBarColor(attribute.data);
335 
336         getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true);
337         getWindow().setStatusBarColor(attribute.data);
338 
339         boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
340         setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title);
341 
342         setContentView(R.layout.uninstall_progress);
343         // Initialize views
344         View snippetView = findViewById(R.id.app_snippet);
345         PackageUtil.initSnippetForInstalledApp(this, mAppInfo, snippetView);
346         mDeviceManagerButton = (Button) findViewById(R.id.device_manager_button);
347         mUsersButton = (Button) findViewById(R.id.users_button);
348         mDeviceManagerButton.setVisibility(View.GONE);
349         mDeviceManagerButton.setOnClickListener(new OnClickListener() {
350             @Override
351             public void onClick(View v) {
352                 Intent intent = new Intent();
353                 intent.setClassName("com.android.settings",
354                         "com.android.settings.Settings$DeviceAdminSettingsActivity");
355                 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
356                 startActivity(intent);
357                 finish();
358             }
359         });
360         mUsersButton.setVisibility(View.GONE);
361         mUsersButton.setOnClickListener(new OnClickListener() {
362             @Override
363             public void onClick(View v) {
364                 Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
365                 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
366                 startActivity(intent);
367                 finish();
368             }
369         });
370         // Hide button till progress is being displayed
371         mOkButton = (Button) findViewById(R.id.ok_button);
372         mOkButton.setOnClickListener(this);
373     }
374 
onClick(View v)375     public void onClick(View v) {
376         if(v == mOkButton) {
377             Log.i(TAG, "Finished uninstalling pkg: " + mAppInfo.packageName);
378             setResultAndFinish(mResultCode);
379         }
380     }
381 
382     @Override
dispatchKeyEvent(KeyEvent ev)383     public boolean dispatchKeyEvent(KeyEvent ev) {
384         if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK) {
385             if (mResultCode == -1) {
386                 // Ignore back key when installation is in progress
387                 return true;
388             } else {
389                 // If installation is done, just set the result code
390                 setResult(mResultCode);
391             }
392         }
393         return super.dispatchKeyEvent(ev);
394     }
395 }
396