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

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

Introduce hidden logging API for restriction types and reasons

Hidden API to be called from various parts of the system to
log when app restriction levels change. The API should be called
before a state change occurs, to log/store the reason before
the actual change to restriction state occurs.

Initial usage of the API to track hibernation, power-allowlist
exemption and background restriction.

More logging of state changes to follow...

Bug: 333882527
Test: m -j statsd_testdrive && statsd_testdrive 863
      Manually play with settings UI to restrict app
      or force-stop

Change-Id: Ic58f111c2c23108d8c28691bf0d8b69017ce2bcd
parent 718d9741
Loading
Loading
Loading
Loading
+166 −7
Original line number Diff line number Diff line
@@ -1349,12 +1349,26 @@ public class ActivityManager {
    public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;

    /**
     * The most restricted level where the apps are considered "in-hibernation",
     * its package visibility to the rest of the system is limited.
     * The restricted level where the apps are in a force-stopped state.
     *
     * @hide
     */
    public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
    public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60;

    /**
     * The heavily background restricted level, where apps cannot start without an explicit
     * launch by the user.
     *
     * @hide
     */
    public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70;

    /**
     * A reserved restriction level that is not well-defined.
     *
     * @hide
     */
    public static final int RESTRICTION_LEVEL_CUSTOM = 90;

    /**
     * Not a valid restriction level, it defines the maximum numerical value of restriction level.
@@ -1371,12 +1385,116 @@ public class ActivityManager {
            RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
            RESTRICTION_LEVEL_RESTRICTED_BUCKET,
            RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
            RESTRICTION_LEVEL_HIBERNATION,
            RESTRICTION_LEVEL_FORCE_STOPPED,
            RESTRICTION_LEVEL_USER_LAUNCH_ONLY,
            RESTRICTION_LEVEL_CUSTOM,
            RESTRICTION_LEVEL_MAX,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RestrictionLevel{}

    /**
     * Maximum string length for sub reason for restriction.
     *
     * @hide
     */
    public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;

    /**
     * Restriction reason unknown - do not use directly.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_UNKNOWN = 0;

    /**
     * Restriction reason to be used when this is normal behavior for the state.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_DEFAULT = 1;

    /**
     * Restriction reason is some kind of timeout that moves the app to a more restricted state.
     * The threshold should specify how long the app was dormant, in milliseconds.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_DORMANT = 2;

    /**
     * Restriction reason to be used when removing a restriction due to direct or indirect usage
     * of the app, especially to undo any automatic restrictions.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_USAGE = 3;

    /**
     * Restriction reason to be used when the user chooses to manually restrict the app, through
     * UI or command line interface.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_USER = 4;

    /**
     * Restriction reason to be used when the user chooses to manually restrict the app on being
     * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
     * high battery drain or affecting system performance and the OS recommends that the user
     * restrict the app.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_USER_NUDGED = 5;

    /**
     * Restriction reason to be used when the OS automatically detects that the app is causing
     * system health issues such as performance degradation, battery drain, high memory usage, etc.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;

    /**
     * Restriction reason to be used when there is a server-side decision made to restrict an app
     * that is showing widespread problems on user devices, or violating policy in some way.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;

    /**
     * Restriction reason to be used when some other problem requires restricting the app.
     *
     * For use with noteAppRestrictionEnabled()
     * @hide
     */
    public static final int RESTRICTION_REASON_OTHER = 8;

    /** @hide */
    @IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
            RESTRICTION_REASON_UNKNOWN,
            RESTRICTION_REASON_DEFAULT,
            RESTRICTION_REASON_DORMANT,
            RESTRICTION_REASON_USAGE,
            RESTRICTION_REASON_USER,
            RESTRICTION_REASON_USER_NUDGED,
            RESTRICTION_REASON_SYSTEM_HEALTH,
            RESTRICTION_REASON_REMOTE_TRIGGER,
            RESTRICTION_REASON_OTHER
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RestrictionReason{}

    /** @hide */
    @android.ravenwood.annotation.RavenwoodKeep
    public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -1393,12 +1511,16 @@ public class ActivityManager {
                return "restricted_bucket";
            case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
                return "background_restricted";
            case RESTRICTION_LEVEL_HIBERNATION:
                return "hibernation";
            case RESTRICTION_LEVEL_FORCE_STOPPED:
                return "stopped";
            case RESTRICTION_LEVEL_USER_LAUNCH_ONLY:
                return "user_only";
            case RESTRICTION_LEVEL_CUSTOM:
                return "custom";
            case RESTRICTION_LEVEL_MAX:
                return "max";
            default:
                return "";
                return String.valueOf(level);
        }
    }

