summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Xin Guan <guanxin@google.com> 2024-01-19 19:06:15 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-19 19:06:15 +0000
commitb8e91f3d2a284be6640d1423bfd1ad19ebfe551b (patch)
treea64537431ff3646108d7bb81fa71347bf1b880fc
parentb0b561e7644a5830d747ff0882e7dc5c89fe7b7a (diff)
parentdbb2e270372914019dd5b11335b55f8a131a87f8 (diff)
Merge "UsageStats: Support event query with package filter" into main
-rw-r--r--core/api/current.txt2
-rw-r--r--core/java/android/app/usage/UsageEventsQuery.java62
-rw-r--r--core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java16
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java24
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java8
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);
}