diff options
| author | 2019-01-07 13:59:10 -0800 | |
|---|---|---|
| committer | 2019-01-22 22:19:21 -0800 | |
| commit | 0b4ab1f1714ba236f03c15981efe7f3a803495cf (patch) | |
| tree | b6610486fa3000d56ad17b4be36406ff0fa6a92b | |
| parent | 9e10c879141b269ed8f03644e01a734ca5c31794 (diff) | |
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
17 files changed, 385 insertions, 54 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 3a260083c24a..01e3c98508db 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1095,6 +1095,8 @@ package android.app.usage { 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 @@ package android.app.usage { 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 @@ package android.app.usage { 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 d08983103ce8..59e49a5fcfa1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -352,6 +352,10 @@ package android.app.usage { 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 5cac0489e8df..86e658dfdcb1 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -219,9 +219,11 @@ public abstract class ActivityManagerInternal { * @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 bbae7d3463ae..d2934b9f5a21 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -59,4 +59,6 @@ interface IUsageStatsManager { 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 2c5fe046faad..451f44b93c32 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 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String mClass; - /** * {@hide} */ @@ -295,6 +295,16 @@ public final class UsageEvents implements Parcelable { /** * {@hide} */ + public String mTaskRootPackage; + + /** + * {@hide} + */ + public String mTaskRootClass; + + /** + * {@hide} + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long mTimeStamp; @@ -373,6 +383,8 @@ public final class UsageEvents implements Parcelable { 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 @@ public final class UsageEvents implements Parcelable { } /** + * 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 @@ public final class UsageEvents implements Parcelable { @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 @@ public final class UsageEvents implements Parcelable { mParcel.setDataSize(mParcel.dataPosition()); mParcel.setDataPosition(positionInParcel); } + mIncludeTaskRoots = true; } /** @@ -560,16 +598,27 @@ public final class UsageEvents implements Parcelable { */ 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, boolean includeTaskRoots) { mStringPool = stringPool; mEventCount = events.size(); mEventsToWrite = events; + mIncludeTaskRoots = includeTaskRoots; } /** @@ -645,9 +694,25 @@ public final class UsageEvents implements Parcelable { } 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 @@ public final class UsageEvents implements Parcelable { 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 605deac80994..d2de8872c1bd 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; 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 @@ public final class UsageStatsManager { @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 @@ public final class UsageStatsManager { } } + /** + * 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 @@ public final class UsageStatsManager { 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 cc3ab0025864..d2d0cf9ca90b 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -40,9 +40,11 @@ public abstract class UsageStatsManagerInternal { * {@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 516f49ccc910..39f750f29e07 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11495,6 +11495,20 @@ public final class Settings { 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 528c1a4134f1..050ec7a0a95f 100644 --- a/core/proto/android/server/usagestatsservice.proto +++ b/core/proto/android/server/usagestatsservice.proto @@ -88,6 +88,11 @@ message IntervalStatsProto { // 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 87ad3d1067b2..3523c541af01 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -120,6 +120,7 @@ public class SettingsBackupTest { 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 089847d1ff7f..2f77ed64c670 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 @@ public class ActivityManagerService extends IActivityManager.Stub * @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 61c4863c0bc9..2affa974e999 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } 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 860656bf47f4..8d9b3cfcff35 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 @@ import java.util.Locale; @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 @@ public class UsageStatsDatabaseTest { 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 @@ public class UsageStatsDatabaseTest { 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 @@ public class UsageStatsDatabaseTest { break; } - mIntervalStats.events.insert(event); + mIntervalStats.addEvent(event); mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType, event.mInstanceId); @@ -234,31 +243,40 @@ public class UsageStatsDatabaseTest { 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 @@ public class UsageStatsDatabaseTest { } 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 @@ public class UsageStatsDatabaseTest { mIntervalStatsVerifier); assertEquals(1, stats.size()); - compareIntervalStats(mIntervalStats, stats.get(0)); + compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION); } /** @@ -359,8 +377,10 @@ public class UsageStatsDatabaseTest { 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 @@ public class UsageStatsDatabaseTest { 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 9a5bd1379717..f1ddfe4cd0d5 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 @@ public class IntervalStats { 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 @@ public class IntervalStats { 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 d70653781eff..11d49eb40bc0 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 @@ final class UsageStatsProto { 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 76a3aa81530e..6ad698b39763 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.DEVICE_SHUTDOWN; 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.UsageEvents.Event; 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.SystemClock; 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 @@ public class UsageStatsService extends SystemService implements private File mUsageStatsDir; long mRealTimeSnapshot; long mSystemTimeSnapshot; + int mUsageSource; /** Manages the standby state of apps. */ AppStandbyController mAppStandby; @@ -258,6 +263,7 @@ public class UsageStatsService extends SystemService implements } else { Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE); } + readUsageSourceSetting(); } } @@ -268,6 +274,13 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements * 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 @@ public class UsageStatsService extends SystemService implements final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId, timeNow); - return service.queryEventsForPackage(beginTime, endTime, packageName); + return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot); } } @@ -738,6 +767,10 @@ public class UsageStatsService extends SystemService implements mAppStandby.dumpState(args, pw); } + idpw.println(); + idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource)); + idpw.println(); + mAppTimeLimit.dump(null, pw); } } @@ -808,7 +841,7 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements 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 class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements @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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements @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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements @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 @@ public class UsageStatsService extends SystemService implements 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 2d1098c7cf5c..d52d32faa7d1 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 @@ class UserUsageStatsService { } 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 @@ class UserUsageStatsService { 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 @@ class UserUsageStatsService { 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 @@ class UserUsageStatsService { 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 @@ class UserUsageStatsService { 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 @@ class UserUsageStatsService { 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); } |