From ced54398cc0dfd2f782153560c2ffd0eb8743045 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 26 Feb 2018 13:07:42 -0800 Subject: [PATCH] Work on issue #74404949: Screen state usage API Add usage stats tracking of screen time. There are two new events, one for when the device is an interactive state, the other for when it is non-interactive. Also add a whole new usage stats API for retrieving aggregated data that is associated with general events, not particular packages. In this case it allows you to find the time the device spent interactive and non-interactive and the count of the transitions in to each of those states. Bug: 74404949 Test: atest CtsUsageStatsTestCases:UsageStatsTest\#testInteractiveEvents Change-Id: Ibe6d55e2aecb0c8519b1358644378ec5c7a4250d --- api/current.txt | 17 ++ core/java/android/app/usage/EventStats.java | 182 ++++++++++++++++++ .../android/app/usage/IUsageStatsManager.aidl | 2 + core/java/android/app/usage/UsageEvents.java | 13 ++ .../android/app/usage/UsageStatsManager.java | 38 ++++ core/java/android/os/Parcel.java | 4 +- .../server/am/ActivityManagerService.java | 17 ++ .../com/android/server/am/UserController.java | 16 ++ .../android/server/am/UserControllerTest.java | 8 + .../android/server/usage/IntervalStats.java | 63 ++++++ .../server/usage/UsageStatsService.java | 40 ++++ .../android/server/usage/UsageStatsXmlV1.java | 37 ++++ .../server/usage/UserUsageStatsService.java | 76 ++++++-- 13 files changed, 497 insertions(+), 16 deletions(-) create mode 100644 core/java/android/app/usage/EventStats.java diff --git a/api/current.txt b/api/current.txt index 5e7fa591385e0..d538421f0b391 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7327,6 +7327,20 @@ package android.app.usage { field public static final android.os.Parcelable.Creator CREATOR; } + public final class EventStats implements android.os.Parcelable { + ctor public EventStats(android.app.usage.EventStats); + method public void add(android.app.usage.EventStats); + method public int describeContents(); + method public int getCount(); + method public int getEventType(); + method public long getFirstTimeStamp(); + method public long getLastTimeStamp(); + method public long getLastTimeUsed(); + method public long getTotalTime(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class ExternalStorageStats implements android.os.Parcelable { method public int describeContents(); method public long getAppBytes(); @@ -7432,6 +7446,8 @@ package android.app.usage { 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 + field public static final int SCREEN_INTERACTIVE = 15; // 0xf + field public static final int SCREEN_NON_INTERACTIVE = 16; // 0x10 field public static final int SHORTCUT_INVOCATION = 8; // 0x8 field public static final int STANDBY_BUCKET_CHANGED = 11; // 0xb field public static final int USER_INTERACTION = 7; // 0x7 @@ -7455,6 +7471,7 @@ package android.app.usage { method public boolean isAppInactive(java.lang.String); method public java.util.Map queryAndAggregateUsageStats(long, long); method public java.util.List queryConfigurations(int, long, long); + method public java.util.List queryEventStats(int, long, long); method public android.app.usage.UsageEvents queryEvents(long, long); method public android.app.usage.UsageEvents queryEventsForSelf(long, long); method public java.util.List queryUsageStats(int, long, long); diff --git a/core/java/android/app/usage/EventStats.java b/core/java/android/app/usage/EventStats.java new file mode 100644 index 0000000000000..b799de9410a36 --- /dev/null +++ b/core/java/android/app/usage/EventStats.java @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2018 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.os.Parcel; +import android.os.Parcelable; + +/** + * Contains usage statistics for an event type for a specific + * time range. + */ +public final class EventStats implements Parcelable { + + /** + * {@hide} + */ + public int mEventType; + + /** + * {@hide} + */ + public long mBeginTimeStamp; + + /** + * {@hide} + */ + public long mEndTimeStamp; + + /** + * Last time used by the user with an explicit action (notification, activity launch). + * {@hide} + */ + public long mLastTimeUsed; + + /** + * {@hide} + */ + public long mTotalTime; + + /** + * {@hide} + */ + public int mCount; + + /** + * {@hide} + */ + public EventStats() { + } + + public EventStats(EventStats stats) { + mEventType = stats.mEventType; + mBeginTimeStamp = stats.mBeginTimeStamp; + mEndTimeStamp = stats.mEndTimeStamp; + mLastTimeUsed = stats.mLastTimeUsed; + mTotalTime = stats.mTotalTime; + mCount = stats.mCount; + } + + /** + * Return the type of event this is usage for. May be one of the event + * constants in {@link UsageEvents.Event}. + */ + public int getEventType() { + return mEventType; + } + + /** + * Get the beginning of the time range this {@link android.app.usage.EventStats} represents, + * measured in milliseconds since the epoch. + *

