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.PackageInfo;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.res.Resources;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.util.Xml;
35import android.view.inputmethod.InputMethodInfo;
36import android.view.inputmethod.InputMethodManager;
37
38import com.android.internal.util.FastXmlSerializer;
39import com.android.managedprovisioning.ProvisionLogger;
40import com.android.managedprovisioning.R;
41import com.android.managedprovisioning.Utils;
42
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileOutputStream;
46import java.io.IOException;
47
48import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.Collections;
51import java.util.HashSet;
52import java.util.List;
53import java.util.Set;
54import java.util.concurrent.atomic.AtomicInteger;
55
56import org.xmlpull.v1.XmlPullParser;
57import org.xmlpull.v1.XmlPullParserException;
58import org.xmlpull.v1.XmlSerializer;
59
60/**
61 * Deletes all system apps with a launcher that are not in the required set of packages.
62 * Furthermore deletes all disallowed apps.
63 *
64 * Note: If an app is mistakenly listed as both required and disallowed, it will be treated as
65 * required.
66 *
67 * This task may be run when a profile (both for managed device and managed profile) is created.
68 * In that case the newProfile flag should be true.
69 *
70 * It should also be run after a system update with newProfile false, if
71 * {@link #shouldDeleteNonRequiredApps} returns true. Note that only newly installed system apps
72 * will be deleted.
73 */
74public class DeleteNonRequiredAppsTask {
75    private final Callback mCallback;
76    private final Context mContext;
77    private final String mMdmPackageName;
78    private final IPackageManager mIpm;
79    private final PackageManager mPm;
80    private final List<String> mRequiredAppsList;
81    private final List<String> mDisallowedAppsList;
82    private final List<String> mVendorRequiredAppsList;
83    private final List<String> mVendorDisallowedAppsList;
84    private final int mUserId;
85    private final int mProvisioningType;
86    private final boolean mNewProfile; // If we are provisioning a new managed profile/device.
87    private final boolean mLeaveAllSystemAppsEnabled;
88
89    private static final String TAG_SYSTEM_APPS = "system-apps";
90    private static final String TAG_PACKAGE_LIST_ITEM = "item";
91    private static final String ATTR_VALUE = "value";
92
93    public static final int DEVICE_OWNER = 0;
94    public static final int PROFILE_OWNER = 1;
95
96    /**
97     * Provisioning type should be either {@link #DEVICE_OWNER} or {@link #PROFILE_OWNER}.
98     **/
99    public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int provisioningType,
100            boolean newProfile, int userId, boolean leaveAllSystemAppsEnabled, Callback callback) {
101
102        mCallback = callback;
103        mContext = context;
104        mMdmPackageName = mdmPackageName;
105        mProvisioningType = provisioningType;
106        mUserId = userId;
107        mNewProfile = newProfile;
108        mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
109        mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
110        mPm = context.getPackageManager();
111
112        int requiredAppsListArray;
113        int vendorRequiredAppsListArray;
114        int disallowedAppsListArray;
115        int vendorDisallowedAppsListArray;
116        if (mProvisioningType == DEVICE_OWNER) {
117            requiredAppsListArray = R.array.required_apps_managed_device;
118            disallowedAppsListArray = R.array.disallowed_apps_managed_device;
119            vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_device;
120            vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_device;
121        } else if (mProvisioningType == PROFILE_OWNER) {
122            requiredAppsListArray = R.array.required_apps_managed_profile;
123            disallowedAppsListArray = R.array.disallowed_apps_managed_profile;
124            vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_profile;
125            vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_profile;
126        } else {
127            throw new IllegalArgumentException("Provisioning type " + mProvisioningType +
128                    " not supported.");
129        }
130
131        Resources resources = mContext.getResources();
132        mRequiredAppsList = Arrays.asList(resources.getStringArray(requiredAppsListArray));
133        mDisallowedAppsList = Arrays.asList(resources.getStringArray(disallowedAppsListArray));
134        mVendorRequiredAppsList = Arrays.asList(
135                resources.getStringArray(vendorRequiredAppsListArray));
136        mVendorDisallowedAppsList = Arrays.asList(
137                resources.getStringArray(vendorDisallowedAppsListArray));
138    }
139
140    public void run() {
141        if (mLeaveAllSystemAppsEnabled) {
142            ProvisionLogger.logd("Not deleting non-required apps.");
143            mCallback.onSuccess();
144            return;
145        }
146        ProvisionLogger.logd("Deleting non required apps.");
147
148        File systemAppsFile = getSystemAppsFile(mContext, mUserId);
149        systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
150
151        Set<String> currentApps = Utils.getCurrentSystemApps(mUserId);
152        Set<String> previousApps;
153        if (mNewProfile) {
154            // Provisioning case.
155            previousApps = new HashSet<String>();
156        } else {
157            // OTA case.
158            if (!systemAppsFile.exists()) {
159                ProvisionLogger.loge("Could not find the system apps file " +
160                        systemAppsFile.getAbsolutePath());
161                mCallback.onError();
162                return;
163            }
164            previousApps = readSystemApps(systemAppsFile);
165        }
166
167        writeSystemApps(currentApps, systemAppsFile);
168        Set<String> newApps = currentApps;
169        newApps.removeAll(previousApps);
170
171        // Newly installed system apps are uninstalled when they are not required and are either
172        // disallowed or have a launcher icon.
173        Set<String> packagesToDelete = newApps;
174        packagesToDelete.removeAll(getRequiredApps());
175        Set<String> packagesToRetain = getCurrentAppsWithLauncher();
176        // Don't delete the system input method packages in case of Device owner provisioning.
177        if (mProvisioningType == DEVICE_OWNER) {
178            packagesToRetain.removeAll(getSystemInputMethods());
179        }
180        packagesToRetain.addAll(getDisallowedApps());
181        packagesToDelete.retainAll(packagesToRetain);
182
183        if (packagesToDelete.isEmpty()) {
184            mCallback.onSuccess();
185            return;
186        }
187        removeNonInstalledPackages(packagesToDelete);
188
189        PackageDeleteObserver packageDeleteObserver =
190                new PackageDeleteObserver(packagesToDelete.size());
191        for (String packageName : packagesToDelete) {
192            try {
193                mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId,
194                        PackageManager.DELETE_SYSTEM_APP);
195            } catch (RemoteException neverThrown) {
196                // Never thrown, as we are making local calls.
197                ProvisionLogger.loge("This should not happen.", neverThrown);
198            }
199        }
200    }
201
202    /**
203     * Remove all packages from the set that are not installed.
204     */
205    private void removeNonInstalledPackages(Set<String> packages) {
206        Set<String> toBeRemoved = new HashSet<String>();
207        for (String packageName : packages) {
208            try {
209                PackageInfo info = mIpm.getPackageInfo(packageName, 0 /* default flags */, mUserId);
210                if (info == null) {
211                    toBeRemoved.add(packageName);
212                }
213            } catch (RemoteException neverThrown) {
214                // Never thrown, as we are making local calls.
215                ProvisionLogger.loge("This should not happen.", neverThrown);
216            }
217        }
218        packages.removeAll(toBeRemoved);
219    }
220
221    /**
222     * Returns if this task should be run on OTA.
223     * This is indicated by the presence of the system apps file.
224     */
225    public static boolean shouldDeleteNonRequiredApps(Context context, int userId) {
226        return getSystemAppsFile(context, userId).exists();
227    }
228
229    static File getSystemAppsFile(Context context, int userId) {
230        return new File(context.getFilesDir() + File.separator + "system_apps"
231                + File.separator + "user" + userId + ".xml");
232    }
233
234    private Set<String> getCurrentAppsWithLauncher() {
235        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
236        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
237        List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
238                PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS,
239                mUserId);
240        Set<String> apps = new HashSet<String>();
241        for (ResolveInfo resolveInfo : resolveInfos) {
242            apps.add(resolveInfo.activityInfo.packageName);
243        }
244        return apps;
245    }
246
247    private Set<String> getSystemInputMethods() {
248        final InputMethodManager inputMethodManager =
249                (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
250        List<InputMethodInfo> inputMethods = inputMethodManager.getInputMethodList();
251        Set<String> systemInputMethods = new HashSet<String>();
252        for (InputMethodInfo inputMethodInfo : inputMethods) {
253            ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
254            if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
255                systemInputMethods.add(inputMethodInfo.getPackageName());
256            }
257        }
258        return systemInputMethods;
259    }
260
261    private void writeSystemApps(Set<String> packageNames, File systemAppsFile) {
262        try {
263            FileOutputStream stream = new FileOutputStream(systemAppsFile, false);
264            XmlSerializer serializer = new FastXmlSerializer();
265            serializer.setOutput(stream, "utf-8");
266            serializer.startDocument(null, true);
267            serializer.startTag(null, TAG_SYSTEM_APPS);
268            for (String packageName : packageNames) {
269                serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
270                serializer.attribute(null, ATTR_VALUE, packageName);
271                serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
272            }
273            serializer.endTag(null, TAG_SYSTEM_APPS);
274            serializer.endDocument();
275            stream.close();
276        } catch (IOException e) {
277            ProvisionLogger.loge("IOException trying to write the system apps", e);
278        }
279    }
280
281    private Set<String> readSystemApps(File systemAppsFile) {
282        Set<String> result = new HashSet<String>();
283        if (!systemAppsFile.exists()) {
284            return result;
285        }
286        try {
287            FileInputStream stream = new FileInputStream(systemAppsFile);
288
289            XmlPullParser parser = Xml.newPullParser();
290            parser.setInput(stream, null);
291
292            int type = parser.next();
293            int outerDepth = parser.getDepth();
294            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
295                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
296                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
297                    continue;
298                }
299                String tag = parser.getName();
300                if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
301                    result.add(parser.getAttributeValue(null, ATTR_VALUE));
302                } else {
303                    ProvisionLogger.loge("Unknown tag: " + tag);
304                }
305            }
306            stream.close();
307        } catch (IOException e) {
308            ProvisionLogger.loge("IOException trying to read the system apps", e);
309        } catch (XmlPullParserException e) {
310            ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
311        }
312        return result;
313    }
314
315    protected Set<String> getRequiredApps() {
316        HashSet<String> requiredApps = new HashSet<String>();
317        requiredApps.addAll(mRequiredAppsList);
318        requiredApps.addAll(mVendorRequiredAppsList);
319        requiredApps.add(mMdmPackageName);
320        return requiredApps;
321    }
322
323    private Set<String> getDisallowedApps() {
324        HashSet<String> disallowedApps = new HashSet<String>();
325        disallowedApps.addAll(mDisallowedAppsList);
326        disallowedApps.addAll(mVendorDisallowedAppsList);
327        return disallowedApps;
328    }
329
330    /**
331     * Runs the next task when all packages have been deleted or shuts down the activity if package
332     * deletion fails.
333     */
334    class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
335        private final AtomicInteger mPackageCount = new AtomicInteger(0);
336
337        public PackageDeleteObserver(int packageCount) {
338            this.mPackageCount.set(packageCount);
339        }
340
341        @Override
342        public void packageDeleted(String packageName, int returnCode) {
343            if (returnCode != PackageManager.DELETE_SUCCEEDED) {
344                ProvisionLogger.logw(
345                        "Could not finish the provisioning: package deletion failed");
346                mCallback.onError();
347                return;
348            }
349            int currentPackageCount = mPackageCount.decrementAndGet();
350            if (currentPackageCount == 0) {
351                ProvisionLogger.logi("All non-required system apps with launcher icon, "
352                        + "and all disallowed apps have been uninstalled.");
353                mCallback.onSuccess();
354            }
355        }
356    }
357
358    public abstract static class Callback {
359        public abstract void onSuccess();
360        public abstract void onError();
361    }
362}
363