Add Configuration changes to UsageStats

Bug:17354208
Change-Id: I9b2f595e51b656607e30e798926cfb7e25134944
This commit is contained in:
Adam Lesinski
2014-09-02 16:43:52 -07:00
parent d400ca2f8d
commit 7f61e96db7
17 changed files with 966 additions and 193 deletions

View File

@@ -5680,6 +5680,19 @@ package android.app.job {
package android.app.usage {
public final class ConfigurationStats implements android.os.Parcelable {
ctor public ConfigurationStats(android.app.usage.ConfigurationStats);
method public int describeContents();
method public int getActivationCount();
method public android.content.res.Configuration getConfiguration();
method public long getFirstTimeStamp();
method public long getLastTimeActive();
method public long getLastTimeStamp();
method public long getTotalTimeActive();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
public final class UsageEvents implements android.os.Parcelable {
method public int describeContents();
method public boolean getNextEvent(android.app.usage.UsageEvents.Event);
@@ -5692,9 +5705,11 @@ package android.app.usage {
public static final class UsageEvents.Event {
ctor public UsageEvents.Event();
method public java.lang.String getClassName();
method public android.content.res.Configuration getConfiguration();
method public int getEventType();
method public java.lang.String getPackageName();
method public long getTimeStamp();
field public static final int CONFIGURATION_CHANGE = 5; // 0x5
field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
field public static final int NONE = 0; // 0x0
@@ -5715,6 +5730,7 @@ package android.app.usage {
public final class UsageStatsManager {
method public java.util.Map<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long);
method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
method public android.app.usage.UsageEvents queryEvents(long, long);
method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
field public static final int INTERVAL_BEST = 4; // 0x4

View File

@@ -0,0 +1,161 @@
/**
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package android.app.usage;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Represents the usage statistics of a device {@link android.content.res.Configuration} for a
* specific time range.
*/
public final class ConfigurationStats implements Parcelable {
/**
* {@hide}
*/
public Configuration mConfiguration;
/**
* {@hide}
*/
public long mBeginTimeStamp;
/**
* {@hide}
*/
public long mEndTimeStamp;
/**
* {@hide}
*/
public long mLastTimeActive;
/**
* {@hide}
*/
public long mTotalTimeActive;
/**
* {@hide}
*/
public int mActivationCount;
/**
* {@hide}
*/
public ConfigurationStats() {
}
public ConfigurationStats(ConfigurationStats stats) {
mConfiguration = stats.mConfiguration;
mBeginTimeStamp = stats.mBeginTimeStamp;
mEndTimeStamp = stats.mEndTimeStamp;
mLastTimeActive = stats.mLastTimeActive;
mTotalTimeActive = stats.mTotalTimeActive;
mActivationCount = stats.mActivationCount;
}
public Configuration getConfiguration() {
return mConfiguration;
}
/**
* Get the beginning of the time range this {@link ConfigurationStats} represents,
* measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
*/
public long getFirstTimeStamp() {
return mBeginTimeStamp;
}
/**
* Get the end of the time range this {@link ConfigurationStats} represents,
* measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
*/
public long getLastTimeStamp() {
return mEndTimeStamp;
}
/**
* Get the last time this configuration was active, measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
*/
public long getLastTimeActive() {
return mLastTimeActive;
}
/**
* Get the total time this configuration was active, measured in milliseconds.
*/
public long getTotalTimeActive() {
return mTotalTimeActive;
}
/**
* Get the number of times this configuration was active.
*/
public int getActivationCount() {
return mActivationCount;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mConfiguration != null) {
dest.writeInt(1);
mConfiguration.writeToParcel(dest, flags);
} else {
dest.writeInt(0);
}
dest.writeLong(mBeginTimeStamp);
dest.writeLong(mEndTimeStamp);
dest.writeLong(mLastTimeActive);
dest.writeLong(mTotalTimeActive);
dest.writeInt(mActivationCount);
}
public static final Creator<ConfigurationStats> CREATOR = new Creator<ConfigurationStats>() {
@Override
public ConfigurationStats createFromParcel(Parcel source) {
ConfigurationStats stats = new ConfigurationStats();
if (source.readInt() != 0) {
stats.mConfiguration = Configuration.CREATOR.createFromParcel(source);
}
stats.mBeginTimeStamp = source.readLong();
stats.mEndTimeStamp = source.readLong();
stats.mLastTimeActive = source.readLong();
stats.mTotalTimeActive = source.readLong();
stats.mActivationCount = source.readInt();
return stats;
}
@Override
public ConfigurationStats[] newArray(int size) {
return new ConfigurationStats[size];
}
};
}

View File

@@ -27,5 +27,7 @@ import android.content.pm.ParceledListSlice;
interface IUsageStatsManager {
ParceledListSlice queryUsageStats(int bucketType, long beginTime, long endTime,
String callingPackage);
ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime,
String callingPackage);
UsageEvents queryEvents(long beginTime, long endTime, String callingPackage);
}

View File

@@ -16,6 +16,7 @@
package android.app.usage;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -62,6 +63,11 @@ public final class UsageEvents implements Parcelable {
*/
public static final int CONTINUE_PREVIOUS_DAY = 4;
/**
* An event type denoting that the device configuration has changed.
*/
public static final int CONFIGURATION_CHANGE = 5;
/**
* {@hide}
*/
@@ -82,6 +88,12 @@ public final class UsageEvents implements Parcelable {
*/
public int mEventType;
/**
* Only present for {@link #CONFIGURATION_CHANGE} event types.
* {@hide}
*/
public Configuration mConfiguration;
/**
* TODO(adamlesinski): Removed before release.
* {@hide}
@@ -123,6 +135,14 @@ public final class UsageEvents implements Parcelable {
public int getEventType() {
return mEventType;
}
/**
* Returns a {@link Configuration} for this event if the event is of type
* {@link #CONFIGURATION_CHANGE}, otherwise it returns null.
*/
public Configuration getConfiguration() {
return mConfiguration;
}
}
// Only used when creating the resulting events. Not used for reading/unparceling.
@@ -201,23 +221,9 @@ public final class UsageEvents implements Parcelable {
return false;
}
final int packageIndex = mParcel.readInt();
if (packageIndex >= 0) {
eventOut.mPackage = mStringPool[packageIndex];
} else {
eventOut.mPackage = null;
}
readEventFromParcel(mParcel, eventOut);
final int classIndex = mParcel.readInt();
if (classIndex >= 0) {
eventOut.mClass = mStringPool[classIndex];
} else {
eventOut.mClass = null;
}
eventOut.mEventType = mParcel.readInt();
eventOut.mTimeStamp = mParcel.readLong();
mIndex++;
if (mIndex >= mEventCount) {
mParcel.recycle();
mParcel = null;
@@ -235,11 +241,6 @@ public final class UsageEvents implements Parcelable {
}
}
@Override
public int describeContents() {
return 0;
}
private int findStringIndex(String str) {
final int index = Arrays.binarySearch(mStringPool, str);
if (index < 0) {
@@ -248,6 +249,66 @@ public final class UsageEvents implements Parcelable {
return index;
}
/**
* Writes a single event to the parcel. Modify this when updating {@link Event}.
*/
private void writeEventToParcel(Event event, Parcel p, int flags) {
final int packageIndex;
if (event.mPackage != null) {
packageIndex = findStringIndex(event.mPackage);
} else {
packageIndex = -1;
}
final int classIndex;
if (event.mClass != null) {
classIndex = findStringIndex(event.mClass);
} else {
classIndex = -1;
}
p.writeInt(packageIndex);
p.writeInt(classIndex);
p.writeInt(event.mEventType);
p.writeLong(event.mTimeStamp);
if (event.mEventType == Event.CONFIGURATION_CHANGE) {
event.mConfiguration.writeToParcel(p, flags);
}
}
/**
* Reads a single event from the parcel. Modify this when updating {@link Event}.
*/
private void readEventFromParcel(Parcel p, Event eventOut) {
final int packageIndex = p.readInt();
if (packageIndex >= 0) {
eventOut.mPackage = mStringPool[packageIndex];
} else {
eventOut.mPackage = null;
}
final int classIndex = p.readInt();
if (classIndex >= 0) {
eventOut.mClass = mStringPool[classIndex];
} else {
eventOut.mClass = null;
}
eventOut.mEventType = p.readInt();
eventOut.mTimeStamp = p.readLong();
// Extract the configuration for configuration change events.
if (eventOut.mEventType == Event.CONFIGURATION_CHANGE) {
eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p);
} else {
eventOut.mConfiguration = null;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mEventCount);
@@ -262,25 +323,9 @@ public final class UsageEvents implements Parcelable {
p.setDataPosition(0);
for (int i = 0; i < mEventCount; i++) {
final Event event = mEventsToWrite.get(i);
final int packageIndex;
if (event.mPackage != null) {
packageIndex = findStringIndex(event.mPackage);
} else {
packageIndex = -1;
}
final int classIndex;
if (event.mClass != null) {
classIndex = findStringIndex(event.mClass);
} else {
classIndex = -1;
}
p.writeInt(packageIndex);
p.writeInt(classIndex);
p.writeInt(event.getEventType());
p.writeLong(event.getTimeStamp());
writeEventToParcel(event, p, flags);
}
final int listByteLength = p.dataPosition();
// Write the total length of the data.

View File

@@ -125,9 +125,9 @@ public final class UsageStatsManager {
* @see #INTERVAL_YEARLY
* @see #INTERVAL_BEST
*/
@SuppressWarnings("unchecked")
public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
endTime, mContext.getOpPackageName());
if (slice != null) {
@@ -136,7 +136,32 @@ public final class UsageStatsManager {
} catch (RemoteException e) {
// fallthrough and return null.
}
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
/**
* Gets the hardware configurations the device was in for the given time range, aggregated by
* the specified interval. The results are ordered as in
* {@link #queryUsageStats(int, long, long)}.
*
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
* @return A list of {@link ConfigurationStats} or null if none are available.
*/
public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime,
long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<ConfigurationStats> slice = mService.queryConfigurationStats(
intervalType, beginTime, endTime, mContext.getOpPackageName());
if (slice != null) {
return slice.getList();
}
} catch (RemoteException e) {
// fallthrough and return the empty list.
}
return Collections.emptyList();
}
/**

View File

@@ -17,6 +17,7 @@
package android.app.usage;
import android.content.ComponentName;
import android.content.res.Configuration;
/**
* UsageStatsManager local system service interface.
@@ -28,14 +29,19 @@ public abstract class UsageStatsManagerInternal {
/**
* Reports an event to the UsageStatsManager.
*
* @param component The component for which this event ocurred.
* @param component The component for which this event occurred.
* @param userId The user id to which the component belongs to.
* @param timeStamp The time at which this event ocurred.
* @param eventType The event that occured. Valid values can be found at
* @param eventType The event that occurred. Valid values can be found at
* {@link UsageEvents}
*/
public abstract void reportEvent(ComponentName component, int userId,
long timeStamp, int eventType);
public abstract void reportEvent(ComponentName component, int userId, int eventType);
/**
* Reports a configuration change to the UsageStatsManager.
*
* @param config The new device configuration.
*/
public abstract void reportConfigurationChange(Configuration config, int userId);
/**
* Prepares the UsageStatsService for shutdown.

View File

@@ -16,6 +16,12 @@
package android.content.res;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Parcel;
@@ -23,7 +29,7 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.view.View;
import java.text.Format;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
@@ -1353,8 +1359,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* Returns a string representation of the configuration that can be parsed
* by build tools (like AAPT).
*
*
*
* @hide
*/
public static String resourceQualifierString(Configuration config) {
@@ -1568,4 +1572,229 @@ public final class Configuration implements Parcelable, Comparable<Configuration
parts.add("v" + Build.VERSION.RESOURCES_SDK_INT);
return TextUtils.join("-", parts);
}
/**
* Generate a delta Configuration between <code>base</code> and <code>change</code>. The
* resulting delta can be used with {@link #updateFrom(Configuration)}.
* <p />
* Caveat: If the any of the Configuration's members becomes undefined, then
* {@link #updateFrom(Configuration)} will treat it as a no-op and not update that member.
*
* This is fine for device configurations as no member is ever undefined.
* {@hide}
*/
public static Configuration generateDelta(Configuration base, Configuration change) {
final Configuration delta = new Configuration();
if (base.fontScale != change.fontScale) {
delta.fontScale = change.fontScale;
}
if (base.mcc != change.mcc) {
delta.mcc = change.mcc;
}
if (base.mnc != change.mnc) {
delta.mnc = change.mnc;
}
if ((base.locale == null && change.locale != null) ||
(base.locale != null && !base.locale.equals(change.locale))) {
delta.locale = change.locale;
}
if (base.touchscreen != change.touchscreen) {
delta.touchscreen = change.touchscreen;
}
if (base.keyboard != change.keyboard) {
delta.keyboard = change.keyboard;
}
if (base.keyboardHidden != change.keyboardHidden) {
delta.keyboardHidden = change.keyboardHidden;
}
if (base.navigation != change.navigation) {
delta.navigation = change.navigation;
}
if (base.navigationHidden != change.navigationHidden) {
delta.navigationHidden = change.navigationHidden;
}
if (base.orientation != change.orientation) {
delta.orientation = change.orientation;
}
if ((base.screenLayout & SCREENLAYOUT_SIZE_MASK) !=
(change.screenLayout & SCREENLAYOUT_SIZE_MASK)) {
delta.screenLayout |= change.screenLayout & SCREENLAYOUT_SIZE_MASK;
}
if ((base.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK) !=
(change.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
delta.screenLayout |= change.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
}
if ((base.screenLayout & SCREENLAYOUT_LONG_MASK) !=
(change.screenLayout & SCREENLAYOUT_LONG_MASK)) {
delta.screenLayout |= change.screenLayout & SCREENLAYOUT_LONG_MASK;
}
if ((base.uiMode & UI_MODE_TYPE_MASK) != (change.uiMode & UI_MODE_TYPE_MASK)) {
delta.uiMode |= change.uiMode & UI_MODE_TYPE_MASK;
}
if ((base.uiMode & UI_MODE_NIGHT_MASK) != (change.uiMode & UI_MODE_NIGHT_MASK)) {
delta.uiMode |= change.uiMode & UI_MODE_NIGHT_MASK;
}
if (base.screenWidthDp != change.screenWidthDp) {
delta.screenWidthDp = change.screenWidthDp;
}
if (base.screenHeightDp != change.screenHeightDp) {
delta.screenHeightDp = change.screenHeightDp;
}
if (base.smallestScreenWidthDp != change.smallestScreenWidthDp) {
delta.smallestScreenWidthDp = change.smallestScreenWidthDp;
}
if (base.densityDpi != change.densityDpi) {
delta.densityDpi = change.densityDpi;
}
return delta;
}
private static final String XML_ATTR_FONT_SCALE = "fs";
private static final String XML_ATTR_MCC = "mcc";
private static final String XML_ATTR_MNC = "mnc";
private static final String XML_ATTR_LOCALE = "locale";
private static final String XML_ATTR_TOUCHSCREEN = "touch";
private static final String XML_ATTR_KEYBOARD = "key";
private static final String XML_ATTR_KEYBOARD_HIDDEN = "keyHid";
private static final String XML_ATTR_HARD_KEYBOARD_HIDDEN = "hardKeyHid";
private static final String XML_ATTR_NAVIGATION = "nav";
private static final String XML_ATTR_NAVIGATION_HIDDEN = "navHid";
private static final String XML_ATTR_ORIENTATION = "ori";
private static final String XML_ATTR_SCREEN_LAYOUT = "scrLay";
private static final String XML_ATTR_UI_MODE = "ui";
private static final String XML_ATTR_SCREEN_WIDTH = "width";
private static final String XML_ATTR_SCREEN_HEIGHT = "height";
private static final String XML_ATTR_SMALLEST_WIDTH = "sw";
private static final String XML_ATTR_DENSITY = "density";
/**
* Reads the attributes corresponding to Configuration member fields from the Xml parser.
* The parser is expected to be on a tag which has Configuration attributes.
*
* @param parser The Xml parser from which to read attributes.
* @param configOut The Configuration to populate from the Xml attributes.
* {@hide}
*/
public static void readXmlAttrs(XmlPullParser parser, Configuration configOut)
throws XmlPullParserException, IOException {
configOut.fontScale = Float.intBitsToFloat(
XmlUtils.readIntAttribute(parser, XML_ATTR_FONT_SCALE, 0));
configOut.mcc = XmlUtils.readIntAttribute(parser, XML_ATTR_MCC, 0);
configOut.mnc = XmlUtils.readIntAttribute(parser, XML_ATTR_MNC, 0);
final String localeStr = XmlUtils.readStringAttribute(parser, XML_ATTR_LOCALE);
if (localeStr != null) {
configOut.locale = Locale.forLanguageTag(localeStr);
}
configOut.touchscreen = XmlUtils.readIntAttribute(parser, XML_ATTR_TOUCHSCREEN,
TOUCHSCREEN_UNDEFINED);
configOut.keyboard = XmlUtils.readIntAttribute(parser, XML_ATTR_KEYBOARD,
KEYBOARD_UNDEFINED);
configOut.keyboardHidden = XmlUtils.readIntAttribute(parser, XML_ATTR_KEYBOARD_HIDDEN,
KEYBOARDHIDDEN_UNDEFINED);
configOut.hardKeyboardHidden =
XmlUtils.readIntAttribute(parser, XML_ATTR_HARD_KEYBOARD_HIDDEN,
HARDKEYBOARDHIDDEN_UNDEFINED);
configOut.navigation = XmlUtils.readIntAttribute(parser, XML_ATTR_NAVIGATION,
NAVIGATION_UNDEFINED);
configOut.navigationHidden = XmlUtils.readIntAttribute(parser, XML_ATTR_NAVIGATION_HIDDEN,
NAVIGATIONHIDDEN_UNDEFINED);
configOut.orientation = XmlUtils.readIntAttribute(parser, XML_ATTR_ORIENTATION,
ORIENTATION_UNDEFINED);
configOut.screenLayout = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_LAYOUT,
SCREENLAYOUT_UNDEFINED);
configOut.uiMode = XmlUtils.readIntAttribute(parser, XML_ATTR_UI_MODE, 0);
configOut.screenWidthDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_WIDTH,
SCREEN_WIDTH_DP_UNDEFINED);
configOut.screenHeightDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_HEIGHT,
SCREEN_HEIGHT_DP_UNDEFINED);
configOut.smallestScreenWidthDp =
XmlUtils.readIntAttribute(parser, XML_ATTR_SMALLEST_WIDTH,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
configOut.densityDpi = XmlUtils.readIntAttribute(parser, XML_ATTR_DENSITY,
DENSITY_DPI_UNDEFINED);
}
/**
* Writes the Configuration's member fields as attributes into the XmlSerializer.
* The serializer is expected to have already started a tag so that attributes can be
* immediately written.
*
* @param xml The serializer to which to write the attributes.
* @param config The Configuration whose member fields to write.
* {@hide}
*/
public static void writeXmlAttrs(XmlSerializer xml, Configuration config) throws IOException {
XmlUtils.writeIntAttribute(xml, XML_ATTR_FONT_SCALE,
Float.floatToIntBits(config.fontScale));
if (config.mcc != 0) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_MCC, config.mcc);
}
if (config.mnc != 0) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_MNC, config.mnc);
}
if (config.locale != null) {
XmlUtils.writeStringAttribute(xml, XML_ATTR_LOCALE, config.locale.toLanguageTag());
}
if (config.touchscreen != TOUCHSCREEN_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_TOUCHSCREEN, config.touchscreen);
}
if (config.keyboard != KEYBOARD_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_KEYBOARD, config.keyboard);
}
if (config.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_KEYBOARD_HIDDEN, config.keyboardHidden);
}
if (config.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_HARD_KEYBOARD_HIDDEN,
config.hardKeyboardHidden);
}
if (config.navigation != NAVIGATION_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_NAVIGATION, config.navigation);
}
if (config.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_NAVIGATION_HIDDEN, config.navigationHidden);
}
if (config.orientation != ORIENTATION_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_ORIENTATION, config.orientation);
}
if (config.screenLayout != SCREENLAYOUT_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_LAYOUT, config.screenLayout);
}
if (config.uiMode != 0) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_UI_MODE, config.uiMode);
}
if (config.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_WIDTH, config.screenWidthDp);
}
if (config.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_HEIGHT, config.screenHeightDp);
}
if (config.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_SMALLEST_WIDTH, config.smallestScreenWidthDp);
}
if (config.densityDpi != DENSITY_DPI_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_DENSITY, config.densityDpi);
}
}
}

