• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.packageinstaller;
18 
19 import android.annotation.NonNull;
20 import android.app.Notification;
21 import android.app.NotificationChannel;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.admin.IDevicePolicyManager;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.IPackageManager;
30 import android.content.pm.PackageInstaller;
31 import android.content.pm.PackageManager;
32 import android.content.pm.UserInfo;
33 import android.graphics.drawable.Icon;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.provider.Settings;
39 import android.util.Log;
40 import android.widget.Toast;
41 
42 import java.util.List;
43 
44 /**
45  * Finish an uninstallation and show Toast on success or failure notification.
46  */
47 public class UninstallFinish extends BroadcastReceiver {
48     private static final String LOG_TAG = UninstallFinish.class.getSimpleName();
49 
50     private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall failure";
51 
52     static final String EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID";
53     static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
54 
55     @Override
onReceive(Context context, Intent intent)56     public void onReceive(Context context, Intent intent) {
57         int returnCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
58 
59         Log.i(LOG_TAG, "Uninstall finished extras=" + intent.getExtras());
60 
61         if (returnCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
62             context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
63             return;
64         }
65 
66         int uninstallId = intent.getIntExtra(EXTRA_UNINSTALL_ID, 0);
67         ApplicationInfo appInfo = intent.getParcelableExtra(
68                 PackageUtil.INTENT_ATTR_APPLICATION_INFO);
69         String appLabel = intent.getStringExtra(EXTRA_APP_LABEL);
70         boolean allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
71 
72         NotificationManager notificationManager =
73                 context.getSystemService(NotificationManager.class);
74         UserManager userManager = context.getSystemService(UserManager.class);
75 
76         NotificationChannel uninstallFailureChannel = new NotificationChannel(
77                 UNINSTALL_FAILURE_CHANNEL,
78                 context.getString(R.string.uninstall_failure_notification_channel),
79                 NotificationManager.IMPORTANCE_DEFAULT);
80         notificationManager.createNotificationChannel(uninstallFailureChannel);
81 
82         Notification.Builder uninstallFailedNotification = new Notification.Builder(context,
83                 UNINSTALL_FAILURE_CHANNEL);
84 
85         switch (returnCode) {
86             case PackageInstaller.STATUS_SUCCESS:
87                 notificationManager.cancel(uninstallId);
88 
89                 Toast.makeText(context, context.getString(R.string.uninstall_done_app, appLabel),
90                         Toast.LENGTH_LONG).show();
91                 return;
92             case PackageInstaller.STATUS_FAILURE_BLOCKED: {
93                 int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
94 
95                 switch (legacyStatus) {
96                     case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
97                         IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
98                                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
99                         // Find out if the package is an active admin for some non-current user.
100                         int myUserId = UserHandle.myUserId();
101                         UserInfo otherBlockingUser = null;
102                         for (UserInfo user : userManager.getUsers()) {
103                             // We only catch the case when the user in question is neither the
104                             // current user nor its profile.
105                             if (isProfileOfOrSame(userManager, myUserId, user.id)) {
106                                 continue;
107                             }
108 
109                             try {
110                                 if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) {
111                                     otherBlockingUser = user;
112                                     break;
113                                 }
114                             } catch (RemoteException e) {
115                                 Log.e(LOG_TAG, "Failed to talk to package manager", e);
116                             }
117                         }
118                         if (otherBlockingUser == null) {
119                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
120                                     + " is a device admin");
121 
122                             addDeviceManagerButton(context, uninstallFailedNotification);
123                             setBigText(uninstallFailedNotification, context.getString(
124                                     R.string.uninstall_failed_device_policy_manager));
125                         } else {
126                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
127                                     + " is a device admin of user " + otherBlockingUser);
128 
129                             setBigText(uninstallFailedNotification, String.format(context.getString(
130                                     R.string.uninstall_failed_device_policy_manager_of_user),
131                                     otherBlockingUser.name));
132                         }
133                         break;
134                     }
135                     case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
136                         IPackageManager packageManager = IPackageManager.Stub.asInterface(
137                                 ServiceManager.getService("package"));
138 
139                         List<UserInfo> users = userManager.getUsers();
140                         int blockingUserId = UserHandle.USER_NULL;
141                         for (int i = 0; i < users.size(); ++i) {
142                             final UserInfo user = users.get(i);
143                             try {
144                                 if (packageManager.getBlockUninstallForUser(appInfo.packageName,
145                                         user.id)) {
146                                     blockingUserId = user.id;
147                                     break;
148                                 }
149                             } catch (RemoteException e) {
150                                 // Shouldn't happen.
151                                 Log.e(LOG_TAG, "Failed to talk to package manager", e);
152                             }
153                         }
154 
155                         int myUserId = UserHandle.myUserId();
156                         if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
157                             addDeviceManagerButton(context, uninstallFailedNotification);
158                         } else {
159                             addManageUsersButton(context, uninstallFailedNotification);
160                         }
161 
162                         if (blockingUserId == UserHandle.USER_NULL) {
163                             Log.d(LOG_TAG,
164                                     "Uninstall failed for " + appInfo.packageName + " with code "
165                                             + returnCode + " no blocking user");
166                         } else if (blockingUserId == UserHandle.USER_SYSTEM) {
167                             setBigText(uninstallFailedNotification,
168                                     context.getString(R.string.uninstall_blocked_device_owner));
169                         } else {
170                             if (allUsers) {
171                                 setBigText(uninstallFailedNotification,
172                                         context.getString(
173                                                 R.string.uninstall_all_blocked_profile_owner));
174                             } else {
175                                 setBigText(uninstallFailedNotification, context.getString(
176                                         R.string.uninstall_blocked_profile_owner));
177                             }
178                         }
179                         break;
180                     }
181                     default:
182                         Log.d(LOG_TAG, "Uninstall blocked for " + appInfo.packageName
183                                 + " with legacy code " + legacyStatus);
184                 } break;
185             }
186             default:
187                 Log.d(LOG_TAG, "Uninstall failed for " + appInfo.packageName + " with code "
188                         + returnCode);
189                 break;
190         }
191 
192         uninstallFailedNotification.setContentTitle(
193                 context.getString(R.string.uninstall_failed_app, appLabel));
194         uninstallFailedNotification.setOngoing(false);
195         uninstallFailedNotification.setSmallIcon(R.drawable.ic_error);
196         notificationManager.notify(uninstallId, uninstallFailedNotification.build());
197     }
198 
199     /**
200      * Is a profile part of a user?
201      *
202      * @param userManager The user manager
203      * @param userId The id of the user
204      * @param profileId The id of the profile
205      *
206      * @return If the profile is part of the user or the profile parent of the user
207      */
isProfileOfOrSame(@onNull UserManager userManager, int userId, int profileId)208     private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) {
209         if (userId == profileId) {
210             return true;
211         }
212 
213         UserInfo parentUser = userManager.getProfileParent(profileId);
214         return parentUser != null && parentUser.id == userId;
215     }
216 
217     /**
218      * Set big text for the notification.
219      *
220      * @param builder The builder of the notification
221      * @param text The text to set.
222      */
setBigText(@onNull Notification.Builder builder, @NonNull CharSequence text)223     private void setBigText(@NonNull Notification.Builder builder,
224             @NonNull CharSequence text) {
225         builder.setStyle(new Notification.BigTextStyle().bigText(text));
226     }
227 
228     /**
229      * Add a button to the notification that links to the user management.
230      *
231      * @param context The context the notification is created in
232      * @param builder The builder of the notification
233      */
addManageUsersButton(@onNull Context context, @NonNull Notification.Builder builder)234     private void addManageUsersButton(@NonNull Context context,
235             @NonNull Notification.Builder builder) {
236         Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
237         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
238 
239         builder.addAction((new Notification.Action.Builder(
240                 Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
241                 context.getString(R.string.manage_users),
242                 PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
243                         | PendingIntent.FLAG_IMMUTABLE))).build());
244     }
245 
246     /**
247      * Add a button to the notification that links to the device policy management.
248      *
249      * @param context The context the notification is created in
250      * @param builder The builder of the notification
251      */
addDeviceManagerButton(@onNull Context context, @NonNull Notification.Builder builder)252     private void addDeviceManagerButton(@NonNull Context context,
253             @NonNull Notification.Builder builder) {
254         Intent intent = new Intent();
255         intent.setClassName("com.android.settings",
256                 "com.android.settings.Settings$DeviceAdminSettingsActivity");
257         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
258 
259         builder.addAction((new Notification.Action.Builder(
260                 Icon.createWithResource(context, R.drawable.ic_lock),
261                 context.getString(R.string.manage_device_administrators),
262                 PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
263                         | PendingIntent.FLAG_IMMUTABLE))).build());
264     }
265 }
266