diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-09-02 16:43:52 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-09-04 12:15:32 -0700 |
commit | 7f61e96db7c90c1f4418359672aa4656aebee500 (patch) | |
tree | 6748f94db635e24c26a6a7236c444e2c0d906181 | |
parent | d400ca2f8db42e57e41f2a999833703619348fef (diff) |
Add Configuration changes to UsageStats
Bug:17354208
Change-Id: I9b2f595e51b656607e30e798926cfb7e25134944
17 files changed, 959 insertions, 186 deletions
diff --git a/api/current.txt b/api/current.txt index d12898f2bebd..66a74cd6e3d1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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 diff --git a/core/java/android/app/usage/ConfigurationStats.java b/core/java/android/app/usage/ConfigurationStats.java new file mode 100644 index 000000000000..080216ce3b25 --- /dev/null +++ b/core/java/android/app/usage/ConfigurationStats.java @@ -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]; + } + }; +} diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 3b098881e73f..4ed148911a43 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -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); } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index fb80de2f4700..1a947ecf6c90 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -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; @@ -63,6 +64,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} */ public String mPackage; @@ -83,6 +89,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. diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 5830fcf49e89..bc6099a7d5e3 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -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(); } /** diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 119d7054b1b5..083a48a292d3 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -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. diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index e63fd0703339..27bbb242f575 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -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); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3770dc2833db..f1f7a6aff7ab 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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); diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index dc036e251878..6c80a65ff007 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -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; - } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index e6ce0fe0b45b..37340a4e194c 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -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); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0e8b427bfb2c..e77bf86e6764 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -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; } @@ -232,15 +234,23 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - UsageEvents queryEvents(int userId, long beginTime, long endTime) { - final long timeNow = System.currentTimeMillis(); + List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime, + long endTime) { + if (!validRange(beginTime, endTime)) { + return null; + } - // 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); + synchronized (mLock) { + UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); + return service.queryConfigurationStats(bucketType, beginTime, endTime); + } + } - if (beginTime > endTime) { + /** + * Called by the Binder stub. + */ + UsageEvents queryEvents(int userId, long beginTime, long 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++) { @@ -323,6 +338,28 @@ public class UsageStatsService extends SystemService implements } @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)) { return null; @@ -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,12 +392,27 @@ 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 // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 374429a8c01b..65299507d852 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -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); - } - - if (parser.getEventType() != XmlPullParser.START_TAG || - !parser.getName().equals(EVENT_LOG_TAG)) { - return null; + final Configuration config = new Configuration(); + Configuration.readXmlAttrs(parser, config); + + 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; + + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { + event.mConfiguration = new Configuration(); + Configuration.readXmlAttrs(parser, event.mConfiguration); + } + + if (statsOut.events == null) { + statsOut.events = new TimeSparseArray<>(); + } + statsOut.events.put(event.mTimeStamp, event); } - private static void writeUsageStats(FastXmlSerializer serializer, UsageStats stats) + private static void writeUsageStats(XmlSerializer xml, final 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); + 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(FastXmlSerializer serializer, UsageEvents.Event event) + private static void writeEvent(XmlSerializer xml, final UsageEvents.Event event) throws IOException { - serializer.startTag(null, EVENT_LOG_TAG); - serializer.attribute(null, PACKAGE_ATTR, event.mPackage); + xml.startTag(null, EVENT_LOG_TAG); + XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage); if (event.mClass != null) { - serializer.attribute(null, CLASS_ATTR, event.mClass); + 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); } - serializer.attribute(null, TYPE_ATTR, Integer.toString(event.getEventType())); - serializer.attribute(null, TIME_ATTR, Long.toString(event.getTimeStamp())); - serializer.endTag(null, EVENT_LOG_TAG); + + 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); - } + 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; - UsageEvents.Event event; - while ((event = readNextEvent(parser, statsOut)) != null) { - if (statsOut.events == null) { - statsOut.events = new TimeSparseArray<>(); + 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)); } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 6951590f19da..7142a9999dda 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -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); + } + + // Add the event to the daily list. + if (currentDailyStats.events == null) { + currentDailyStats.events = new TimeSparseArray<>(); } - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events.put(event.getTimeStamp(), event); + 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"; } diff --git a/tests/UsageStatsTest/Android.mk b/tests/UsageStatsTest/Android.mk index 69fefeb5bddf..5f7467a301d2 100644 --- a/tests/UsageStatsTest/Android.mk +++ b/tests/UsageStatsTest/Android.mk @@ -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) diff --git a/tests/UsageStatsTest/res/layout/config_row_item.xml b/tests/UsageStatsTest/res/layout/config_row_item.xml new file mode 100644 index 000000000000..547de04ad728 --- /dev/null +++ b/tests/UsageStatsTest/res/layout/config_row_item.xml @@ -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"/> diff --git a/tests/UsageStatsTest/res/layout/row_item.xml b/tests/UsageStatsTest/res/layout/row_item.xml index da50163c94ab..4f2bfe4222fb 100644 --- a/tests/UsageStatsTest/res/layout/row_item.xml +++ b/tests/UsageStatsTest/res/layout/row_item.xml @@ -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> diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java index 5f62ad8032e7..31e7c38d3a03 100644 --- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java +++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java @@ -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); } @@ -95,41 +106,72 @@ public class UsageLogActivity extends ListActivity implements Runnable { } @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; } } |