Add Task Root package info to UsageEvent
Bug: 113094946
Test: manual (use "adb shell dumpsys usagestats apptimelimit" to verify
apps at the root of tasks are considered active)
Test: atest cts/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java#testTaskRootEventField
Test: atest cts/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java#testUsageSourceAttribution
Test: atest UsageStatsDatabaseTest
Change-Id: I40f86743d33c13892de0e59ae02c9ebddb606ee7
diff --git a/api/system-current.txt b/api/system-current.txt
index 3a26008..01e3c98 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1095,6 +1095,8 @@
public static final class UsageEvents.Event {
method public int getInstanceId();
method public String getNotificationChannelId();
+ method @Nullable public String getTaskRootClassName();
+ method @Nullable public String getTaskRootPackageName();
field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
field public static final int NOTIFICATION_SEEN = 10; // 0xa
field public static final int SLICE_PINNED = 14; // 0xe
@@ -1109,6 +1111,7 @@
public final class UsageStatsManager {
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
+ method public int getUsageSource();
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1124,6 +1127,8 @@
field public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5
field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32
+ field public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; // 0x2
+ field public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; // 0x1
}
}
diff --git a/api/test-current.txt b/api/test-current.txt
index d089831..59e49a5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -352,6 +352,10 @@
method public boolean isReservedSupported(@NonNull java.util.UUID);
}
+ public final class UsageStatsManager {
+ method public void forceUsageSourceSettingRead();
+ }
+
}
package android.bluetooth {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5cac048..86e658d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -219,9 +219,11 @@
* @param userId
* @param event
* @param appToken ActivityRecord's appToken.
+ * @param taskRoot TaskRecord's root
*/
public abstract void updateActivityUsageStats(
- ComponentName activity, int userId, int event, IBinder appToken);
+ ComponentName activity, int userId, int event, IBinder appToken,
+ ComponentName taskRoot);
public abstract void updateForegroundTimeIfOnBattery(
String packageName, int uid, long cpuTimeDiff);
public abstract void sendForegroundProfileChanged(int userId);
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index bbae7d3..d2934b9 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -59,4 +59,6 @@
void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
String callingPackage);
void reportUsageStop(in IBinder activity, String token, String callingPackage);
+ int getUsageSource();
+ void forceUsageSourceSettingRead();
}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 2c5fe04..451f44b 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.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
@@ -286,7 +287,6 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String mClass;
-
/**
* {@hide}
*/
@@ -295,6 +295,16 @@
/**
* {@hide}
*/
+ public String mTaskRootPackage;
+
+ /**
+ * {@hide}
+ */
+ public String mTaskRootClass;
+
+ /**
+ * {@hide}
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public long mTimeStamp;
@@ -373,6 +383,8 @@
mPackage = orig.mPackage;
mClass = orig.mClass;
mInstanceId = orig.mInstanceId;
+ mTaskRootPackage = orig.mTaskRootPackage;
+ mTaskRootClass = orig.mTaskRootClass;
mTimeStamp = orig.mTimeStamp;
mEventType = orig.mEventType;
mConfiguration = orig.mConfiguration;
@@ -411,6 +423,28 @@
}
/**
+ * The package name of the task root when this event was reported.
+ * Or {@code null} for queries from apps without {@link
+ * android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getTaskRootPackageName() {
+ return mTaskRootPackage;
+ }
+
+ /**
+ * The class name of the task root when this event was reported.
+ * Or {@code null} for queries from apps without {@link
+ * android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getTaskRootClassName() {
+ return mTaskRootClass;
+ }
+
+ /**
* The time at which this event occurred, measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
@@ -522,6 +556,9 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mIndex = 0;
+ // Only used when parceling events. If false, task roots will be omitted from the parcel
+ private final boolean mIncludeTaskRoots;
+
/*
* In order to save space, since ComponentNames will be duplicated everywhere,
* we use a map and index into it.
@@ -552,6 +589,7 @@
mParcel.setDataSize(mParcel.dataPosition());
mParcel.setDataPosition(positionInParcel);
}
+ mIncludeTaskRoots = true;
}
/**
@@ -560,16 +598,27 @@
*/
UsageEvents() {
mEventCount = 0;
+ mIncludeTaskRoots = true;
+ }
+
+ /**
+ * Construct the iterator in preparation for writing it to a parcel.
+ * Defaults to excluding task roots from the parcel.
+ * {@hide}
+ */
+ public UsageEvents(List<Event> events, String[] stringPool) {
+ this(events, stringPool, false);
}
/**
* Construct the iterator in preparation for writing it to a parcel.
* {@hide}
*/
- public UsageEvents(List<Event> events, String[] stringPool) {
+ public UsageEvents(List<Event> events, String[] stringPool, boolean includeTaskRoots) {
mStringPool = stringPool;
mEventCount = events.size();
mEventsToWrite = events;
+ mIncludeTaskRoots = includeTaskRoots;
}
/**
@@ -645,9 +694,25 @@
} else {
classIndex = -1;
}
+
+ final int taskRootPackageIndex;
+ if (mIncludeTaskRoots && event.mTaskRootPackage != null) {
+ taskRootPackageIndex = findStringIndex(event.mTaskRootPackage);
+ } else {
+ taskRootPackageIndex = -1;
+ }
+
+ final int taskRootClassIndex;
+ if (mIncludeTaskRoots && event.mTaskRootClass != null) {
+ taskRootClassIndex = findStringIndex(event.mTaskRootClass);
+ } else {
+ taskRootClassIndex = -1;
+ }
p.writeInt(packageIndex);
p.writeInt(classIndex);
p.writeInt(event.mInstanceId);
+ p.writeInt(taskRootPackageIndex);
+ p.writeInt(taskRootClassIndex);
p.writeInt(event.mEventType);
p.writeLong(event.mTimeStamp);
@@ -691,6 +756,21 @@
eventOut.mClass = null;
}
eventOut.mInstanceId = p.readInt();
+
+ final int taskRootPackageIndex = p.readInt();
+ if (taskRootPackageIndex >= 0) {
+ eventOut.mTaskRootPackage = mStringPool[taskRootPackageIndex];
+ } else {
+ eventOut.mTaskRootPackage = null;
+ }
+
+ final int taskRootClassIndex = p.readInt();
+ if (taskRootClassIndex >= 0) {
+ eventOut.mTaskRootClass = mStringPool[taskRootClassIndex];
+ } else {
+ eventOut.mTaskRootClass = null;
+ }
+
eventOut.mEventType = p.readInt();
eventOut.mTimeStamp = p.readLong();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 605deac..d2de887 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -22,6 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
@@ -234,6 +235,29 @@
@SystemApi
public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
+
+ /**
+ * App usage observers will consider the task root package the source of usage.
+ * @hide
+ */
+ @SystemApi
+ public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1;
+
+ /**
+ * App usage observers will consider the visible activity's package the source of usage.
+ * @hide
+ */
+ @SystemApi
+ public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "USAGE_SOURCE_" }, value = {
+ USAGE_SOURCE_TASK_ROOT_ACTIVITY,
+ USAGE_SOURCE_CURRENT_ACTIVITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageSource {}
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private static final UsageEvents sEmptyResults = new UsageEvents();
@@ -776,6 +800,38 @@
}
}
+ /**
+ * Get what App Usage Observers will consider the source of usage for an activity. Usage Source
+ * is decided at boot and will not change until next boot.
+ * @see #USAGE_SOURCE_TASK_ROOT_ACTIVITY
+ * @see #USAGE_SOURCE_CURRENT_ACTIVITY
+ *
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ public @UsageSource int getUsageSource() {
+ try {
+ return mService.getUsageSource();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force the Usage Source be reread from global settings.
+ * @hide
+ */
+ @TestApi
+ public void forceUsageSourceSettingRead() {
+ try {
+ mService.forceUsageSourceSettingRead();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String reasonToString(int standbyReason) {
StringBuilder sb = new StringBuilder();
@@ -845,6 +901,22 @@
return sb.toString();
}
+ /** @hide */
+ public static String usageSourceToString(int usageSource) {
+ switch (usageSource) {
+ case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+ return "TASK_ROOT_ACTIVITY";
+ case USAGE_SOURCE_CURRENT_ACTIVITY:
+ return "CURRENT_ACTIVITY";
+ default:
+ StringBuilder sb = new StringBuilder();
+ sb.append("UNKNOWN(");
+ sb.append(usageSource);
+ sb.append(")");
+ return sb.toString();
+ }
+ }
+
/**
* {@hide}
* Temporarily whitelist the specified app for a short duration. This is to allow an app
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index cc3ab00..d2d0cf9 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -40,9 +40,11 @@
* {@link UsageEvents}
* @param instanceId For activity, hashCode of ActivityRecord's appToken.
* For non-activity, it is not used.
+ * @param taskRoot For activity, the name of the package at the root of the task
+ * For non-activity, it is not used.
*/
public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
- int instanceId);
+ int instanceId, ComponentName taskRoot);
/**
* Reports an event to the UsageStatsManager.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 516f49c..39f750f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11495,6 +11495,20 @@
public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
/**
+ * App time limit usage source setting.
+ * This controls which app in a task will be considered the source of usage when
+ * calculating app usage time limits.
+ *
+ * 1 -> task root app
+ * 2 -> current app
+ * Any other value defaults to task root app.
+ *
+ * Need to reboot the device for this setting to take effect.
+ * @hide
+ */
+ public static final String APP_TIME_LIMIT_USAGE_SOURCE = "app_time_limit_usage_source";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 528c1a4..050ec7a 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -88,6 +88,11 @@
// If class field is an Activity, instance_id is a unique id of the
// Activity object.
optional int32 instance_id = 14;
+ // task_root_package_index contains the index + 1 of the task root package name in the string
+ // pool
+ optional int32 task_root_package_index = 15;
+ // task_root_class_index contains the index + 1 of the task root class name in the string pool
+ optional int32 task_root_class_index = 16;
}
// The following fields contain supplemental data used to build IntervalStats, such as a string
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 87ad3d1..3523c54 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -120,6 +120,7 @@
Settings.Global.APP_IDLE_CONSTANTS,
Settings.Global.APP_OPS_CONSTANTS,
Settings.Global.APP_STANDBY_ENABLED,
+ Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
Settings.Global.ASSISTED_GPS_ENABLED,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 089847d..2f77ed6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2864,16 +2864,18 @@
* @param userId
* @param event
* @param appToken ActivityRecord's appToken.
+ * @param taskRoot TaskRecord's root
*/
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken) {
+ IBinder appToken, ComponentName taskRoot) {
if (DEBUG_SWITCH) {
Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
+ activity + " hash=" + appToken.hashCode() + " event=" + event);
}
synchronized (this) {
if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
+ mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(),
+ taskRoot);
}
}
}
@@ -2911,7 +2913,7 @@
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(service, userId,
started ? UsageEvents.Event.FOREGROUND_SERVICE_START
- : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
+ : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0, null);
}
}
}
@@ -17521,10 +17523,10 @@
@Override
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken) {
+ IBinder appToken, ComponentName taskRoot) {
synchronized (ActivityManagerService.this) {
ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
- appToken);
+ appToken, taskRoot);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 61c4863..2affa97 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5313,9 +5313,18 @@
}
void updateActivityUsageStats(ActivityRecord activity, int event) {
+ ComponentName taskRoot = null;
+ final TaskRecord task = activity.getTaskRecord();
+ if (task != null) {
+ final ActivityRecord rootActivity = task.getRootActivity();
+ if (rootActivity != null) {
+ taskRoot = rootActivity.mActivityComponent;
+ }
+ }
+
final Message m = PooledLambda.obtainMessage(
ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
- activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+ activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot);
mH.sendMessage(m);
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 860656b..8d9b3cf 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -45,6 +45,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UsageStatsDatabaseTest {
+
+ private static final int MAX_TESTED_VERSION = 4;
protected Context mContext;
private UsageStatsDatabase mUsageStatsDatabase;
private File mTestDir;
@@ -131,8 +133,8 @@
for (int i = 0; i < numberOfEvents; i++) {
Event event = new Event();
- final int packageInt = ((i / 3) % 7);
- event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps"
+ final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps"
+ event.mPackage = "fake.package.name" + packageInt;
if (packageInt == 3) {
// Third app is an instant app
event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
@@ -144,6 +146,13 @@
event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
event.mInstanceId = instanceId;
+
+ final int rootPackageInt = (i % 5); // 5 "apps" start each task
+ event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
+
+ final int rootClassInt = i % 6;
+ event.mTaskRootClass = ".fake.class.name" + rootClassInt;
+
switch (event.mEventType) {
case Event.CONFIGURATION_CHANGE:
//empty config,
@@ -163,7 +172,7 @@
break;
}
- mIntervalStats.events.insert(event);
+ mIntervalStats.addEvent(event);
mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
event.mInstanceId);
@@ -234,31 +243,40 @@
assertEquals(us1.mChooserCounts, us2.mChooserCounts);
}
- void compareUsageEvent(Event e1, Event e2, int debugId) {
- assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
- assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
- assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
- assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
- switch (e1.mEventType) {
- case Event.CONFIGURATION_CHANGE:
- assertEquals(e1.mConfiguration, e2.mConfiguration,
- "Usage event " + debugId + e2.mConfiguration.toString());
- break;
- case Event.SHORTCUT_INVOCATION:
- assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
- break;
- case Event.STANDBY_BUCKET_CHANGED:
- assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId);
- break;
- case Event.NOTIFICATION_INTERRUPTION:
- assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
- "Usage event " + debugId);
- break;
+ void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
+ switch (minVersion) {
+ case 4: // test fields added in version 4
+ assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
+ assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
+ assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId);
+ // fallthrough
+ default:
+ assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
+ assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
+ assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
+ assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
+ switch (e1.mEventType) {
+ case Event.CONFIGURATION_CHANGE:
+ assertEquals(e1.mConfiguration, e2.mConfiguration,
+ "Usage event " + debugId + e2.mConfiguration.toString());
+ break;
+ case Event.SHORTCUT_INVOCATION:
+ assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
+ break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ assertEquals(e1.mBucketAndReason, e2.mBucketAndReason,
+ "Usage event " + debugId);
+ break;
+ case Event.NOTIFICATION_INTERRUPTION:
+ assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
+ "Usage event " + debugId);
+ break;
+ }
+ assertEquals(e1.mFlags, e2.mFlags);
}
- assertEquals(e1.mFlags, e2.mFlags);
}
- void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) {
+ void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) {
assertEquals(stats1.majorVersion, stats2.majorVersion);
assertEquals(stats1.minorVersion, stats2.minorVersion);
assertEquals(stats1.beginTime, stats2.beginTime);
@@ -311,7 +329,7 @@
} else {
assertEquals(stats1.events.size(), stats2.events.size());
for (int i = 0; i < stats1.events.size(); i++) {
- compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
+ compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion);
}
}
}
@@ -326,7 +344,7 @@
mIntervalStatsVerifier);
assertEquals(1, stats.size());
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION);
}
/**
@@ -359,8 +377,10 @@
mIntervalStatsVerifier);
assertEquals(1, stats.size());
+
+ final int minVersion = oldVersion < newVersion ? oldVersion : newVersion;
// The written and read IntervalStats should match
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), minVersion);
}
/**
@@ -401,7 +421,7 @@
if (mIntervalStats.events != null) mIntervalStats.events.clear();
// The written and read IntervalStats should match
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), version);
}
/**
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 9a5bd13..f1ddfe4 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -218,6 +218,14 @@
case (int) IntervalStatsProto.Event.INSTANCE_ID:
event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
break;
+ case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
+ event.mTaskRootPackage = getCachedStringRef(stringPool.get(
+ parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
+ break;
+ case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
+ event.mTaskRootClass = getCachedStringRef(stringPool.get(
+ parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
+ break;
case ProtoInputStream.NO_MORE_FIELDS:
// Handle default values for certain events types
switch (event.mEventType) {
@@ -332,6 +340,12 @@
if (event.mClass != null) {
event.mClass = getCachedStringRef(event.mClass);
}
+ if (event.mTaskRootPackage != null) {
+ event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
+ }
+ if (event.mTaskRootClass != null) {
+ event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
+ }
if (event.mEventType == NOTIFICATION_INTERRUPTION) {
event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index d706537..11d49eb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -442,6 +442,28 @@
proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
+ if (event.mTaskRootPackage != null) {
+ final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage);
+ if (taskRootPackageIndex >= 0) {
+ proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
+ taskRootPackageIndex + 1);
+ } else {
+ // Task root package not in Stringpool for some reason.
+ Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
+ + ") not found in IntervalStats string cache");
+ }
+ }
+ if (event.mTaskRootClass != null) {
+ final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass);
+ if (taskRootClassIndex >= 0) {
+ proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
+ taskRootClassIndex + 1);
+ } else {
+ // Task root class not in Stringpool for some reason.
+ Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
+ + ") not found in IntervalStats string cache");
+ }
+ }
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 76a3aa8..6ad698b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -22,6 +22,8 @@
import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
import android.Manifest;
import android.app.ActivityManager;
@@ -39,6 +41,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.UsageSource;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -65,6 +68,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -132,6 +136,7 @@
private File mUsageStatsDir;
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
+ int mUsageSource;
/** Manages the standby state of apps. */
AppStandbyController mAppStandby;
@@ -258,6 +263,7 @@
} else {
Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
}
+ readUsageSourceSetting();
}
}
@@ -268,6 +274,13 @@
return mDpmInternal;
}
+ private void readUsageSourceSetting() {
+ synchronized (mLock) {
+ mUsageSource = Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ }
+ }
+
private class UserActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -459,12 +472,28 @@
service.reportEvent(event);
mAppStandby.reportEvent(event, elapsedRealtime, userId);
+
+ String packageName;
+
+ switch(mUsageSource) {
+ case USAGE_SOURCE_CURRENT_ACTIVITY:
+ packageName = event.getPackageName();
+ break;
+ case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+ default:
+ packageName = event.getTaskRootPackageName();
+ if (packageName == null) {
+ packageName = event.getPackageName();
+ }
+ break;
+ }
+
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
synchronized (mVisibleActivities) {
mVisibleActivities.put(event.mInstanceId, event.getClassName());
try {
- mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+ mAppTimeLimit.noteUsageStart(packageName, userId);
} catch (IllegalArgumentException iae) {
Slog.e(TAG, "Failed to note usage start", iae);
}
@@ -496,7 +525,7 @@
synchronized (mVisibleActivities) {
if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) {
try {
- mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+ mAppTimeLimit.noteUsageStop(packageName, userId);
} catch (IllegalArgumentException iae) {
Slog.w(TAG, "Failed to note usage stop", iae);
}
@@ -638,7 +667,7 @@
* Called by the Binder stub.
*/
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
- String packageName) {
+ String packageName, boolean includeTaskRoot) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
@@ -647,7 +676,7 @@
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- return service.queryEventsForPackage(beginTime, endTime, packageName);
+ return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
}
}
@@ -738,6 +767,10 @@
mAppStandby.dumpState(args, pw);
}
+ idpw.println();
+ idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
+ idpw.println();
+
mAppTimeLimit.dump(null, pw);
}
}
@@ -808,7 +841,7 @@
return mode == AppOpsManager.MODE_ALLOWED;
}
- private boolean hasObserverPermission(String callingPackage) {
+ private boolean hasObserverPermission() {
final int callingUid = Binder.getCallingUid();
DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (callingUid == Process.SYSTEM_UID
@@ -939,10 +972,12 @@
final int callingUserId = UserHandle.getUserId(callingUid);
checkCallerIsSameApp(callingPackage);
+ final boolean includeTaskRoot = hasPermission(callingPackage);
+
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime,
- endTime, callingPackage);
+ endTime, callingPackage, includeTaskRoot);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -989,7 +1024,7 @@
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(userId, beginTime,
- endTime, pkg);
+ endTime, pkg, true);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1229,7 +1264,7 @@
public void registerAppUsageObserver(int observerId,
String[] packages, long timeLimitMs, PendingIntent
callbackIntent, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1252,7 +1287,7 @@
@Override
public void unregisterAppUsageObserver(int observerId, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1271,7 +1306,7 @@
long timeLimitMs, long sessionThresholdTimeMs,
PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1295,7 +1330,7 @@
@Override
public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1373,6 +1408,21 @@
Binder.restoreCallingIdentity(binderToken);
}
}
+
+ @Override
+ public @UsageSource int getUsageSource() {
+ if (!hasObserverPermission()) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+ synchronized (mLock) {
+ return mUsageSource;
+ }
+ }
+
+ @Override
+ public void forceUsageSourceSettingRead() {
+ readUsageSourceSetting();
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
@@ -1406,7 +1456,7 @@
@Override
public void reportEvent(ComponentName component, int userId, int eventType,
- int instanceId) {
+ int instanceId, ComponentName taskRoot) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
@@ -1416,6 +1466,13 @@
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
event.mInstanceId = instanceId;
+ if (taskRoot == null) {
+ event.mTaskRootPackage = null;
+ event.mTaskRootClass = null;
+ } else {
+ event.mTaskRootPackage = taskRoot.getPackageName();
+ event.mTaskRootClass = taskRoot.getClassName();
+ }
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 2d1098c7..d52d32f 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -401,7 +401,7 @@
}
UsageEvents queryEvents(final long beginTime, final long endTime,
- boolean obfuscateInstantApps) {
+ boolean obfuscateInstantApps) {
final ArraySet<String> names = new ArraySet<>();
List<Event> results = queryStats(INTERVAL_DAILY,
beginTime, endTime, new StatCombiner<Event>() {
@@ -425,6 +425,12 @@
if (event.mClass != null) {
names.add(event.mClass);
}
+ if (event.mTaskRootPackage != null) {
+ names.add(event.mTaskRootPackage);
+ }
+ if (event.mTaskRootClass != null) {
+ names.add(event.mTaskRootClass);
+ }
accumulatedResult.add(event);
}
}
@@ -436,11 +442,11 @@
String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
- return new UsageEvents(results, table);
+ return new UsageEvents(results, table, true);
}
UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
- final String packageName) {
+ final String packageName, boolean includeTaskRoot) {
final ArraySet<String> names = new ArraySet<>();
names.add(packageName);
final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -459,6 +465,12 @@
if (event.mClass != null) {
names.add(event.mClass);
}
+ if (includeTaskRoot && event.mTaskRootPackage != null) {
+ names.add(event.mTaskRootPackage);
+ }
+ if (includeTaskRoot && event.mTaskRootClass != null) {
+ names.add(event.mTaskRootClass);
+ }
accumulatedResult.add(event);
}
});
@@ -469,7 +481,7 @@
final String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
- return new UsageEvents(results, table);
+ return new UsageEvents(results, table, includeTaskRoot);
}
void persistActiveStats() {
@@ -684,6 +696,14 @@
pw.printPair("instanceId", event.getInstanceId());
}
+ if (event.getTaskRootPackageName() != null) {
+ pw.printPair("taskRootPackage", event.getTaskRootPackageName());
+ }
+
+ if (event.getTaskRootClassName() != null) {
+ pw.printPair("taskRootClass", event.getTaskRootClassName());
+ }
+
if (event.mNotificationChannelId != null) {
pw.printPair("channelId", event.mNotificationChannelId);
}