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

Commit bc813eb2 authored by Amith Yamasani's avatar Amith Yamasani
Browse files

Provide app launch count in UsageStats

This counts the number of times the app was launched from outside
the app and ignores intra-app activity transitions.

Introduce a new permission for registering to observe app usage.

Fixes a bug where Settings couldn't force the app into another
bucket if it was recently launched.

Bug: 74335821
Fixes: 76100712
Test: Manual test using Settings
Test: UsageStatsTest to verify permission change
Change-Id: Ibd343c1cfa37089a3ac6fc30ba3194e21a9be499
parent 9531b70c
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -118,6 +118,7 @@ package android {
    field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
    field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP";
    field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
    field public static final java.lang.String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
    field public static final java.lang.String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
    field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
    field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
@@ -726,6 +727,10 @@ package android.app.usage {
    field public static final int NOTIFICATION_SEEN = 10; // 0xa
  }

  public final class UsageStats implements android.os.Parcelable {
    method public int getAppLaunchCount();
  }

  public final class UsageStatsManager {
    method public int getAppStandbyBucket(java.lang.String);
    method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
+20 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app.usage;

import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -58,6 +59,11 @@ public final class UsageStats implements Parcelable {
     */
    public int mLaunchCount;

    /**
     * {@hide}
     */
    public int mAppLaunchCount;

    /**
     * {@hide}
     */
@@ -81,6 +87,7 @@ public final class UsageStats implements Parcelable {
        mLastTimeUsed = stats.mLastTimeUsed;
        mTotalTimeInForeground = stats.mTotalTimeInForeground;
        mLaunchCount = stats.mLaunchCount;
        mAppLaunchCount = stats.mAppLaunchCount;
        mLastEvent = stats.mLastEvent;
        mChooserCounts = stats.mChooserCounts;
    }
@@ -136,6 +143,16 @@ public final class UsageStats implements Parcelable {
        return mTotalTimeInForeground;
    }

    /**
     * Returns the number of times the app was launched as an activity from outside of the app.
     * Excludes intra-app activity transitions.
     * @hide
     */
    @SystemApi
    public int getAppLaunchCount() {
        return mAppLaunchCount;
    }

    /**
     * Add the statistics from the right {@link UsageStats} to the left. The package name for
     * both {@link UsageStats} objects must be the same.
@@ -161,6 +178,7 @@ public final class UsageStats implements Parcelable {
        mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
        mTotalTimeInForeground += right.mTotalTimeInForeground;
        mLaunchCount += right.mLaunchCount;
        mAppLaunchCount += right.mAppLaunchCount;
        if (mChooserCounts == null) {
            mChooserCounts = right.mChooserCounts;
        } else if (right.mChooserCounts != null) {
@@ -196,6 +214,7 @@ public final class UsageStats implements Parcelable {
        dest.writeLong(mLastTimeUsed);
        dest.writeLong(mTotalTimeInForeground);
        dest.writeInt(mLaunchCount);
        dest.writeInt(mAppLaunchCount);
        dest.writeInt(mLastEvent);
        Bundle allCounts = new Bundle();
        if (mChooserCounts != null) {
@@ -224,6 +243,7 @@ public final class UsageStats implements Parcelable {
            stats.mLastTimeUsed = in.readLong();
            stats.mTotalTimeInForeground = in.readLong();
            stats.mLaunchCount = in.readInt();
            stats.mAppLaunchCount = in.readInt();
            stats.mLastEvent = in.readInt();
            Bundle allCounts = in.readBundle();
            if (allCounts != null) {
+18 −14
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app.usage;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -500,26 +501,28 @@ public final class UsageStatsManager {
    /**
     * @hide
     * Register an app usage limit observer that receives a callback on the provided intent when
     * the sum of usages of apps in the packages array exceeds the timeLimit specified. The
     * the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The
     * observer will automatically be unregistered when the time limit is reached and the intent
     * is delivered.
     * is delivered. Registering an {@code observerId} that was already registered will override
     * the previous one.
     * @param observerId A unique id associated with the group of apps to be monitored. There can
     *                  be multiple groups with common packages and different time limits.
     * @param packages The list of packages to observe for foreground activity time. Must include
     *                 at least one package.
     * @param packages The list of packages to observe for foreground activity time. Cannot be null
     *                 and must include at least one package.
     * @param timeLimit The total time the set of apps can be in the foreground before the
     *                  callbackIntent is delivered. Must be greater than 0.
     * @param timeUnit The unit for time specified in timeLimit.
     * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
     * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
     *                       exceeded by the group of apps. The delivered Intent will also contain
     *                       the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
     *                       {@link #EXTRA_TIME_USED}.
     * @throws SecurityException if the caller doesn't have the PACKAGE_USAGE_STATS permission.
     *                       {@link #EXTRA_TIME_USED}. Cannot be null.
     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or
     *                           is not the profile owner of this user.
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
    public void registerAppUsageObserver(int observerId, String[] packages, long timeLimit,
            TimeUnit timeUnit, PendingIntent callbackIntent) {
    @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
    public void registerAppUsageObserver(int observerId, @NonNull String[] packages, long timeLimit,
            @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
        try {
            mService.registerAppUsageObserver(observerId, packages, timeUnit.toMillis(timeLimit),
                    callbackIntent, mContext.getOpPackageName());
@@ -529,14 +532,15 @@ public final class UsageStatsManager {

    /**
     * @hide
     * Unregister the app usage observer specified by the observerId. This will only apply to any
     * observer registered by this application. Unregistering an observer that was already
     * Unregister the app usage observer specified by the {@code observerId}. This will only apply
     * to any observer registered by this application. Unregistering an observer that was already
     * unregistered or never registered will have no effect.
     * @param observerId The id of the observer that was previously registered.
     * @throws SecurityException if the caller doesn't have the PACKAGE_USAGE_STATS permission.
     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is
     *                           not the profile owner of this user.
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
    @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
    public void unregisterAppUsageObserver(int observerId) {
        try {
            mService.unregisterAppUsageObserver(observerId, mContext.getOpPackageName());
+5 −0
Original line number Diff line number Diff line
@@ -3274,6 +3274,11 @@
        android:protectionLevel="signature|privileged|development|appop" />
    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />

    <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
         for callbacks when apps reach a certain usage time limit, etc. -->
    <permission android:name="android.permission.OBSERVE_APP_USAGE"
        android:protectionLevel="signature|privileged" />

    <!-- @hide @SystemApi Allows an application to change the app idle state of an app.
         <p>Not for use by third-party applications. -->
    <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
+3 −18
Original line number Diff line number Diff line
@@ -71,16 +71,13 @@ public class AppTimeLimitController {
        /** The time when the current app came to the foreground */
        private long currentForegroundedTime;

        /** The last app that was in the background */
        private String lastBackgroundedPackage;

        /** Map from package name for quick lookup */
        private ArrayMap<String, ArrayList<TimeLimitGroup>> packageMap = new ArrayMap<>();

        /** Map of observerId to details of the time limit group */
        private SparseArray<TimeLimitGroup> groups = new SparseArray<>();

        UserData(@UserIdInt int userId) {
        private UserData(@UserIdInt int userId) {
            this.userId = userId;
        }
    }
@@ -114,7 +111,7 @@ public class AppTimeLimitController {
        int userId;
    }

    class MyHandler extends Handler {
    private class MyHandler extends Handler {

        static final int MSG_CHECK_TIMEOUT = 1;
        static final int MSG_INFORM_LISTENER = 2;
@@ -151,7 +148,7 @@ public class AppTimeLimitController {
    }

    /** Returns an existing UserData object for the given userId, or creates one */
    UserData getOrCreateUserDataLocked(int userId) {
    private UserData getOrCreateUserDataLocked(int userId) {
        UserData userData = mUsers.get(userId);
        if (userData == null) {
            userData = new UserData(userId);
@@ -258,12 +255,6 @@ public class AppTimeLimitController {
            user.currentForegroundedPackage = packageName;
            user.currentForegroundedTime = getUptimeMillis();

            // Check if the last package that was backgrounded is the same as this one
            if (!TextUtils.equals(packageName, user.lastBackgroundedPackage)) {
                // TODO: Move this logic up to usage stats to persist there.
                incTotalLaunchesLocked(user, packageName);
            }

            // Check if any of the groups need to watch for this package
            maybeWatchForPackageLocked(user, packageName, user.currentForegroundedTime);
        }
@@ -279,7 +270,6 @@ public class AppTimeLimitController {
    public void moveToBackground(String packageName, String className, int userId) {
        synchronized (mLock) {
            UserData user = getOrCreateUserDataLocked(userId);
            user.lastBackgroundedPackage = packageName;
            if (!TextUtils.equals(user.currentForegroundedPackage, packageName)) {
                Slog.w(TAG, "Eh? Last foregrounded package = " + user.currentForegroundedPackage
                        + " and now backgrounded = " + packageName);
@@ -433,10 +423,6 @@ public class AppTimeLimitController {
        }
    }

    private void incTotalLaunchesLocked(UserData user, String packageName) {
        // TODO: Inform UsageStatsService and aggregate the counter per app
    }

    void dump(PrintWriter pw) {
        synchronized (mLock) {
            pw.println("\n  App Time Limits");
@@ -457,7 +443,6 @@ public class AppTimeLimitController {
                pw.println();
                pw.print("    currentForegroundedPackage=");
                pw.println(user.currentForegroundedPackage);
                pw.print("    lastBackgroundedPackage="); pw.println(user.lastBackgroundedPackage);
            }
        }
    }
Loading