View File

@@ -3209,7 +3209,6 @@ public final class ActivityManagerService extends ActivityManagerNative
if (resumed) {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(component.realActivity, component.userId,
System.currentTimeMillis(),
UsageEvents.Event.MOVE_TO_FOREGROUND);
}
synchronized (stats) {
@@ -3218,7 +3217,6 @@ public final class ActivityManagerService extends ActivityManagerNative
} else {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(component.realActivity, component.userId,
System.currentTimeMillis(),
UsageEvents.Event.MOVE_TO_BACKGROUND);
}
synchronized (stats) {
@@ -15918,6 +15916,7 @@ public final class ActivityManagerService extends ActivityManagerNative
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
//mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);

View File

@@ -15,17 +15,23 @@
*/
package com.android.server.usage;
import android.app.usage.ConfigurationStats;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.ArrayList;
class IntervalStats {
public long beginTime;
public long endTime;
public long lastTimeSaved;
public final ArrayMap<String, UsageStats> stats = new ArrayMap<>();
public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
public Configuration activeConfiguration;
public TimeSparseArray<UsageEvents.Event> events;
// A string cache. This is important as when we're parsing XML files, we don't want to
@@ -34,18 +40,49 @@ class IntervalStats {
// strings that had identical copies in the cache.
private final ArraySet<String> mStringCache = new ArraySet<>();
/**
* Gets the UsageStats object for the given package, or creates one and adds it internally.
*/
UsageStats getOrCreateUsageStats(String packageName) {
UsageStats usageStats = stats.get(packageName);
if (usageStats == null) {
usageStats = new UsageStats();
usageStats.mPackageName = packageName;
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
stats.put(packageName, usageStats);
stats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
}
/**
* Gets the ConfigurationStats object for the given configuration, or creates one and adds it
* internally.
*/
ConfigurationStats getOrCreateConfigurationStats(Configuration config) {
ConfigurationStats configStats = configurations.get(config);
if (configStats == null) {
configStats = new ConfigurationStats();
configStats.mBeginTimeStamp = beginTime;
configStats.mEndTimeStamp = endTime;
configStats.mConfiguration = config;
configurations.put(config, configStats);
}
return configStats;
}
/**
* Builds a UsageEvents.Event, but does not add it internally.
*/
UsageEvents.Event buildEvent(String packageName, String className) {
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = getCachedStringRef(packageName);
if (className != null) {
event.mClass = getCachedStringRef(className);
}
return event;
}
void update(String packageName, long timeStamp, int eventType) {
UsageStats usageStats = getOrCreateUsageStats(packageName);
@@ -61,6 +98,28 @@ class IntervalStats {
usageStats.mLastEvent = eventType;
usageStats.mLastTimeUsed = timeStamp;
usageStats.mEndTimeStamp = timeStamp;
if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
usageStats.mLaunchCount += 1;
}
endTime = timeStamp;
}
void updateConfigurationStats(Configuration config, long timeStamp) {
if (activeConfiguration != null) {
ConfigurationStats activeStats = configurations.get(activeConfiguration);
activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive;
activeStats.mLastTimeActive = timeStamp - 1;
}
if (config != null) {
ConfigurationStats configStats = getOrCreateConfigurationStats(config);
configStats.mLastTimeActive = timeStamp;
configStats.mActivationCount += 1;
activeConfiguration = configStats.mConfiguration;
}
endTime = timeStamp;
}
@@ -72,13 +131,4 @@ class IntervalStats {
}
return mStringCache.valueAt(index);
}
UsageEvents.Event buildEvent(String packageName, String className) {
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = getCachedStringRef(packageName);
if (className != null) {
event.mClass = getCachedStringRef(className);
}
return event;
}
}

View File

@@ -17,7 +17,6 @@
package com.android.server.usage;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.util.AtomicFile;
import android.util.Slog;
@@ -133,9 +132,30 @@ class UsageStatsDatabase {
}
/**
* Find all {@link UsageStats} for the given range and interval type.
* Figures out what to extract from the given IntervalStats object.
*/
public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
interface StatCombiner<T> {
/**
* Implementations should extract interesting from <code>stats</code> and add it
* to the <code>accumulatedResult</code> list.
*
* If the <code>stats</code> object is mutable, <code>mutable</code> will be true,
* which means you should make a copy of the data before adding it to the
* <code>accumulatedResult</code> list.
*
* @param stats The {@link IntervalStats} object selected.
* @param mutable Whether or not the data inside the stats object is mutable.
* @param accumulatedResult The list to which to add extracted data.
*/
void combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult);
}
/**
* Find all {@link IntervalStats} for the given range and interval type.
*/
public <T> List<T> queryUsageStats(int intervalType, long beginTime, long endTime,
StatCombiner<T> combiner) {
synchronized (mLock) {
if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
throw new IllegalArgumentException("Bad interval type " + intervalType);
@@ -157,7 +177,7 @@ class UsageStatsDatabase {
try {
IntervalStats stats = new IntervalStats();
ArrayList<UsageStats> results = new ArrayList<>();
ArrayList<T> results = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i);
@@ -167,7 +187,7 @@ class UsageStatsDatabase {
UsageStatsXml.read(f, stats);
if (beginTime < stats.endTime) {
results.addAll(stats.stats.values());
combiner.combine(stats, false, results);
}
}
return results;
@@ -209,6 +229,10 @@ class UsageStatsDatabase {
public void prune() {
synchronized (mLock) {
long timeNow = System.currentTimeMillis();
mCal.setTimeInMillis(timeNow);
mCal.add(Calendar.YEAR, -3);
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY],
mCal.getTimeInMillis());
mCal.setTimeInMillis(timeNow);
mCal.add(Calendar.MONTH, -6);

View File

@@ -18,6 +18,7 @@ package com.android.server.usage;
import android.Manifest;
import android.app.AppOpsManager;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
@@ -30,11 +31,13 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -218,8 +221,7 @@ public class UsageStatsService extends SystemService implements
* Called by the Binder stub.
*/
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) {
final long timeNow = System.currentTimeMillis();
if (beginTime > timeNow) {
if (!validRange(beginTime, endTime)) {
return null;
}
@@ -229,18 +231,26 @@ public class UsageStatsService extends SystemService implements
}
}
/**
* Called by the Binder stub.
*/
List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
long endTime) {
if (!validRange(beginTime, endTime)) {
return null;
}
synchronized (mLock) {
UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId);
return service.queryConfigurationStats(bucketType, beginTime, endTime);
}
}
/**
* Called by the Binder stub.
*/
UsageEvents queryEvents(int userId, long beginTime, long endTime) {
final long timeNow = System.currentTimeMillis();
// Adjust the endTime so that we don't query for the latest events.
// This is to prevent apps from making decision based on what app launched them,
// etc.
endTime = Math.min(endTime, timeNow - END_TIME_DELAY);
if (beginTime > endTime) {
if (!validRange(beginTime, endTime)) {
return null;
}
@@ -250,6 +260,11 @@ public class UsageStatsService extends SystemService implements
}
}
private static boolean validRange(long beginTime, long endTime) {
final long timeNow = System.currentTimeMillis();
return beginTime <= timeNow && beginTime < endTime;
}
private void flushToDiskLocked() {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
@@ -322,6 +337,28 @@ public class UsageStatsService extends SystemService implements
return null;
}
@Override
public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType,
long beginTime, long endTime, String callingPackage) throws RemoteException {
if (!hasPermission(callingPackage)) {
return null;
}
final int userId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
final List<ConfigurationStats> results =
UsageStatsService.this.queryConfigurationStats(userId, bucketType,
beginTime, endTime);
if (results != null) {
return new ParceledListSlice<>(results);
}
} finally {
Binder.restoreCallingIdentity(token);
}
return null;
}
@Override
public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) {
if (!hasPermission(callingPackage)) {
@@ -346,8 +383,7 @@ public class UsageStatsService extends SystemService implements
private class LocalService extends UsageStatsManagerInternal {
@Override
public void reportEvent(ComponentName component, int userId,
long timeStamp, int eventType) {
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
@@ -356,11 +392,26 @@ public class UsageStatsService extends SystemService implements
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
event.mTimeStamp = timeStamp;
event.mTimeStamp = System.currentTimeMillis();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportConfigurationChange(Configuration config, int userId) {
if (config == null) {
Slog.w(TAG, "Configuration event reported with a null config");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = "android";
event.mTimeStamp = System.currentTimeMillis();
event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else

View File

@@ -20,68 +20,69 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.app.usage.ConfigurationStats;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.ComponentName;
import android.content.res.Configuration;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.Locale;
/**
* UsageStats reader/writer for version 1 of the XML format.
*/
final class UsageStatsXmlV1 {
private static final String PACKAGE_TAG = "package";
private static final String CONFIGURATION_TAG = "config";
private static final String EVENT_LOG_TAG = "event-log";
private static final String BEGIN_TIME_ATTR = "beginTime";
private static final String END_TIME_ATTR = "endTime";
private static final String PACKAGE_TAG = "package";
private static final String NAME_ATTR = "name";
private static final String PACKAGE_ATTR = "package";
private static final String CLASS_ATTR = "class";
private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive";
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
private static final String COUNT_ATTR = "count";
private static final String ACTIVE_ATTR = "active";
private static final String LAST_EVENT_ATTR = "lastEvent";
private static final String EVENT_LOG_TAG = "event-log";
private static final String TYPE_ATTR = "type";
private static final String TIME_ATTR = "time";
private static UsageStats readNextUsageStats(XmlPullParser parser)
private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
XmlUtils.nextElement(parser);
}
if (parser.getEventType() != XmlPullParser.START_TAG ||
!parser.getName().equals(PACKAGE_TAG)) {
return null;
}
final String name = parser.getAttributeValue(null, NAME_ATTR);
if (name == null) {
throw new ProtocolException("no " + NAME_ATTR + " attribute present");
}
UsageStats stats = new UsageStats();
stats.mPackageName = name;
UsageStats stats = statsOut.getOrCreateUsageStats(name);
stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
stats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
XmlUtils.skipCurrentTag(parser);
return stats;
}
private static UsageEvents.Event readNextEvent(XmlPullParser parser, IntervalStats statsOut)
private static void loadConfigStats(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
XmlUtils.nextElement(parser);
}
final Configuration config = new Configuration();
Configuration.readXmlAttrs(parser, config);
if (parser.getEventType() != XmlPullParser.START_TAG ||
!parser.getName().equals(EVENT_LOG_TAG)) {
return null;
ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
configStats.mLastTimeActive = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
configStats.mTotalTimeActive = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
configStats.mActivationCount = XmlUtils.readIntAttribute(parser, COUNT_ATTR);
if (XmlUtils.readBooleanAttribute(parser, ACTIVE_ATTR)) {
statsOut.activeConfiguration = configStats.mConfiguration;
}
}
private static void loadEvent(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
String className;
if (packageName == null) {
@@ -105,31 +106,60 @@ final class UsageStatsXmlV1 {
UsageEvents.Event event = statsOut.buildEvent(packageName, className);
event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR);
XmlUtils.skipCurrentTag(parser);
return event;
}
private static void writeUsageStats(FastXmlSerializer serializer, UsageStats stats)
throws IOException {
serializer.startTag(null, PACKAGE_TAG);
serializer.attribute(null, NAME_ATTR, stats.mPackageName);
serializer.attribute(null, TOTAL_TIME_ACTIVE_ATTR,
Long.toString(stats.mTotalTimeInForeground));
serializer.attribute(null, LAST_TIME_ACTIVE_ATTR, Long.toString(stats.mLastTimeUsed));
serializer.attribute(null, LAST_EVENT_ATTR, Integer.toString(stats.mLastEvent));
serializer.endTag(null, PACKAGE_TAG);
}
private static void writeEvent(FastXmlSerializer serializer, UsageEvents.Event event)
throws IOException {
serializer.startTag(null, EVENT_LOG_TAG);
serializer.attribute(null, PACKAGE_ATTR, event.mPackage);
if (event.mClass != null) {
serializer.attribute(null, CLASS_ATTR, event.mClass);
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
event.mConfiguration = new Configuration();
Configuration.readXmlAttrs(parser, event.mConfiguration);
}
serializer.attribute(null, TYPE_ATTR, Integer.toString(event.getEventType()));
serializer.attribute(null, TIME_ATTR, Long.toString(event.getTimeStamp()));
serializer.endTag(null, EVENT_LOG_TAG);
if (statsOut.events == null) {
statsOut.events = new TimeSparseArray<>();
}
statsOut.events.put(event.mTimeStamp, event);
}
private static void writeUsageStats(XmlSerializer xml, final UsageStats stats)
throws IOException {
xml.startTag(null, PACKAGE_TAG);
XmlUtils.writeStringAttribute(xml, NAME_ATTR, stats.mPackageName);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeInForeground);
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeUsed);
XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, stats.mLastEvent);
xml.endTag(null, PACKAGE_TAG);
}
private static void writeConfigStats(XmlSerializer xml, final ConfigurationStats stats,
boolean isActive) throws IOException {
xml.startTag(null, CONFIGURATION_TAG);
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeActive);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeActive);
XmlUtils.writeIntAttribute(xml, COUNT_ATTR, stats.mActivationCount);
if (isActive) {
XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true);
}
// Now write the attributes representing the configuration object.
Configuration.writeXmlAttrs(xml, stats.mConfiguration);
xml.endTag(null, CONFIGURATION_TAG);
}
private static void writeEvent(XmlSerializer xml, final UsageEvents.Event event)
throws IOException {
xml.startTag(null, EVENT_LOG_TAG);
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage);
if (event.mClass != null) {
XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass);
}
XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp);
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE
&& event.mConfiguration != null) {
Configuration.writeXmlAttrs(xml, event.mConfiguration);
}
xml.endTag(null, EVENT_LOG_TAG);
}
/**
@@ -142,6 +172,8 @@ final class UsageStatsXmlV1 {
public static void read(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
statsOut.stats.clear();
statsOut.configurations.clear();
statsOut.activeConfiguration = null;
if (statsOut.events != null) {
statsOut.events.clear();
@@ -149,21 +181,29 @@ final class UsageStatsXmlV1 {
statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR);
statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
XmlUtils.nextElement(parser);
UsageStats pkgStats;
while ((pkgStats = readNextUsageStats(parser)) != null) {
pkgStats.mBeginTimeStamp = statsOut.beginTime;
pkgStats.mEndTimeStamp = statsOut.endTime;
statsOut.stats.put(pkgStats.mPackageName, pkgStats);
}
UsageEvents.Event event;
while ((event = readNextEvent(parser, statsOut)) != null) {
if (statsOut.events == null) {
statsOut.events = new TimeSparseArray<>();
int eventCode;
int outerDepth = parser.getDepth();
while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT
&& (eventCode != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (eventCode != XmlPullParser.START_TAG) {
continue;
}
final String tag = parser.getName();
switch (tag) {
case PACKAGE_TAG:
loadUsageStats(parser, statsOut);
break;
case CONFIGURATION_TAG:
loadConfigStats(parser, statsOut);
break;
case EVENT_LOG_TAG:
loadEvent(parser, statsOut);
break;
}
statsOut.events.put(event.getTimeStamp(), event);
}
}
@@ -184,11 +224,15 @@ final class UsageStatsXmlV1 {
writeUsageStats(serializer, stats.stats.valueAt(i));
}
if (stats.events != null) {
final int eventCount = stats.events.size();
for (int i = 0; i < eventCount; i++) {
writeEvent(serializer, stats.events.valueAt(i));
}
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
writeConfigStats(serializer, stats.configurations.valueAt(i), active);
}
final int eventCount = stats.events != null ? stats.events.size() : 0;
for (int i = 0; i < eventCount; i++) {
writeEvent(serializer, stats.events.valueAt(i));
}
}

View File

@@ -16,13 +16,17 @@
package com.android.server.usage;
import android.app.usage.ConfigurationStats;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.res.Configuration;
import android.util.ArraySet;
import android.util.Slog;
import com.android.server.usage.UsageStatsDatabase.StatCombiner;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
@@ -108,35 +112,91 @@ class UserUsageStatsService {
notifyStatsChanged();
}
}
stat.updateConfigurationStats(null, stat.lastTimeSaved);
}
}
void reportEvent(UsageEvents.Event event) {
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
+ "[" + event.getTimeStamp() + "]: "
+ eventToString(event.getEventType()));
+ "[" + event.mTimeStamp + "]: "
+ eventToString(event.mEventType));
}
if (event.getTimeStamp() >= mDailyExpiryDate.getTimeInMillis()) {
if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
// Need to rollover
rolloverStats();
}
if (mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events == null) {
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events = new TimeSparseArray<>();
final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
final Configuration newFullConfig = event.mConfiguration;
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
currentDailyStats.activeConfiguration != null) {
// Make the event configuration a delta.
event.mConfiguration = Configuration.generateDelta(
currentDailyStats.activeConfiguration, newFullConfig);
}
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events.put(event.getTimeStamp(), event);
// Add the event to the daily list.
if (currentDailyStats.events == null) {
currentDailyStats.events = new TimeSparseArray<>();
}
currentDailyStats.events.put(event.mTimeStamp, event);
for (IntervalStats stats : mCurrentStats) {
stats.update(event.mPackage, event.getTimeStamp(),
event.getEventType());
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
} else {
stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
}
}
notifyStatsChanged();
}
List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
private static final StatCombiner<UsageStats> sUsageStatsCombiner =
new StatCombiner<UsageStats>() {
@Override
public void combine(IntervalStats stats, boolean mutable,
List<UsageStats> accResult) {
if (!mutable) {
accResult.addAll(stats.stats.values());
return;
}
final int statCount = stats.stats.size();
for (int i = 0; i < statCount; i++) {
accResult.add(new UsageStats(stats.stats.valueAt(i)));
}
}
};
private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
new StatCombiner<ConfigurationStats>() {
@Override
public void combine(IntervalStats stats, boolean mutable,
List<ConfigurationStats> accResult) {
if (!mutable) {
accResult.addAll(stats.configurations.values());
return;
}
final int configCount = stats.configurations.size();
for (int i = 0; i < configCount; i++) {
accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
}
}
};
/**
* Generic query method that selects the appropriate IntervalStats for the specified time range
* and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
* provided to select the stats to use from the IntervalStats object.
*/
private <T> List<T> queryStats(int bucketType, long beginTime, long endTime,
StatCombiner<T> combiner) {
if (bucketType == UsageStatsManager.INTERVAL_BEST) {
bucketType = mDatabase.findBestFitBucket(beginTime, endTime);
}
@@ -161,11 +221,8 @@ class UserUsageStatsService {
Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType);
}
// Fast path for retrieving in-memory state.
ArrayList<UsageStats> results = new ArrayList<>();
final int packageCount = mCurrentStats[bucketType].stats.size();
for (int i = 0; i < packageCount; i++) {
results.add(new UsageStats(mCurrentStats[bucketType].stats.valueAt(i)));
}
ArrayList<T> results = new ArrayList<>();
combiner.combine(mCurrentStats[bucketType], true, results);
return results;
}
@@ -178,13 +235,21 @@ class UserUsageStatsService {
+ beginTime + " AND endTime < " + endTime);
}
final List<UsageStats> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime);
final List<T> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime, combiner);
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Results: " + (results == null ? 0 : results.size()));
}
return results;
}
List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
}
List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
}
UsageEvents queryEvents(long beginTime, long endTime) {
if (endTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime) {
if (beginTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].endTime) {
@@ -245,6 +310,8 @@ class UserUsageStatsService {
// Finish any ongoing events with an END_OF_DAY event. Make a note of which components
// need a new CONTINUE_PREVIOUS_DAY entry.
final Configuration previousConfig =
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
ArraySet<String> continuePreviousDay = new ArraySet<>();
for (IntervalStats stat : mCurrentStats) {
final int pkgCount = stat.stats.size();
@@ -253,11 +320,13 @@ class UserUsageStatsService {
if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
continuePreviousDay.add(pkgStats.mPackageName);
stat.update(pkgStats.mPackageName,
mDailyExpiryDate.getTimeInMillis() - 1, UsageEvents.Event.END_OF_DAY);
stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
UsageEvents.Event.END_OF_DAY);
mStatsChanged = true;
}
}
stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
}
persistActiveStats();
@@ -267,9 +336,10 @@ class UserUsageStatsService {
final int continueCount = continuePreviousDay.size();
for (int i = 0; i < continueCount; i++) {
String name = continuePreviousDay.valueAt(i);
final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
for (IntervalStats stat : mCurrentStats) {
stat.update(name, mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime,
UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
stat.updateConfigurationStats(previousConfig, beginTime);
mStatsChanged = true;
}
}
@@ -353,6 +423,8 @@ class UserUsageStatsService {
return "END_OF_DAY";
case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
return "CONTINUE_PREVIOUS_DAY";
case UsageEvents.Event.CONFIGURATION_CHANGE:
return "CONFIGURATION_CHANGE";
default:
return "UNKNOWN";
}

View File

@@ -6,6 +6,8 @@ LOCAL_MODULE_TAGS := tests
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
LOCAL_PACKAGE_NAME := UsageStatsTest
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"/>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
@@ -8,13 +9,10 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_weight="1"
android:textStyle="bold"/>
<TextView android:id="@android:id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignBaseline="@android:id/text1"/>
</RelativeLayout>
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@@ -21,6 +21,7 @@ import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.util.CircularArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -35,11 +36,14 @@ public class UsageLogActivity extends ListActivity implements Runnable {
private UsageStatsManager mUsageStatsManager;
private Adapter mAdapter;
private Handler mHandler = new Handler();
private long mLastTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
mLastTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
mAdapter = new Adapter();
setListAdapter(mAdapter);
}
@@ -59,24 +63,31 @@ public class UsageLogActivity extends ListActivity implements Runnable {
@Override
public void run() {
long now = System.currentTimeMillis();
long beginTime = now - USAGE_STATS_PERIOD;
UsageEvents events = mUsageStatsManager.queryEvents(beginTime, now);
mAdapter.update(events);
UsageEvents events = mUsageStatsManager.queryEvents(mLastTime, now);
long lastEventTime = mAdapter.update(events);
if (lastEventTime >= 0) {
mLastTime = lastEventTime + 1;
}
mHandler.postDelayed(this, 1000 * 5);
}
private class Adapter extends BaseAdapter {
private static final int MAX_EVENTS = 50;
private final CircularArray<UsageEvents.Event> mEvents = new CircularArray<>(MAX_EVENTS);
private final ArrayList<UsageEvents.Event> mEvents = new ArrayList<>();
public void update(UsageEvents results) {
mEvents.clear();
public long update(UsageEvents results) {
long lastTimeStamp = -1;
while (results.hasNextEvent()) {
UsageEvents.Event event = new UsageEvents.Event();
results.getNextEvent(event);
mEvents.add(event);
lastTimeStamp = event.getTimeStamp();
if (mEvents.size() == MAX_EVENTS) {
mEvents.popLast();
}
mEvents.addFirst(event);
}
notifyDataSetChanged();
return lastTimeStamp;
}
@Override
@@ -85,7 +96,7 @@ public class UsageLogActivity extends ListActivity implements Runnable {
}
@Override
public Object getItem(int position) {
public UsageEvents.Event getItem(int position) {
return mEvents.get(position);
}
@@ -94,42 +105,73 @@ public class UsageLogActivity extends ListActivity implements Runnable {
return position;
}
@Override
public int getItemViewType(int position) {
final int eventType = getItem(position).getEventType();
if (eventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
return 1;
}
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final UsageEvents.Event event = getItem(position);
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(UsageLogActivity.this)
.inflate(R.layout.row_item, parent, false);
holder = new ViewHolder();
holder.packageName = (TextView) convertView.findViewById(android.R.id.text1);
holder.state = (TextView) convertView.findViewById(android.R.id.text2);
if (event.getEventType() == UsageEvents.Event.CONFIGURATION_CHANGE) {
convertView = LayoutInflater.from(UsageLogActivity.this)
.inflate(R.layout.config_row_item, parent, false);
holder.config = (TextView) convertView.findViewById(android.R.id.text1);
} else {
convertView = LayoutInflater.from(UsageLogActivity.this)
.inflate(R.layout.row_item, parent, false);
holder.packageName = (TextView) convertView.findViewById(android.R.id.text1);
holder.state = (TextView) convertView.findViewById(android.R.id.text2);
}
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.packageName.setText(mEvents.get(position).getPackageName());
String state;
switch (mEvents.get(position).getEventType()) {
if (holder.packageName != null) {
holder.packageName.setText(event.getPackageName());
}
if (holder.state != null) {
holder.state.setText(eventToString(event.getEventType()));
}
if (holder.config != null &&
event.getEventType() == UsageEvents.Event.CONFIGURATION_CHANGE) {
holder.config.setText(event.getConfiguration().toString());
}
return convertView;
}
private String eventToString(int eventType) {
switch (eventType) {
case UsageEvents.Event.MOVE_TO_FOREGROUND:
state = "Foreground";
break;
return "Foreground";
case UsageEvents.Event.MOVE_TO_BACKGROUND:
state = "Background";
break;
return "Background";
case UsageEvents.Event.CONFIGURATION_CHANGE:
return "Config change";
default:
state = "Unknown: " + mEvents.get(position).getEventType();
break;
return "Unknown: " + eventType;
}
holder.state.setText(state);
return convertView;
}
}
static class ViewHolder {
public TextView packageName;
public TextView state;
public TextView config;
}
}