diff --git a/api/current.txt b/api/current.txt index cc16210f850f4..b61e74862683c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7334,6 +7334,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(); @@ -7439,6 +7453,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 @@ -7462,6 +7478,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 100ec86d1b42a..6b2448b432989 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13067,6 +13067,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; @@ -13076,6 +13092,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"; }