diff options
| author | 2024-01-19 19:06:15 +0000 | |
|---|---|---|
| committer | 2024-01-19 19:06:15 +0000 | |
| commit | b8e91f3d2a284be6640d1423bfd1ad19ebfe551b (patch) | |
| tree | a64537431ff3646108d7bb81fa71347bf1b880fc | |
| parent | b0b561e7644a5830d747ff0882e7dc5c89fe7b7a (diff) | |
| parent | dbb2e270372914019dd5b11335b55f8a131a87f8 (diff) | |
Merge "UsageStats: Support event query with package filter" into main
5 files changed, 101 insertions, 11 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 865afc060736..4e61b0aaac62 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9380,6 +9380,7 @@ package android.app.usage { method public long getBeginTimeMillis(); method public long getEndTimeMillis(); method @NonNull public int[] getEventTypes(); + method @NonNull public java.util.Set<java.lang.String> getPackageNames(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.UsageEventsQuery> CREATOR; } @@ -9388,6 +9389,7 @@ package android.app.usage { ctor public UsageEventsQuery.Builder(long, long); method @NonNull public android.app.usage.UsageEventsQuery build(); method @NonNull public android.app.usage.UsageEventsQuery.Builder setEventTypes(@NonNull int...); + method @NonNull public android.app.usage.UsageEventsQuery.Builder setPackageNames(@NonNull java.lang.String...); } public final class UsageStats implements android.os.Parcelable { diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java index df6324f744a8..c0f13ca557e2 100644 --- a/core/java/android/app/usage/UsageEventsQuery.java +++ b/core/java/android/app/usage/UsageEventsQuery.java @@ -24,11 +24,15 @@ import android.app.usage.UsageEvents.Event; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.util.ArrayUtils; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * An Object-Oriented representation for a {@link UsageEvents} query. @@ -40,12 +44,14 @@ public final class UsageEventsQuery implements Parcelable { private final @CurrentTimeMillisLong long mEndTimeMillis; private final @Event.EventType int[] mEventTypes; private final @UserIdInt int mUserId; + private final String[] mPackageNames; private UsageEventsQuery(@NonNull Builder builder) { mBeginTimeMillis = builder.mBeginTimeMillis; mEndTimeMillis = builder.mEndTimeMillis; mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes); mUserId = builder.mUserId; + mPackageNames = builder.mPackageNames.toArray(new String[builder.mPackageNames.size()]); } private UsageEventsQuery(Parcel in) { @@ -55,6 +61,9 @@ public final class UsageEventsQuery implements Parcelable { mEventTypes = new int[eventTypesLength]; in.readIntArray(mEventTypes); mUserId = in.readInt(); + int packageNamesLength = in.readInt(); + mPackageNames = new String[packageNamesLength]; + in.readStringArray(mPackageNames); } /** @@ -92,6 +101,28 @@ public final class UsageEventsQuery implements Parcelable { return mUserId; } + /** + * Retrieves a {@code Set} of package names for the query. + * <p>Note that an empty set indicates querying usage events for all packages, and + * it may cause additional system overhead when calling + * {@link UsageStatsManager#queryEvents(UsageEventsQuery)}. Apps are encouraged to + * provide a list of package names via {@link Builder#setPackageNames(String...)}</p> + * + * @return a {@code Set} contains the package names that was previously set through + * {@link Builder#setPackageNames(String...)} or an empty set if no value has been set. + */ + public @NonNull Set<String> getPackageNames() { + if (ArrayUtils.isEmpty(mPackageNames)) { + return Collections.emptySet(); + } + + final HashSet<String> pkgNameSet = new HashSet<>(); + for (String pkgName: mPackageNames) { + pkgNameSet.add(pkgName); + } + return pkgNameSet; + } + @Override public int describeContents() { return 0; @@ -104,6 +135,8 @@ public final class UsageEventsQuery implements Parcelable { dest.writeInt(mEventTypes.length); dest.writeIntArray(mEventTypes); dest.writeInt(mUserId); + dest.writeInt(mPackageNames.length); + dest.writeStringArray(mPackageNames); } @NonNull @@ -128,6 +161,7 @@ public final class UsageEventsQuery implements Parcelable { private final @CurrentTimeMillisLong long mEndTimeMillis; private final ArraySet<Integer> mEventTypes = new ArraySet<>(); private @UserIdInt int mUserId = UserHandle.USER_NULL; + private final ArraySet<String> mPackageNames = new ArraySet<>(); /** * Constructor that specifies the period for which to return events. @@ -194,5 +228,33 @@ public final class UsageEventsQuery implements Parcelable { mUserId = userId; return this; } + + /** + * Sets the list of package names to be included in the query. + * + * <p>Note: </p> An empty {@code Set} will be returned by + * {@link UsageEventsQuery#getPackageNames()} without calling this method, which indicates + * querying usage events for all packages. Apps are encouraged to provide a list of package + * names. Only the matching names supplied will be used to query. + * + * @param pkgNames the array of the package names, each package name should be a non-empty + * string, {@code null} or empty string("") is omitted. + * @see UsageEventsQuery#getPackageNames() + * @see UsageStatsManager#queryEvents(UsageEventsQuery) + * @throws NullPointerException if {@code pkgNames} is {@code null} or empty. + */ + public @NonNull Builder setPackageNames(@NonNull String... pkgNames) { + if (pkgNames == null || pkgNames.length == 0) { + throw new NullPointerException("pkgNames is null or empty"); + } + mPackageNames.clear(); + for (int i = 0; i < pkgNames.length; i++) { + if (!TextUtils.isEmpty(pkgNames[i])) { + mPackageNames.add(pkgNames[i]); + } + } + + return this; + } } } diff --git a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java index 4565978434ee..55168458d49a 100644 --- a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java +++ b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Random; +import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest @@ -142,4 +143,19 @@ public class UsageEventsQueryTest { fail("Valid event type: " + eventType); } } + + @Test + @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API) + public void testQueryEventPackages() { + UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000); + + // Test with duplicate package names and empty package name + final String pkgName = "test.package.name"; + UsageEventsQuery query = queryBuilder.setPackageNames(pkgName, pkgName, "", pkgName) + .build(); + Set<String> pkgNameSet = query.getPackageNames(); + // Duplicated package names and empty package name will be ignored. + assertEquals(pkgNameSet.size(), 1); + assertEquals(pkgName, pkgNameSet.iterator().next()); + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 9fc64feb4b25..a58cf5fcd620 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1526,14 +1526,15 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) { - return queryEventsWithTypes(userId, beginTime, endTime, flags, EmptyArray.INT); + return queryEventsWithQueryFilters(userId, beginTime, endTime, flags, + /* eventTypeFilter= */ EmptyArray.INT, /* pkgNameFilter= */ null); } /** * Called by the Binder stub. */ - UsageEvents queryEventsWithTypes(int userId, long beginTime, long endTime, int flags, - int[] eventTypeFilter) { + UsageEvents queryEventsWithQueryFilters(int userId, long beginTime, long endTime, int flags, + int[] eventTypeFilter, ArraySet<String> pkgNameFilter) { synchronized (mLock) { if (!mUserUnlockedStates.contains(userId)) { Slog.w(TAG, "Failed to query events for locked user " + userId); @@ -1544,7 +1545,7 @@ public class UsageStatsService extends SystemService implements if (service == null) { return null; // user was stopped or removed } - return service.queryEvents(beginTime, endTime, flags, eventTypeFilter); + return service.queryEvents(beginTime, endTime, flags, eventTypeFilter, pkgNameFilter); } } @@ -2276,7 +2277,7 @@ public class UsageStatsService extends SystemService implements } private UsageEvents queryEventsHelper(int userId, long beginTime, long endTime, - String callingPackage, int[] eventTypeFilter) { + String callingPackage, int[] eventTypeFilter, ArraySet<String> pkgNameFilter) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller( @@ -2295,8 +2296,8 @@ public class UsageStatsService extends SystemService implements if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS; if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; - return UsageStatsService.this.queryEventsWithTypes(userId, beginTime, endTime, - flags, eventTypeFilter); + return UsageStatsService.this.queryEventsWithQueryFilters(userId, + beginTime, endTime, flags, eventTypeFilter, pkgNameFilter); } finally { Binder.restoreCallingIdentity(token); } @@ -2414,7 +2415,8 @@ public class UsageStatsService extends SystemService implements } return queryEventsHelper(UserHandle.getCallingUserId(), beginTime, endTime, - callingPackage, /* eventTypeFilter= */ EmptyArray.INT); + callingPackage, /* eventTypeFilter= */ EmptyArray.INT, + /* pkgNameFilter= */ null); } @Override @@ -2440,7 +2442,8 @@ public class UsageStatsService extends SystemService implements } return queryEventsHelper(userId, query.getBeginTimeMillis(), - query.getEndTimeMillis(), callingPackage, query.getEventTypes()); + query.getEndTimeMillis(), callingPackage, query.getEventTypes(), + /* pkgNameFilter= */ new ArraySet<>(query.getPackageNames())); } @Override @@ -2476,7 +2479,8 @@ public class UsageStatsService extends SystemService implements } return queryEventsHelper(userId, beginTime, endTime, callingPackage, - /* eventTypeFilter= */ EmptyArray.INT); + /* eventTypeFilter= */ EmptyArray.INT, + /* pkgNameFilter= */ null); } @Override diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 3bc77526a967..96f45fa952bf 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -536,13 +536,14 @@ class UserUsageStatsService { } UsageEvents queryEvents(final long beginTime, final long endTime, int flags, - int[] eventTypeFilter) { + int[] eventTypeFilter, ArraySet<String> pkgNameFilter) { if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { return null; } // Ensure valid event type filter. final boolean isQueryForAllEvents = ArrayUtils.isEmpty(eventTypeFilter); + final boolean isQueryForAllPackages = pkgNameFilter == null || pkgNameFilter.isEmpty(); final boolean[] queryEventFilter = new boolean[Event.MAX_EVENT_TYPE + 1]; if (!isQueryForAllEvents) { for (int eventType : eventTypeFilter) { @@ -589,6 +590,11 @@ class UserUsageStatsService { if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) { event = event.getObfuscatedIfInstantApp(); } + + if (!isQueryForAllPackages && !pkgNameFilter.contains(event.mPackage)) { + continue; + } + if (event.mPackage != null) { names.add(event.mPackage); } |