+ * See {@link System#currentTimeMillis()}. + */ + public long getFirstTimeStamp() { + return mBeginTimeStamp; + } + + /** + * Get the end of the time range this {@link android.app.usage.EventStats} represents, + * measured in milliseconds since the epoch. + *

+ * See {@link System#currentTimeMillis()}. + */ + public long getLastTimeStamp() { + return mEndTimeStamp; + } + + /** + * Get the last time this event was used, measured in milliseconds since the epoch. + *

+ * See {@link System#currentTimeMillis()}. + */ + public long getLastTimeUsed() { + return mLastTimeUsed; + } + + /** + * Return the number of times that this event occurred over the interval. + */ + public int getCount() { + return mCount; + } + + /** + * Get the total time this event was active, measured in milliseconds. + */ + public long getTotalTime() { + return mTotalTime; + } + + /** + * Add the statistics from the right {@link EventStats} to the left. The event type for + * both {@link UsageStats} objects must be the same. + * @param right The {@link EventStats} object to merge into this one. + * @throws java.lang.IllegalArgumentException if the event types of the two + * {@link UsageStats} objects are different. + */ + public void add(EventStats right) { + if (mEventType != right.mEventType) { + throw new IllegalArgumentException("Can't merge EventStats for event #" + + mEventType + " with EventStats for event #" + right.mEventType); + } + + // We use the mBeginTimeStamp due to a bug where UsageStats files can overlap with + // regards to their mEndTimeStamp. + if (right.mBeginTimeStamp > mBeginTimeStamp) { + mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed); + } + mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp); + mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); + mTotalTime += right.mTotalTime; + mCount += right.mCount; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEventType); + dest.writeLong(mBeginTimeStamp); + dest.writeLong(mEndTimeStamp); + dest.writeLong(mLastTimeUsed); + dest.writeLong(mTotalTime); + dest.writeInt(mCount); + } + + public static final Creator CREATOR = new Creator() { + @Override + public EventStats createFromParcel(Parcel in) { + EventStats stats = new EventStats(); + stats.mEventType = in.readInt(); + stats.mBeginTimeStamp = in.readLong(); + stats.mEndTimeStamp = in.readLong(); + stats.mLastTimeUsed = in.readLong(); + stats.mTotalTime = in.readLong(); + stats.mCount = in.readInt(); + return stats; + } + + @Override + public EventStats[] newArray(int size) { + return new EventStats[size]; + } + }; +} diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index d52bd37641014..00d8711c7baac 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -32,6 +32,8 @@ interface IUsageStatsManager { String callingPackage); ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime, String callingPackage); + ParceledListSlice queryEventStats(int bucketType, long beginTime, long endTime, + String callingPackage); UsageEvents queryEvents(long beginTime, long endTime, String callingPackage); UsageEvents queryEventsForPackage(long beginTime, long endTime, String callingPackage); void setAppInactive(String packageName, boolean inactive, int userId); diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index b354e8122a980..a665652120009 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -139,6 +139,19 @@ public final class UsageEvents implements Parcelable { @SystemApi public static final int SLICE_PINNED = 14; + /** + * An event type denoting that the screen has gone in to an interactive state (turned + * on for full user interaction, not ambient display or other non-interactive state). + */ + public static final int SCREEN_INTERACTIVE = 15; + + /** + * An event type denoting that the screen has gone in to a non-interactive state + * (completely turned off or turned on only in a non-interactive state like ambient + * display). + */ + public static final int SCREEN_NON_INTERACTIVE = 16; + /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 47441476f95b0..6feb5278a48cb 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -300,6 +300,44 @@ public final class UsageStatsManager { return Collections.emptyList(); } + /** + * Gets aggregated event stats for the given time range, aggregated by the specified interval. + *

The returned list will contain a {@link EventStats} object for each event type that + * is being aggregated and has data for an interval that is a subset of the time range given. + * + *

The current event types that will be aggregated here are:

+ *
    + *
  • {@link UsageEvents.Event#SCREEN_INTERACTIVE}
  • + *
  • {@link UsageEvents.Event#SCREEN_NON_INTERACTIVE}
  • + *
+ * + *

The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS}

+ * + * @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 EventStats} + * + * @see #INTERVAL_DAILY + * @see #INTERVAL_WEEKLY + * @see #INTERVAL_MONTHLY + * @see #INTERVAL_YEARLY + * @see #INTERVAL_BEST + */ + public List queryEventStats(int intervalType, long beginTime, long endTime) { + try { + @SuppressWarnings("unchecked") + ParceledListSlice slice = mService.queryEventStats(intervalType, beginTime, + endTime, mContext.getOpPackageName()); + if (slice != null) { + return slice.getList(); + } + } catch (RemoteException e) { + // fallthrough and return the empty list. + } + return Collections.emptyList(); + } + /** * Query for events in the given time range. Events are only kept by the system for a few * days. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 62bb38540e449..e3c48700b4c0c 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -2803,8 +2803,8 @@ public final class Parcel { Class parcelableClass = Class.forName(name, false /* initialize */, parcelableClassLoader); if (!Parcelable.class.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable protocol requires that the " - + "class implements Parcelable"); + throw new BadParcelableException("Parcelable protocol requires subclassing " + + "from Parcelable on class " + name); } Field f = parcelableClass.getField("CREATOR"); if ((f.getModifiers() & Modifier.STATIC) == 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4f0acf73b9011..f2e6d2295e255 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13029,6 +13029,22 @@ public class ActivityManagerService extends IActivityManager.Stub return mSleeping; } + void reportGlobalUsageEventLocked(int event) { + mUsageStatsService.reportEvent("android", mUserController.getCurrentUserId(), event); + int[] profiles = mUserController.getCurrentProfileIds(); + if (profiles != null) { + for (int i = profiles.length - 1; i >= 0; i--) { + mUsageStatsService.reportEvent((String)null, profiles[i], event); + } + } + } + + void reportCurWakefulnessUsageEventLocked() { + reportGlobalUsageEventLocked(mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE + ? UsageEvents.Event.SCREEN_INTERACTIVE + : UsageEvents.Event.SCREEN_NON_INTERACTIVE); + } + void onWakefulnessChanged(int wakefulness) { synchronized(this) { boolean wasAwake = mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE; @@ -13038,6 +13054,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (wasAwake != isAwake) { // Also update state in a special way for running foreground services UI. mServices.updateScreenStateLocked(isAwake); + reportCurWakefulnessUsageEventLocked(); mHandler.obtainMessage(DISPATCH_SCREEN_AWAKE_MSG, isAwake ? 1 : 0, 0) .sendToTarget(); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 0d125e0515499..f710690fbd2f1 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -47,6 +47,7 @@ import android.app.Dialog; import android.app.IStopUserCallback; import android.app.IUserSwitchObserver; import android.app.KeyguardManager; +import android.app.usage.UsageEvents; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; @@ -959,6 +960,8 @@ class UserController implements Handler.Callback { mInjector.getUserManagerInternal().setUserState(userId, uss.state); } if (foreground) { + // Make sure the old user is no longer considering the display to be on. + mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE); synchronized (mLock) { mCurrentUserId = userId; mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up @@ -966,6 +969,7 @@ class UserController implements Handler.Callback { mInjector.updateUserConfiguration(); updateCurrentProfileIds(); mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds()); + mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device // with the option to show the user switcher on the keyguard. if (mUserSwitchUiEnabled) { @@ -2183,6 +2187,18 @@ class UserController implements Handler.Callback { d.show(); } + void reportGlobalUsageEventLocked(int event) { + synchronized (mService) { + mService.reportGlobalUsageEventLocked(event); + } + } + + void reportCurWakefulnessUsageEvent() { + synchronized (mService) { + mService.reportCurWakefulnessUsageEventLocked(); + } + } + void stackSupervisorRemoveUser(int userId) { synchronized (mService) { mService.mStackSupervisor.removeUserLocked(userId); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 6fdb02988feb6..3c10b0394ff92 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -389,6 +389,14 @@ public class UserControllerTest extends AndroidTestCase { sentIntents.add(intent); return 0; } + + @Override + void reportGlobalUsageEventLocked(int event) { + } + + @Override + void reportCurWakefulnessUsageEvent() { + } } private static class TestHandler extends Handler { diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 00826e057a861..b070e03607cb6 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -16,6 +16,7 @@ package com.android.server.usage; import android.app.usage.ConfigurationStats; +import android.app.usage.EventStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; @@ -23,10 +24,18 @@ import android.content.res.Configuration; import android.util.ArrayMap; import android.util.ArraySet; +import java.util.List; + class IntervalStats { public long beginTime; public long endTime; public long lastTimeSaved; + public long lastInteractiveTime; + public long lastNonInteractiveTime; + public long interactiveDuration; + public int interactiveCount; + public long nonInteractiveDuration; + public int nonInteractiveCount; public final ArrayMap packageStats = new ArrayMap<>(); public final ArrayMap configurations = new ArrayMap<>(); public Configuration activeConfiguration; @@ -171,6 +180,60 @@ class IntervalStats { usageStats.mAppLaunchCount += 1; } + private void commitInteractiveTime(long timeStamp) { + if (lastInteractiveTime != 0) { + interactiveDuration += timeStamp - lastInteractiveTime; + lastInteractiveTime = 0; + } + if (lastNonInteractiveTime != 0) { + nonInteractiveDuration += timeStamp - lastNonInteractiveTime; + lastNonInteractiveTime = 0; + } + } + + void commitTime(long timeStamp) { + commitInteractiveTime(timeStamp); + } + + void updateScreenInteractive(long timeStamp) { + if (lastInteractiveTime != 0) { + // Already interactive, just keep running. + return; + } + commitInteractiveTime(timeStamp); + lastInteractiveTime = timeStamp; + interactiveCount++; + } + + void updateScreenNonInteractive(long timeStamp) { + if (lastNonInteractiveTime != 0) { + // Already non-interactive, just keep running. + return; + } + commitInteractiveTime(timeStamp); + lastNonInteractiveTime = timeStamp; + nonInteractiveCount++; + } + + private void addOneEventStats(List out, int event, int count, long duration) { + if (count != 0 || duration != 0) { + EventStats ev = new EventStats(); + ev.mEventType = event; + ev.mCount = count; + ev.mTotalTime = duration; + ev.mBeginTimeStamp = beginTime; + ev.mEndTimeStamp = endTime; + out.add(ev); + } + } + + void addEventStatsTo(List out) { + addOneEventStats(out, UsageEvents.Event.SCREEN_INTERACTIVE, interactiveCount, + interactiveDuration); + addOneEventStats(out, UsageEvents.Event.SCREEN_NON_INTERACTIVE, nonInteractiveCount, + nonInteractiveDuration); + } + private String getCachedStringRef(String str) { final int index = mStringCache.indexOf(str); if (index < 0) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index b8317b470d017..2258b243cc3a9 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -25,6 +25,7 @@ import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.AppStandbyInfo; import android.app.usage.ConfigurationStats; +import android.app.usage.EventStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; @@ -483,6 +484,23 @@ public class UsageStatsService extends SystemService implements } } + /** + * Called by the Binder stub. + */ + List queryEventStats(int userId, int bucketType, long beginTime, + long endTime) { + synchronized (mLock) { + final long timeNow = checkAndGetTimeLocked(); + if (!validRange(timeNow, beginTime, endTime)) { + return null; + } + + final UserUsageStatsService service = + getUserDataAndInitializeIfNeededLocked(userId, timeNow); + return service.queryEventStats(bucketType, beginTime, endTime); + } + } + /** * Called by the Binder stub. */ @@ -712,6 +730,28 @@ public class UsageStatsService extends SystemService implements return null; } + @Override + public ParceledListSlice queryEventStats(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 results = + UsageStatsService.this.queryEventStats(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)) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index bcfc421693745..2287b2770f457 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -27,6 +27,7 @@ import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.content.res.Configuration; import android.util.ArrayMap; +import android.util.Pair; import java.io.IOException; import java.net.ProtocolException; @@ -37,6 +38,9 @@ import java.net.ProtocolException; final class UsageStatsXmlV1 { private static final String TAG = "UsageStatsXmlV1"; + private static final String INTERACTIVE_TAG = "interactive"; + private static final String NON_INTERACTIVE_TAG = "non-interactive"; + private static final String PACKAGES_TAG = "packages"; private static final String PACKAGE_TAG = "package"; @@ -99,6 +103,14 @@ final class UsageStatsXmlV1 { } } + private static Pair loadCountAndTime(XmlPullParser parser) + throws IOException, XmlPullParserException { + int count = XmlUtils.readIntAttribute(parser, COUNT_ATTR, 0); + long time = XmlUtils.readLongAttribute(parser, TIME_ATTR, 0); + XmlUtils.skipCurrentTag(parser); + return new Pair<>(count, time); + } + private static void loadChooserCounts( XmlPullParser parser, UsageStats usageStats, String action) throws XmlPullParserException, IOException { @@ -202,6 +214,14 @@ final class UsageStatsXmlV1 { xml.endTag(null, PACKAGE_TAG); } + private static void writeCountAndTime(XmlSerializer xml, String tag, int count, long time) + throws IOException { + xml.startTag(null, tag); + XmlUtils.writeIntAttribute(xml, COUNT_ATTR, count); + XmlUtils.writeLongAttribute(xml, TIME_ATTR, time); + xml.endTag(null, tag); + } + private static void writeChooserCounts(XmlSerializer xml, final UsageStats usageStats) throws IOException { if (usageStats == null || usageStats.mChooserCounts == null || @@ -320,6 +340,18 @@ final class UsageStatsXmlV1 { final String tag = parser.getName(); switch (tag) { + case INTERACTIVE_TAG: { + Pair result = loadCountAndTime(parser); + statsOut.interactiveCount = result.first; + statsOut.interactiveDuration = result.second; + } break; + + case NON_INTERACTIVE_TAG: { + Pair result = loadCountAndTime(parser); + statsOut.nonInteractiveCount = result.first; + statsOut.nonInteractiveDuration = result.second; + } break; + case PACKAGE_TAG: loadUsageStats(parser, statsOut); break; @@ -346,6 +378,11 @@ final class UsageStatsXmlV1 { public static void write(XmlSerializer xml, IntervalStats stats) throws IOException { XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime); + writeCountAndTime(xml, INTERACTIVE_TAG, stats.interactiveCount, stats.interactiveDuration); + + writeCountAndTime(xml, NON_INTERACTIVE_TAG, stats.nonInteractiveCount, + stats.nonInteractiveDuration); + xml.startTag(null, PACKAGES_TAG); final int statsCount = stats.packageStats.size(); for (int i = 0; i < statsCount; i++) { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index afc3d5924c4aa..6ad374bc42905 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -17,6 +17,7 @@ package com.android.server.usage; import android.app.usage.ConfigurationStats; +import android.app.usage.EventStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; @@ -191,21 +192,31 @@ class UserUsageStatsService { } for (IntervalStats stats : mCurrentStats) { - if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { - stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); - } else if (event.mEventType == UsageEvents.Event.CHOOSER_ACTION) { - stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); - String[] annotations = event.mContentAnnotations; - if (annotations != null) { - for (String annotation : annotations) { - stats.updateChooserCounts(event.mPackage, annotation, event.mAction); + switch (event.mEventType) { + case UsageEvents.Event.CONFIGURATION_CHANGE: { + stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); + } break; + case UsageEvents.Event.CHOOSER_ACTION: { + stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); + String[] annotations = event.mContentAnnotations; + if (annotations != null) { + for (String annotation : annotations) { + stats.updateChooserCounts(event.mPackage, annotation, event.mAction); + } } - } - } else { - stats.update(event.mPackage, event.mTimeStamp, event.mEventType); - if (incrementAppLaunch) { - stats.incrementAppLaunchCount(event.mPackage); - } + } break; + case UsageEvents.Event.SCREEN_INTERACTIVE: { + stats.updateScreenInteractive(event.mTimeStamp); + } break; + case UsageEvents.Event.SCREEN_NON_INTERACTIVE: { + stats.updateScreenNonInteractive(event.mTimeStamp); + } break; + default: { + stats.update(event.mPackage, event.mTimeStamp, event.mEventType); + if (incrementAppLaunch) { + stats.incrementAppLaunchCount(event.mPackage); + } + } break; } } @@ -246,6 +257,15 @@ class UserUsageStatsService { } }; + private static final StatCombiner sEventStatsCombiner = + new StatCombiner() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List accResult) { + stats.addEventStatsTo(accResult); + } + }; + /** * 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} @@ -325,6 +345,10 @@ class UserUsageStatsService { return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner); } + List queryEventStats(int bucketType, long beginTime, long endTime) { + return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner); + } + UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps) { final ArraySet names = new ArraySet<>(); @@ -448,6 +472,7 @@ class UserUsageStatsService { } stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1); + stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1); } persistActiveStats(); @@ -640,6 +665,18 @@ class UserUsageStatsService { } } + void printEventAggregation(IndentingPrintWriter pw, String label, int count, long duration, + boolean prettyDates) { + if (count != 0 || duration != 0) { + pw.print(label); + pw.print(": "); + pw.print(count); + pw.print("x for "); + pw.print(formatElapsedTime(duration, prettyDates)); + pw.println(); + } + } + void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates, boolean skipEvents, String pkg) { if (prettyDates) { @@ -713,6 +750,13 @@ class UserUsageStatsService { pw.println(); } pw.decreaseIndent(); + pw.println("event aggregations"); + pw.increaseIndent(); + printEventAggregation(pw, "screen-interactive", stats.interactiveCount, + stats.interactiveDuration, prettyDates); + printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveCount, + stats.nonInteractiveDuration, prettyDates); + pw.decreaseIndent(); } // The last 24 hours of events is already printed in the non checkin dump @@ -781,6 +825,10 @@ class UserUsageStatsService { return "SLICE_PINNED"; case UsageEvents.Event.SLICE_PINNED_PRIV: return "SLICE_PINNED_PRIV"; + case UsageEvents.Event.SCREEN_INTERACTIVE: + return "SCREEN_INTERACTIVE"; + case UsageEvents.Event.SCREEN_NON_INTERACTIVE: + return "SCREEN_NON_INTERACTIVE"; default: return "UNKNOWN"; }