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/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,