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