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
17package com.android.managedprovisioning.task;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.ComponentInfo;
25import android.content.pm.IPackageDeleteObserver;
26import android.content.pm.IPackageManager;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.util.Xml;
33
34import com.android.internal.util.FastXmlSerializer;
35import com.android.managedprovisioning.ProvisionLogger;
36import com.android.managedprovisioning.R;
37
38import java.io.File;
39import java.io.FileInputStream;
40import java.io.FileOutputStream;
41import java.io.IOException;
42
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Set;
48import java.util.concurrent.atomic.AtomicInteger;
49
50import org.xmlpull.v1.XmlPullParser;
51import org.xmlpull.v1.XmlPullParserException;
52import 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 */
61public 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
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
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     */
103    public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
104        return getSystemAppsFile(context, userId).exists();
105    }
106
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
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
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     */
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
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     */
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
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
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
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
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
324        public PackageDeleteObserver(int packageCount) {
325            this.mPackageCount.set(packageCount);
326        }
327
328        @Override
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 {
344        public abstract void onSuccess();
345        public abstract void onError();
346    }
347}
348