Loading core/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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 { core/java/android/app/usage/UsageEventsQuery.java +62 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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) { Loading @@ -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); } /** Loading Loading @@ -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; Loading @@ -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 Loading @@ -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. Loading Loading @@ -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; } } } core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java +16 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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()); } } services/usage/java/com/android/server/usage/UsageStatsService.java +14 −10 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); } } Loading Loading @@ -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( Loading @@ -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); } Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading services/usage/java/com/android/server/usage/UserUsageStatsService.java +7 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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); } Loading Loading
core/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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 {
core/java/android/app/usage/UsageEventsQuery.java +62 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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) { Loading @@ -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); } /** Loading Loading @@ -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; Loading @@ -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 Loading @@ -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. Loading Loading @@ -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; } } }
core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java +16 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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()); } }
services/usage/java/com/android/server/usage/UsageStatsService.java +14 −10 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); } } Loading Loading @@ -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( Loading @@ -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); } Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading
services/usage/java/com/android/server/usage/UserUsageStatsService.java +7 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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); } Loading