Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8e91f3d authored by Xin Guan's avatar Xin Guan Committed by Android (Google) Code Review
Browse files

Merge "UsageStats: Support event query with package filter" into main

parents b0b561e7 dbb2e270
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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 {
+62 −0
Original line number Diff line number Diff line
@@ -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;
        }
    }
}
+16 −0
Original line number Diff line number Diff line
@@ -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());
    }
}
+14 −10
Original line number Diff line number Diff line
@@ -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
+7 −1
Original line number Diff line number Diff line
@@ -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);
                            }