UsageStatsXmlV1.java revision c8e8729244d75584ce71a74d29c452fe538a22c5
1/**
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16package com.android.server.usage;
17
18import com.android.internal.util.XmlUtils;
19
20import org.xmlpull.v1.XmlPullParser;
21import org.xmlpull.v1.XmlPullParserException;
22import org.xmlpull.v1.XmlSerializer;
23
24import android.app.usage.ConfigurationStats;
25import android.app.usage.TimeSparseArray;
26import android.app.usage.UsageEvents;
27import android.app.usage.UsageStats;
28import android.content.res.Configuration;
29import android.text.TextUtils;
30
31import java.io.IOException;
32import java.net.ProtocolException;
33
34/**
35 * UsageStats reader/writer for version 1 of the XML format.
36 */
37final class UsageStatsXmlV1 {
38    private static final String PACKAGES_TAG = "packages";
39    private static final String PACKAGE_TAG = "package";
40
41    private static final String CONFIGURATIONS_TAG = "configurations";
42    private static final String CONFIG_TAG = "config";
43
44    private static final String EVENT_LOG_TAG = "event-log";
45    private static final String EVENT_TAG = "event";
46
47    // Attributes
48    private static final String PACKAGE_ATTR = "package";
49    private static final String CLASS_ATTR = "class";
50    private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
51    private static final String COUNT_ATTR = "count";
52    private static final String ACTIVE_ATTR = "active";
53    private static final String LAST_EVENT_ATTR = "lastEvent";
54    private static final String TYPE_ATTR = "type";
55
56    // Time attributes stored as an offset of the beginTime.
57    private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
58    private static final String LAST_TIME_ACTIVE_SYSTEM_ATTR = "lastTimeActiveSystem";
59    private static final String BEGIN_IDLE_TIME_ATTR = "beginIdleTime";
60    private static final String END_TIME_ATTR = "endTime";
61    private static final String TIME_ATTR = "time";
62
63    private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
64            throws XmlPullParserException, IOException {
65        final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
66        if (pkg == null) {
67            throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
68        }
69
70        final UsageStats stats = statsOut.getOrCreateUsageStats(pkg);
71
72        // Apply the offset to the beginTime to find the absolute time.
73        stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
74                parser, LAST_TIME_ACTIVE_ATTR);
75
76        final String lastTimeUsedSystem = parser.getAttributeValue(null,
77                LAST_TIME_ACTIVE_SYSTEM_ATTR);
78        if (TextUtils.isEmpty(lastTimeUsedSystem)) {
79            // If the field isn't present, use the old one.
80            stats.mLastTimeSystemUsed = stats.mLastTimeUsed;
81        } else {
82            stats.mLastTimeSystemUsed = statsOut.beginTime + Long.parseLong(lastTimeUsedSystem);
83        }
84
85        final String beginIdleTime = parser.getAttributeValue(null, BEGIN_IDLE_TIME_ATTR);
86        if (!TextUtils.isEmpty(beginIdleTime)) {
87            stats.mBeginIdleTime = Long.parseLong(beginIdleTime);
88        }
89        stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
90        stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
91    }
92
93    private static void loadConfigStats(XmlPullParser parser, IntervalStats statsOut)
94            throws XmlPullParserException, IOException {
95        final Configuration config = new Configuration();
96        Configuration.readXmlAttrs(parser, config);
97
98        final ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
99
100        // Apply the offset to the beginTime to find the absolute time.
101        configStats.mLastTimeActive = statsOut.beginTime + XmlUtils.readLongAttribute(
102                parser, LAST_TIME_ACTIVE_ATTR);
103
104        configStats.mTotalTimeActive = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
105        configStats.mActivationCount = XmlUtils.readIntAttribute(parser, COUNT_ATTR);
106        if (XmlUtils.readBooleanAttribute(parser, ACTIVE_ATTR)) {
107            statsOut.activeConfiguration = configStats.mConfiguration;
108        }
109    }
110
111    private static void loadEvent(XmlPullParser parser, IntervalStats statsOut)
112            throws XmlPullParserException, IOException {
113        final String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
114        if (packageName == null) {
115            throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
116        }
117
118        final String className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
119
120        final UsageEvents.Event event = statsOut.buildEvent(packageName, className);
121
122        // Apply the offset to the beginTime to find the absolute time of this event.
123        event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
124
125        event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
126        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
127            event.mConfiguration = new Configuration();
128            Configuration.readXmlAttrs(parser, event.mConfiguration);
129        }
130
131        if (statsOut.events == null) {
132            statsOut.events = new TimeSparseArray<>();
133        }
134        statsOut.events.put(event.mTimeStamp, event);
135    }
136
137    private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,
138            final UsageStats usageStats) throws IOException {
139        xml.startTag(null, PACKAGE_TAG);
140
141        // Write the time offset.
142        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
143                usageStats.mLastTimeUsed - stats.beginTime);
144        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_SYSTEM_ATTR,
145                usageStats.mLastTimeSystemUsed - stats.beginTime);
146
147        XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
148        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
149        XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
150        XmlUtils.writeLongAttribute(xml, BEGIN_IDLE_TIME_ATTR, usageStats.mBeginIdleTime);
151
152        xml.endTag(null, PACKAGE_TAG);
153    }
154
155    private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats,
156            final ConfigurationStats configStats, boolean isActive) throws IOException {
157        xml.startTag(null, CONFIG_TAG);
158
159        // Write the time offset.
160        XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
161                configStats.mLastTimeActive - stats.beginTime);
162
163        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, configStats.mTotalTimeActive);
164        XmlUtils.writeIntAttribute(xml, COUNT_ATTR, configStats.mActivationCount);
165        if (isActive) {
166            XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true);
167        }
168
169        // Now write the attributes representing the configuration object.
170        Configuration.writeXmlAttrs(xml, configStats.mConfiguration);
171
172        xml.endTag(null, CONFIG_TAG);
173    }
174
175    private static void writeEvent(XmlSerializer xml, final IntervalStats stats,
176            final UsageEvents.Event event) throws IOException {
177        xml.startTag(null, EVENT_TAG);
178
179        // Store the time offset.
180        XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp - stats.beginTime);
181
182        XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage);
183        if (event.mClass != null) {
184            XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass);
185        }
186        XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
187
188        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
189                && event.mConfiguration != null) {
190            Configuration.writeXmlAttrs(xml, event.mConfiguration);
191        }
192
193        xml.endTag(null, EVENT_TAG);
194    }
195
196    /**
197     * Reads from the {@link XmlPullParser}, assuming that it is already on the
198     * <code><usagestats></code> tag.
199     *
200     * @param parser The parser from which to read events.
201     * @param statsOut The stats object to populate with the data from the XML file.
202     */
203    public static void read(XmlPullParser parser, IntervalStats statsOut)
204            throws XmlPullParserException, IOException {
205        statsOut.packageStats.clear();
206        statsOut.configurations.clear();
207        statsOut.activeConfiguration = null;
208
209        if (statsOut.events != null) {
210            statsOut.events.clear();
211        }
212
213        statsOut.endTime = statsOut.beginTime + XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
214
215        int eventCode;
216        int outerDepth = parser.getDepth();
217        while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT
218                && (eventCode != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
219            if (eventCode != XmlPullParser.START_TAG) {
220                continue;
221            }
222
223            final String tag = parser.getName();
224            switch (tag) {
225                case PACKAGE_TAG:
226                    loadUsageStats(parser, statsOut);
227                    break;
228
229                case CONFIG_TAG:
230                    loadConfigStats(parser, statsOut);
231                    break;
232
233                case EVENT_TAG:
234                    loadEvent(parser, statsOut);
235                    break;
236            }
237        }
238    }
239
240    /**
241     * Writes the stats object to an XML file. The {@link XmlSerializer}
242     * has already written the <code><usagestats></code> tag, but attributes may still
243     * be added.
244     *
245     * @param xml The serializer to which to write the packageStats data.
246     * @param stats The stats object to write to the XML file.
247     */
248    public static void write(XmlSerializer xml, IntervalStats stats) throws IOException {
249        XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime);
250
251        xml.startTag(null, PACKAGES_TAG);
252        final int statsCount = stats.packageStats.size();
253        for (int i = 0; i < statsCount; i++) {
254            writeUsageStats(xml, stats, stats.packageStats.valueAt(i));
255        }
256        xml.endTag(null, PACKAGES_TAG);
257
258
259        xml.startTag(null, CONFIGURATIONS_TAG);
260        final int configCount = stats.configurations.size();
261        for (int i = 0; i < configCount; i++) {
262            boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
263            writeConfigStats(xml, stats, stats.configurations.valueAt(i), active);
264        }
265        xml.endTag(null, CONFIGURATIONS_TAG);
266
267        xml.startTag(null, EVENT_LOG_TAG);
268        final int eventCount = stats.events != null ? stats.events.size() : 0;
269        for (int i = 0; i < eventCount; i++) {
270            writeEvent(xml, stats, stats.events.valueAt(i));
271        }
272        xml.endTag(null, EVENT_LOG_TAG);
273    }
274
275    private UsageStatsXmlV1() {
276    }
277}
278