• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014, 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.managedprovisioning.task;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.ComponentInfo;
25 import android.content.pm.IPackageDeleteObserver;
26 import android.content.pm.IPackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.util.Xml;
33 
34 import com.android.internal.util.FastXmlSerializer;
35 import com.android.managedprovisioning.ProvisionLogger;
36 import com.android.managedprovisioning.R;
37 
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Set;
48 import java.util.concurrent.atomic.AtomicInteger;
49 
50 import org.xmlpull.v1.XmlPullParser;
51 import org.xmlpull.v1.XmlPullParserException;
52 import org.xmlpull.v1.XmlSerializer;
53 
54 /**
55  * Removes all system apps with a launcher that are not required.
56  * Also disables sharing via Bluetooth, and components that listen to
57  * ACTION_INSTALL_SHORTCUT.
58  * This class is called a first time when a user is created, but also after a system update.
59  * In this case, it checks if the system apps that have been added need to be disabled.
60  */
61 public class DeleteNonRequiredAppsTask {
62     private final Callback mCallback;
63     private final Context mContext;
64     private final IPackageManager mIpm;
65     private final String mMdmPackageName;
66     private final PackageManager mPm;
67     private final int mReqAppsList;
68     private final int mVendorReqAppsList;
69     private final int mUserId;
70     private final boolean mNewProfile; // If we are provisioning a new managed profile/device.
71     private final boolean mDisableInstallShortcutListenersAndTelecom;
72 
73     private static final String TAG_SYSTEM_APPS = "system-apps";
74     private static final String TAG_PACKAGE_LIST_ITEM = "item";
75     private static final String ATTR_VALUE = "value";
76 
DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int userId, int requiredAppsList, int vendorRequiredAppsList, boolean newProfile, boolean disableInstallShortcutListenersAndTelecom, Callback callback)77     public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int userId,
78             int requiredAppsList, int vendorRequiredAppsList, boolean newProfile,
79             boolean disableInstallShortcutListenersAndTelecom, Callback callback) {
80         mCallback = callback;
81         mContext = context;
82         mMdmPackageName = mdmPackageName;
83         mUserId = userId;
84         mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
85         mPm = context.getPackageManager();
86         mReqAppsList = requiredAppsList;
87         mVendorReqAppsList = vendorRequiredAppsList;
88         mNewProfile = newProfile;
89         mDisableInstallShortcutListenersAndTelecom = disableInstallShortcutListenersAndTelecom;
90     }
91 
run()92     public void run() {
93         if (mNewProfile) {
94             disableBluetoothSharing();
95         }
96         deleteNonRequiredApps();
97     }
98 
99     /**
100      * Returns if this task should be run on OTA.
101      * This is indicated by the presence of the system apps file.
102      */
shouldDeleteNonRequiredApps(Context context, int userId)103     public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
104         return getSystemAppsFile(context, userId).exists();
105     }
106 
disableBluetoothSharing()107     private void disableBluetoothSharing() {
108         ProvisionLogger.logd("Disabling Bluetooth sharing.");
109         disableComponent(new ComponentName("com.android.bluetooth",
110                 "com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
111     }
112 
deleteNonRequiredApps()113     private void deleteNonRequiredApps() {
114         ProvisionLogger.logd("Deleting non required apps.");
115 
116         File systemAppsFile = getSystemAppsFile(mContext, mUserId);
117         systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
118 
119         Set<String> currentApps = getCurrentSystemApps();
120         Set<String> previousApps;
121         if (mNewProfile) {
122             // Provisioning case.
123 
124             // If this userId was a managed profile before, file may exist. In this case, we ignore
125             // what is in this file.
126             previousApps = new HashSet<String>();
127         } else {
128             // OTA case.
129 
130             if (!systemAppsFile.exists()) {
131                 // Error, this task should not have been run.
132                 ProvisionLogger.loge("No system apps list found for user " + mUserId);
133                 mCallback.onError();
134                 return;
135             }
136 
137             previousApps = readSystemApps(systemAppsFile);
138         }
139         writeSystemApps(currentApps, systemAppsFile);
140         Set<String> newApps = currentApps;
141         newApps.removeAll(previousApps);
142 
143         if (mDisableInstallShortcutListenersAndTelecom) {
144             Intent actionShortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
145             if (previousApps.isEmpty()) {
146                 // Here, all the apps are in newApps.
147                 // It is faster to do it this way than to go through all the apps one by one.
148                 disableReceivers(actionShortcut);
149             } else {
150                 // Here, all the apps are not in newApps. So we have to go through all the new
151                 // apps one by one.
152                 for (String newApp : newApps) {
153                     actionShortcut.setPackage(newApp);
154                     disableReceivers(actionShortcut);
155                 }
156             }
157         }
158         Set<String> packagesToDelete = newApps;
159         packagesToDelete.removeAll(getRequiredApps());
160         packagesToDelete.retainAll(getCurrentAppsWithLauncher());
161         // com.android.server.telecom should not handle CALL intents in the managed profile.
162         if (mDisableInstallShortcutListenersAndTelecom && mNewProfile) {
163             packagesToDelete.add("com.android.server.telecom");
164         }
165         if (packagesToDelete.isEmpty()) {
166             mCallback.onSuccess();
167             return;
168         }
169         PackageDeleteObserver packageDeleteObserver =
170                 new PackageDeleteObserver(packagesToDelete.size());
171         for (String packageName : packagesToDelete) {
172             try {
173                 mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId,
174                         PackageManager.DELETE_SYSTEM_APP);
175             } catch (RemoteException neverThrown) {
176                     // Never thrown, as we are making local calls.
177                 ProvisionLogger.loge("This should not happen.", neverThrown);
178             }
179         }
180     }
181 
getSystemAppsFile(Context context, int userId)182     static File getSystemAppsFile(Context context, int userId) {
183         return new File(context.getFilesDir() + File.separator + "system_apps"
184                 + File.separator + "user" + userId + ".xml");
185     }
186 
187     /**
188      * Disable all components that can handle the specified broadcast intent.
189      */
disableReceivers(Intent intent)190     private void disableReceivers(Intent intent) {
191         List<ResolveInfo> receivers = mPm.queryBroadcastReceivers(intent, 0, mUserId);
192         for (ResolveInfo ri : receivers) {
193             // One of ri.activityInfo, ri.serviceInfo, ri.providerInfo is not null. Let's find which
194             // one.
195             ComponentInfo ci;
196             if (ri.activityInfo != null) {
197                 ci = ri.activityInfo;
198             } else if (ri.serviceInfo != null) {
199                 ci = ri.serviceInfo;
200             } else {
201                 ci = ri.providerInfo;
202             }
203             disableComponent(new ComponentName(ci.packageName, ci.name));
204         }
205     }
206 
disableComponent(ComponentName toDisable)207     private void disableComponent(ComponentName toDisable) {
208         try {
209             mIpm.setComponentEnabledSetting(toDisable,
210                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP,
211                     mUserId);
212         } catch (RemoteException neverThrown) {
213             ProvisionLogger.loge("This should not happen.", neverThrown);
214         } catch (Exception e) {
215             ProvisionLogger.logw("Component not found, not disabling it: "
216                 + toDisable.toShortString());
217         }
218     }
219 
220     /**
221      * Returns the set of package names of apps that are in the system image,
222      * whether they have been deleted or not.
223      */
getCurrentSystemApps()224     private Set<String> getCurrentSystemApps() {
225         Set<String> apps = new HashSet<String>();
226         List<ApplicationInfo> aInfos = null;
227         try {
228             aInfos = mIpm.getInstalledApplications(
229                     PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList();
230         } catch (RemoteException neverThrown) {
231             // Never thrown, as we are making local calls.
232             ProvisionLogger.loge("This should not happen.", neverThrown);
233         }
234         for (ApplicationInfo aInfo : aInfos) {
235             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
236                 apps.add(aInfo.packageName);
237             }
238         }
239         return apps;
240     }
241 
getCurrentAppsWithLauncher()242     private Set<String> getCurrentAppsWithLauncher() {
243         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
244         launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
245         List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
246                 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId);
247         Set<String> apps = new HashSet<String>();
248         for (ResolveInfo resolveInfo : resolveInfos) {
249             apps.add(resolveInfo.activityInfo.packageName);
250         }
251         return apps;
252     }
253 
writeSystemApps(Set<String> packageNames, File systemAppsFile)254     private void writeSystemApps(Set<String> packageNames, File systemAppsFile) {
255         try {
256             FileOutputStream stream = new FileOutputStream(systemAppsFile, false);
257             XmlSerializer serializer = new FastXmlSerializer();
258             serializer.setOutput(stream, "utf-8");
259             serializer.startDocument(null, true);
260             serializer.startTag(null, TAG_SYSTEM_APPS);
261             for (String packageName : packageNames) {
262                 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
263                 serializer.attribute(null, ATTR_VALUE, packageName);
264                 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
265             }
266             serializer.endTag(null, TAG_SYSTEM_APPS);
267             serializer.endDocument();
268             stream.close();
269         } catch (IOException e) {
270             ProvisionLogger.loge("IOException trying to write the system apps", e);
271         }
272     }
273 
readSystemApps(File systemAppsFile)274     private Set<String> readSystemApps(File systemAppsFile) {
275         Set<String> result = new HashSet<String>();
276         if (!systemAppsFile.exists()) {
277             return result;
278         }
279         try {
280             FileInputStream stream = new FileInputStream(systemAppsFile);
281 
282             XmlPullParser parser = Xml.newPullParser();
283             parser.setInput(stream, null);
284 
285             int type = parser.next();
286             int outerDepth = parser.getDepth();
287             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
288                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
289                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
290                     continue;
291                 }
292                 String tag = parser.getName();
293                 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
294                     result.add(parser.getAttributeValue(null, ATTR_VALUE));
295                 } else {
296                     ProvisionLogger.loge("Unknown tag: " + tag);
297                 }
298             }
299             stream.close();
300         } catch (IOException e) {
301             ProvisionLogger.loge("IOException trying to read the system apps", e);
302         } catch (XmlPullParserException e) {
303             ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
304         }
305         return result;
306     }
307 
getRequiredApps()308     protected Set<String> getRequiredApps() {
309         HashSet<String> requiredApps = new HashSet<String> (Arrays.asList(
310                         mContext.getResources().getStringArray(mReqAppsList)));
311         requiredApps.addAll(Arrays.asList(
312                         mContext.getResources().getStringArray(mVendorReqAppsList)));
313         requiredApps.add(mMdmPackageName);
314         return requiredApps;
315     }
316 
317     /**
318      * Runs the next task when all packages have been deleted or shuts down the activity if package
319      * deletion fails.
320      */
321     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
322         private final AtomicInteger mPackageCount = new AtomicInteger(0);
323 
PackageDeleteObserver(int packageCount)324         public PackageDeleteObserver(int packageCount) {
325             this.mPackageCount.set(packageCount);
326         }
327 
328         @Override
packageDeleted(String packageName, int returnCode)329         public void packageDeleted(String packageName, int returnCode) {
330             if (returnCode != PackageManager.DELETE_SUCCEEDED) {
331                 ProvisionLogger.logw(
332                         "Could not finish the provisioning: package deletion failed");
333                 mCallback.onError();
334             }
335             int currentPackageCount = mPackageCount.decrementAndGet();
336             if (currentPackageCount == 0) {
337                 ProvisionLogger.logi("All non-required system apps have been uninstalled.");
338                 mCallback.onSuccess();
339             }
340         }
341     }
342 
343     public abstract static class Callback {
onSuccess()344         public abstract void onSuccess();
onError()345         public abstract void onError();
346     }
347 }
348