• 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 Nfc, 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 profile.
71     private final boolean mDisableInstallShortcutListeners;
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 disableInstallShortcutListeners, Callback callback)77     public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int userId,
78             int requiredAppsList, int vendorRequiredAppsList, boolean newProfile,
79             boolean disableInstallShortcutListeners, 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         mDisableInstallShortcutListeners = disableInstallShortcutListeners;
90     }
91 
run()92     public void run() {
93         if (mNewProfile) {
94             disableNfcBluetoothSharing();
95         }
96         deleteNonRequiredApps();
97     }
98 
disableNfcBluetoothSharing()99     private void disableNfcBluetoothSharing() {
100         ProvisionLogger.logd("Disabling Nfc and Bluetooth sharing.");
101         disableComponent(new ComponentName("com.android.nfc", "com.android.nfc.BeamShareActivity"));
102         disableComponent(new ComponentName("com.android.bluetooth",
103                 "com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
104     }
105 
deleteNonRequiredApps()106     private void deleteNonRequiredApps() {
107         ProvisionLogger.logd("Deleting non required apps.");
108 
109         File file = new File(mContext.getFilesDir() + File.separator + "system_apps"
110                 + File.separator + "user" + mUserId + ".xml");
111         file.getParentFile().mkdirs(); // Creating the folder if it does not exist
112 
113         Set<String> currentApps = getCurrentSystemApps();
114         Set<String> previousApps;
115         if (mNewProfile) {
116             // If this userId was a managed profile before, file may exist. In this case, we ignore
117             // what is in this file.
118             previousApps = new HashSet<String>();
119         } else {
120             if (file.exists()) {
121                 previousApps = readSystemApps(file);
122             } else {
123                 // If for some reason, the system apps have not been written to file before, we will
124                 // not delete any system apps this time.
125                 writeSystemApps(currentApps, file);
126                 mCallback.onSuccess();
127                 return;
128             }
129         }
130         writeSystemApps(currentApps, file);
131         Set<String> newApps = currentApps;
132         newApps.removeAll(previousApps);
133 
134         if (mDisableInstallShortcutListeners) {
135             Intent actionShortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
136             if (previousApps.isEmpty()) {
137                 // Here, all the apps are in newApps.
138                 // It is faster to do it this way than to go through all the apps one by one.
139                 disableReceivers(actionShortcut);
140             } else {
141                 // Here, all the apps are not in newApps. So we have to go through all the new
142                 // apps one by one.
143                 for (String newApp : newApps) {
144                     actionShortcut.setPackage(newApp);
145                     disableReceivers(actionShortcut);
146                 }
147             }
148         }
149         Set<String> packagesToDelete = newApps;
150         packagesToDelete.removeAll(getRequiredApps());
151         packagesToDelete.retainAll(getCurrentAppsWithLauncher());
152         // com.android.server.telecom should not handle CALL intents in the managed profile.
153         if (mNewProfile) {
154             packagesToDelete.add("com.android.server.telecom");
155         }
156         int size = packagesToDelete.size();
157         if (size > 0) {
158             PackageDeleteObserver packageDeleteObserver =
159                         new PackageDeleteObserver(packagesToDelete.size());
160             for (String packageName : packagesToDelete) {
161                 try {
162                     mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId,
163                             PackageManager.DELETE_SYSTEM_APP);
164                 } catch (RemoteException neverThrown) {
165                     // Never thrown, as we are making local calls.
166                     ProvisionLogger.loge("This should not happen.", neverThrown);
167                 }
168             }
169         } else {
170             mCallback.onSuccess();
171         }
172     }
173 
disableReceivers(Intent intent)174     private void disableReceivers(Intent intent) {
175         List<ResolveInfo> receivers = mPm.queryBroadcastReceivers(intent, 0, mUserId);
176         for (ResolveInfo ri : receivers) {
177             // One of ri.activityInfo, ri.serviceInfo, ri.providerInfo is not null. Let's find which
178             // one.
179             ComponentInfo ci;
180             if (ri.activityInfo != null) {
181                 ci = ri.activityInfo;
182             } else if (ri.serviceInfo != null) {
183                 ci = ri.serviceInfo;
184             } else {
185                 ci = ri.providerInfo;
186             }
187             disableComponent(new ComponentName(ci.packageName, ci.name));
188         }
189     }
190 
disableComponent(ComponentName toDisable)191     private void disableComponent(ComponentName toDisable) {
192         try {
193             mIpm.setComponentEnabledSetting(toDisable,
194                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP,
195                     mUserId);
196         } catch (RemoteException neverThrown) {
197             ProvisionLogger.loge("This should not happen.", neverThrown);
198         } catch (Exception e) {
199             ProvisionLogger.logw("Component not found, not disabling it: "
200                 + toDisable.toShortString());
201         }
202     }
203 
204     /**
205      * Returns the set of package names of apps that are in the system image,
206      * whether they have been deleted or not.
207      */
getCurrentSystemApps()208     private Set<String> getCurrentSystemApps() {
209         Set<String> apps = new HashSet<String>();
210         List<ApplicationInfo> aInfos = null;
211         try {
212             aInfos = mIpm.getInstalledApplications(
213                     PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList();
214         } catch (RemoteException neverThrown) {
215             // Never thrown, as we are making local calls.
216             ProvisionLogger.loge("This should not happen.", neverThrown);
217         }
218         for (ApplicationInfo aInfo : aInfos) {
219             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
220                 apps.add(aInfo.packageName);
221             }
222         }
223         return apps;
224     }
225 
getCurrentAppsWithLauncher()226     private Set<String> getCurrentAppsWithLauncher() {
227         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
228         launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
229         List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
230                 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId);
231         Set<String> apps = new HashSet<String>();
232         for (ResolveInfo resolveInfo : resolveInfos) {
233             apps.add(resolveInfo.activityInfo.packageName);
234         }
235         return apps;
236     }
237 
writeSystemApps(Set<String> packageNames, File file)238     private void writeSystemApps(Set<String> packageNames, File file) {
239         try {
240             FileOutputStream stream = new FileOutputStream(file, false);
241             XmlSerializer serializer = new FastXmlSerializer();
242             serializer.setOutput(stream, "utf-8");
243             serializer.startDocument(null, true);
244             serializer.startTag(null, TAG_SYSTEM_APPS);
245             for (String packageName : packageNames) {
246                 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
247                 serializer.attribute(null, ATTR_VALUE, packageName);
248                 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
249             }
250             serializer.endTag(null, TAG_SYSTEM_APPS);
251             serializer.endDocument();
252             stream.close();
253         } catch (IOException e) {
254             ProvisionLogger.loge("IOException trying to write the system apps", e);
255         }
256     }
257 
readSystemApps(File file)258     private Set<String> readSystemApps(File file) {
259         Set<String> result = new HashSet<String>();
260         if (!file.exists()) {
261             return result;
262         }
263         try {
264             FileInputStream stream = new FileInputStream(file);
265 
266             XmlPullParser parser = Xml.newPullParser();
267             parser.setInput(stream, null);
268 
269             int type = parser.next();
270             int outerDepth = parser.getDepth();
271             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
272                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
273                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
274                     continue;
275                 }
276                 String tag = parser.getName();
277                 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
278                     result.add(parser.getAttributeValue(null, ATTR_VALUE));
279                 } else {
280                     ProvisionLogger.loge("Unknown tag: " + tag);
281                 }
282             }
283             stream.close();
284         } catch (IOException e) {
285             ProvisionLogger.loge("IOException trying to read the system apps", e);
286         } catch (XmlPullParserException e) {
287             ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
288         }
289         return result;
290     }
291 
getRequiredApps()292     protected Set<String> getRequiredApps() {
293         HashSet<String> requiredApps = new HashSet<String> (Arrays.asList(
294                         mContext.getResources().getStringArray(mReqAppsList)));
295         requiredApps.addAll(Arrays.asList(
296                         mContext.getResources().getStringArray(mVendorReqAppsList)));
297         requiredApps.add(mMdmPackageName);
298         return requiredApps;
299     }
300 
301     /**
302      * Runs the next task when all packages have been deleted or shuts down the activity if package
303      * deletion fails.
304      */
305     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
306         private final AtomicInteger mPackageCount = new AtomicInteger(0);
307 
PackageDeleteObserver(int packageCount)308         public PackageDeleteObserver(int packageCount) {
309             this.mPackageCount.set(packageCount);
310         }
311 
312         @Override
packageDeleted(String packageName, int returnCode)313         public void packageDeleted(String packageName, int returnCode) {
314             if (returnCode != PackageManager.DELETE_SUCCEEDED) {
315                 ProvisionLogger.logw(
316                         "Could not finish the provisioning: package deletion failed");
317                 mCallback.onError();
318             }
319             int currentPackageCount = mPackageCount.decrementAndGet();
320             if (currentPackageCount == 0) {
321                 ProvisionLogger.logi("All non-required system apps have been uninstalled.");
322                 mCallback.onSuccess();
323             }
324         }
325     }
326 
327     public abstract static class Callback {
onSuccess()328         public abstract void onSuccess();
onError()329         public abstract void onError();
330     }
331 }
332