@@ -6126,6 +6248,43 @@ public class ActivityManager {
        return PowerExemptionManager.REASON_DENIED;
    }

    /**
     * Requests the system to log the reason for restricting/unrestricting an app. This API
     * should be called before applying any change to the restriction level.
     * <p>
     * The {@code enabled} value determines whether the state is being applied or removed.
     * Not all restrictions are actual restrictions. For example,
     * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
     * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
     * app is being put in a power-save allowlist.
     *
     * @param packageName the package name of the app
     * @param uid the uid of the app
     * @param restrictionLevel the restriction level specified in {@code RestrictionLevel}
     * @param enabled whether the state is being applied or removed
     * @param reason the reason for the restriction state change, from {@code RestrictionReason}
     * @param subReason a string sub reason limited to 16 characters that specifies additional
     *                  information about the reason for restriction.
     * @param threshold for reasons that are due to exceeding some threshold, the threshold value
     *                  must be specified. The unit of the threshold depends on the reason and/or
     *                  subReason. For time, use milliseconds. For memory, use KB. For count, use
     *                  the actual count or normalized as per-hour. For power, use milliwatts. Etc.
     *
     * @hide
     */
    @RequiresPermission(Manifest.permission.DEVICE_POWER)
    public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
            @RestrictionLevel int restrictionLevel, boolean enabled,
            @RestrictionReason int reason,
            @Nullable String subReason, long threshold) {
        try {
            getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
                    reason, subReason, threshold);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Notifies {@link #getRunningAppProcesses app processes} that the system properties
     * have changed.
+8 −0
Original line number Diff line number Diff line
@@ -1009,4 +1009,12 @@ interface IActivityManager {
     * @param originatingUid The UID of the instrumented app that initialized the override
     */
    void clearAllOverridePermissionStates(int originatingUid);

    /**
     * Request the system to log the reason for restricting / unrestricting an app.
     * @see ActivityManager#noteAppRestrictionEnabled
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
    void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
            boolean enabled, int reason, in String subReason, long threshold);
}
+1 −1
Original line number Diff line number Diff line
@@ -78,6 +78,6 @@ public class ActivityManagerTest {
    public void testRestrictionLevel() throws Exception {
        // For the moment mostly want to confirm we don't crash
        assertNotNull(ActivityManager.restrictionLevelToName(
                ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
                ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED));
    }
}
+55 −2
Original line number Diff line number Diff line
@@ -18,12 +18,15 @@ package com.android.settingslib.fuelgauge;

import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;

import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.IDeviceIdleController;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.DeviceConfig;
@@ -163,27 +166,77 @@ public class PowerAllowlistBackend {

    /**
     * Add app into power save allow list.
     * @param pkg packageName
     * @param pkg packageName of the app
     */
    // TODO: Fix all callers to pass in UID
    public void addApp(String pkg) {
        addApp(pkg, Process.INVALID_UID);
    }

    /**
     * Add app into power save allow list.
     * @param pkg packageName of the app
     * @param uid uid of the app
     */
    public void addApp(String pkg, int uid) {
        try {
            if (android.app.Flags.appRestrictionsApi()) {
                if (uid == Process.INVALID_UID) {
                    uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
                }
                final boolean wasInList = isAllowlisted(pkg, uid);

                if (!wasInList) {
                    mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
                            pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
                            true, ActivityManager.RESTRICTION_REASON_USER,
                            "settings", 0);
                }
            }

            mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
            mAllowlistedApps.add(pkg);
        } catch (RemoteException e) {
            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Unable to find package", e);
        }
    }

    /**
     * Remove package from power save allow list.
     * @param pkg
     * @param pkg packageName of the app
     */
    public void removeApp(String pkg) {
        removeApp(pkg, Process.INVALID_UID);
    }

    /**
     * Remove package from power save allow list.
     * @param pkg packageName of the app
     * @param uid uid of the app
     */
    public void removeApp(String pkg, int uid) {
        try {
            if (android.app.Flags.appRestrictionsApi()) {
                if (uid == Process.INVALID_UID) {
                    uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
                }
                final boolean wasInList = isAllowlisted(pkg, uid);
                if (wasInList) {
                    mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
                            pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
                            false, ActivityManager.RESTRICTION_REASON_USER,
                            "settings", 0);
                }
            }

            mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
            mAllowlistedApps.remove(pkg);
        } catch (RemoteException e) {
            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Unable to find package", e);
        }
    }

+3 −3
Original line number Diff line number Diff line
@@ -96,7 +96,7 @@ public class PowerAllowlistBackendTest {
        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();

        mPowerAllowlistBackend.addApp(PACKAGE_TWO);
        mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID);

        verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -104,7 +104,7 @@ public class PowerAllowlistBackendTest {
        assertThat(mPowerAllowlistBackend.isAllowlisted(
                new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue();

        mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
        mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID);

        verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -112,7 +112,7 @@ public class PowerAllowlistBackendTest {
        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();

        mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
        mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID);

        verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse();
Loading