1/*
2 * Copyright 2016, 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.nonrequiredapps;
18
19import static com.android.internal.util.Preconditions.checkNotNull;
20
21import android.annotation.Nullable;
22import android.app.AppGlobals;
23import android.content.Context;
24import android.content.pm.IPackageManager;
25import android.util.Xml;
26
27import com.android.internal.annotations.VisibleForTesting;
28import com.android.internal.util.FastXmlSerializer;
29import com.android.managedprovisioning.common.ProvisionLogger;
30import com.android.managedprovisioning.common.Utils;
31
32import java.io.File;
33import java.io.FileInputStream;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.util.Collections;
37import java.util.HashSet;
38import java.util.Set;
39
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44/**
45 * Stores and retrieves the system apps that were on the device during provisioning and on
46 * subsequent OTAs.
47 */
48public class SystemAppsSnapshot {
49    private static final String TAG_SYSTEM_APPS = "system-apps";
50    private static final String TAG_PACKAGE_LIST_ITEM = "item";
51    private static final String ATTR_VALUE = "value";
52
53    private final Context mContext;
54    private final IPackageManager mIPackageManager;
55    private final Utils mUtils;
56
57    public SystemAppsSnapshot(Context context) {
58        this(context, AppGlobals.getPackageManager(), new Utils());
59    }
60
61    @VisibleForTesting
62    SystemAppsSnapshot(
63            Context context,
64            IPackageManager iPackageManager,
65            Utils utils) {
66        mContext = checkNotNull(context);
67        mIPackageManager = checkNotNull(iPackageManager);
68        mUtils = checkNotNull(utils);
69    }
70
71    /**
72     * Returns whether currently a snapshot exists for the given user.
73     *
74     * @param userId the user id for which the snapshot is requested.
75     */
76    public boolean hasSnapshot(int userId) {
77        return getSystemAppsFile(mContext, userId).exists();
78    }
79
80    /**
81     * Returns the last stored snapshot for the given user.
82     *
83     * @param userId the user id for which the snapshot is requested.
84     */
85    public Set<String> getSnapshot(int userId) {
86        return readSystemApps(getSystemAppsFile(mContext, userId));
87    }
88
89    /**
90     * Call this method to take a snapshot of the current set of system apps.
91     *
92     * @param userId the user id for which the snapshot should be taken.
93     */
94    public void takeNewSnapshot(int userId) {
95        final File systemAppsFile = getSystemAppsFile(mContext, userId);
96        systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist
97        writeSystemApps(mUtils.getCurrentSystemApps(mIPackageManager, userId), systemAppsFile);
98    }
99
100    private void writeSystemApps(Set<String> packageNames, File systemAppsFile) {
101        try {
102            FileOutputStream stream = new FileOutputStream(systemAppsFile, false);
103            XmlSerializer serializer = new FastXmlSerializer();
104            serializer.setOutput(stream, "utf-8");
105            serializer.startDocument(null, true);
106            serializer.startTag(null, TAG_SYSTEM_APPS);
107            for (String packageName : packageNames) {
108                serializer.startTag(null, TAG_PACKAGE_LIST_ITEM);
109                serializer.attribute(null, ATTR_VALUE, packageName);
110                serializer.endTag(null, TAG_PACKAGE_LIST_ITEM);
111            }
112            serializer.endTag(null, TAG_SYSTEM_APPS);
113            serializer.endDocument();
114            stream.close();
115        } catch (IOException e) {
116            ProvisionLogger.loge("IOException trying to write the system apps", e);
117        }
118    }
119
120    private Set<String> readSystemApps(File systemAppsFile) {
121        Set<String> result = new HashSet<>();
122        if (!systemAppsFile.exists()) {
123            return result;
124        }
125        try {
126            FileInputStream stream = new FileInputStream(systemAppsFile);
127
128            XmlPullParser parser = Xml.newPullParser();
129            parser.setInput(stream, null);
130            parser.next();
131
132            int type;
133            int outerDepth = parser.getDepth();
134            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
135                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
136                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
137                    continue;
138                }
139                String tag = parser.getName();
140                if (tag.equals(TAG_PACKAGE_LIST_ITEM)) {
141                    result.add(parser.getAttributeValue(null, ATTR_VALUE));
142                } else {
143                    ProvisionLogger.loge("Unknown tag: " + tag);
144                }
145            }
146            stream.close();
147        } catch (IOException e) {
148            ProvisionLogger.loge("IOException trying to read the system apps", e);
149        } catch (XmlPullParserException e) {
150            ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e);
151        }
152        return result;
153    }
154
155    @VisibleForTesting
156    static File getSystemAppsFile(Context context, int userId) {
157        return new File(context.getFilesDir() + File.separator + "system_apps"
158                + File.separator + "user" + userId + ".xml");
159    }
160}
161