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

Commit eb898f1b authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Do not throttle EXEMPT apps on battery saver

- Also keep track of # of callback calls and their duration and print them in
dumpsys. Example:

  Stats:
    UID_STATE_CHANGED: count=141, total=31.9ms, avg=0.226ms
    RUN_ANY_CHANGED: count=0, total=0.0ms, avg=0.000ms
    ALL_UNWHITELISTED: count=0, total=0.0ms, avg=0.000ms
    ALL_WHITELIST_CHANGED: count=0, total=0.0ms, avg=0.000ms
    TEMP_WHITELIST_CHANGED: count=28, total=14.9ms, avg=0.532ms
    EXEMPT_CHANGED: count=2, total=4.7ms, avg=2.370ms
    FORCE_ALL_CHANGED: count=6, total=1.1ms, avg=0.178ms
    FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED: count=0, total=0.0ms, avg=0.000ms

Bug: 70565111
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
Test: atest CtsBatterySavingTestCases
Test: atest CtsJobSchedulerTestCases
Test: atest CtsAlarmManagerTestCases

Change-Id: Ie4983456dd60f7115a15ee25a8d1bf5c078dac74
parent 4b7abff0
Loading
Loading
Loading
Loading
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.util;

/**
 * A sparse array of ArraySets, which is suitable to hold userid->packages association.
 *
 * @hide
 */
public class SparseSetArray<T> {
    private final SparseArray<ArraySet<T>> mData = new SparseArray<>();

    public SparseSetArray() {
    }

    /**
     * Add a value at index n.
     * @return FALSE when the value already existed at the given index, TRUE otherwise.
     */
    public boolean add(int n, T value) {
        ArraySet<T> set = mData.get(n);
        if (set == null) {
            set = new ArraySet<>();
            mData.put(n, set);
        }
        if (set.contains(value)) {
            return true;
        }
        set.add(value);
        return false;
    }

    /**
     * @return whether a value exists at index n.
     */
    public boolean contains(int n, T value) {
        final ArraySet<T> set = mData.get(n);
        if (set == null) {
            return false;
        }
        return set.contains(value);
    }

    /**
     * Remove a value from index n.
     * @return TRUE when the value existed at the given index and removed, FALSE otherwise.
     */
    public boolean remove(int n, T value) {
        final ArraySet<T> set = mData.get(n);
        if (set == null) {
            return false;
        }
        final boolean ret = set.remove(value);
        if (set.size() == 0) {
            mData.remove(n);
        }
        return ret;
    }

    /**
     * Remove all values from index n.
     */
    public void remove(int n) {
        mData.remove(n);
    }
    public int size() {
        return mData.size();
    }

    public int keyAt(int index) {
        return mData.keyAt(index);
    }

    public int sizeAt(int index) {
        final ArraySet<T> set = mData.valueAt(index);
        if (set == null) {
            return 0;
        }
        return set.size();
    }

    public T valueAt(int intIndex, int valueIndex) {
        return mData.valueAt(intIndex).valueAt(valueIndex);
    }
}
+15 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

syntax = "proto2";

import "frameworks/base/core/proto/android/server/statlogger.proto";

package com.android.server;

option java_multiple_files = true;
@@ -48,6 +50,17 @@ message ForceAppStandbyTrackerProto {
  // Whether force app standby for small battery device setting is enabled
  optional bool force_all_apps_standby_for_small_battery = 7;

  // Whether device is charging
  optional bool is_charging = 8;
  // Whether device is plugged in to the charger
  optional bool is_plugged_in = 8;

  // Performance stats.
  optional StatLoggerProto stats = 9;

  message ExemptedPackage {
    optional int32 userId = 1;
    optional string package_name = 2;
  }

  // Packages that are in the EXEMPT bucket.
  repeated ExemptedPackage exempted_packages = 10;
}
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

syntax = "proto2";

package com.android.server;

option java_multiple_files = true;

// Dump from StatLogger.
message StatLoggerProto {
  message Event {
    optional int32 eventId = 1;
    optional string label = 2;
    optional int32 count = 3;
    optional int64 total_duration_micros = 4;
  }

  repeated Event events = 1;
}
+156 −5
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +45,7 @@ import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
@@ -50,6 +54,7 @@ import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.ForceAppStandbyTrackerProto.ExemptedPackage;
import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;

import java.io.PrintWriter;
@@ -74,7 +79,7 @@ import java.util.List;
 */
public class ForceAppStandbyTracker {
    private static final String TAG = "ForceAppStandbyTracker";
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = true;

    @GuardedBy("ForceAppStandbyTracker.class")
    private static ForceAppStandbyTracker sInstance;
@@ -89,6 +94,8 @@ public class ForceAppStandbyTracker {
    AppOpsManager mAppOpsManager;
    IAppOpsService mAppOpsService;
    PowerManagerInternal mPowerManagerInternal;
    StandbyTracker mStandbyTracker;
    UsageStatsManagerInternal mUsageStatsManagerInternal;

    private final MyHandler mHandler;

@@ -113,6 +120,12 @@ public class ForceAppStandbyTracker {
    @GuardedBy("mLock")
    private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;

    /**
     * Per-user packages that are in the EXEMPT bucket.
     */
    @GuardedBy("mLock")
    private final SparseSetArray<String> mExemptedPackages = new SparseSetArray<>();

    @GuardedBy("mLock")
    final ArraySet<Listener> mListeners = new ArraySet<>();

@@ -146,6 +159,28 @@ public class ForceAppStandbyTracker {
    @GuardedBy("mLock")
    boolean mForcedAppStandbyEnabled;

    interface Stats {
        int UID_STATE_CHANGED = 0;
        int RUN_ANY_CHANGED = 1;
        int ALL_UNWHITELISTED = 2;
        int ALL_WHITELIST_CHANGED = 3;
        int TEMP_WHITELIST_CHANGED = 4;
        int EXEMPT_CHANGED = 5;
        int FORCE_ALL_CHANGED = 6;
        int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 7;
    }

    private final StatLogger mStatLogger = new StatLogger(new String[] {
            "UID_STATE_CHANGED",
            "RUN_ANY_CHANGED",
            "ALL_UNWHITELISTED",
            "ALL_WHITELIST_CHANGED",
            "TEMP_WHITELIST_CHANGED",
            "EXEMPT_CHANGED",
            "FORCE_ALL_CHANGED",
            "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED",
    });

    @VisibleForTesting
    class FeatureFlagsObserver extends ContentObserver {
        FeatureFlagsObserver() {
@@ -162,12 +197,11 @@ public class ForceAppStandbyTracker {
        }

        boolean isForcedAppStandbyEnabled() {
            return Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
            return injectGetGlobalSettingInt(Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
        }

        boolean isForcedAppStandbyForSmallBatteryEnabled() {
            return Settings.Global.getInt(mContext.getContentResolver(),
            return injectGetGlobalSettingInt(
                    Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1;
        }

@@ -258,6 +292,17 @@ public class ForceAppStandbyTracker {
            // only for affected app-ids.

            updateAllJobs();

            // Note when an app is just put in the temp whitelist, we do *not* drain pending alarms.
        }

        /**
         * This is called when the EXEMPT bucket is updated.
         */
        private void onExemptChanged(ForceAppStandbyTracker sender) {
            // This doesn't happen very often, so just re-evaluate all jobs / alarms.
            updateAllJobs();
            unblockAllUnrestrictedAlarms();
        }

        /**
@@ -346,11 +391,16 @@ public class ForceAppStandbyTracker {
            mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
            mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
            mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
            mUsageStatsManagerInternal = Preconditions.checkNotNull(
                    injectUsageStatsManagerInternal());

            mFlagsObserver = new FeatureFlagsObserver();
            mFlagsObserver.register();
            mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled();
            mForceAllAppStandbyForSmallBattery =
                    mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled();
            mStandbyTracker = new StandbyTracker();
            mUsageStatsManagerInternal.addAppIdleStateChangeListener(mStandbyTracker);

            try {
                mIActivityManager.registerUidObserver(new UidObserver(),
@@ -407,11 +457,21 @@ public class ForceAppStandbyTracker {
        return LocalServices.getService(PowerManagerInternal.class);
    }

    @VisibleForTesting
    UsageStatsManagerInternal injectUsageStatsManagerInternal() {
        return LocalServices.getService(UsageStatsManagerInternal.class);
    }

    @VisibleForTesting
    boolean isSmallBatteryDevice() {
        return ActivityManager.isSmallBatteryDevice();
    }

    @VisibleForTesting
    int injectGetGlobalSettingInt(String key, int def) {
        return Settings.Global.getInt(mContext.getContentResolver(), key, def);
    }

    /**
     * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
     */
@@ -604,6 +664,30 @@ public class ForceAppStandbyTracker {
        }
    }

    final class StandbyTracker extends AppIdleStateChangeListener {
        @Override
        public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
                int bucket) {
            if (DEBUG) {
                Slog.d(TAG,"onAppIdleStateChanged: " + packageName + " u" + userId
                        + (idle ? " idle" : " active") + " " + bucket);
            }
            final boolean changed;
            if (bucket == UsageStatsManager.STANDBY_BUCKET_EXEMPTED) {
                changed = mExemptedPackages.add(userId, packageName);
            } else {
                changed = mExemptedPackages.remove(userId, packageName);
            }
            if (changed) {
                mHandler.notifyExemptChanged();
            }
        }

        @Override
        public void onParoleStateChanged(boolean isParoleOn) {
        }
    }

    private Listener[] cloneListeners() {
        synchronized (mLock) {
            return mListeners.toArray(new Listener[mListeners.size()]);
@@ -619,6 +703,7 @@ public class ForceAppStandbyTracker {
        private static final int MSG_FORCE_ALL_CHANGED = 6;
        private static final int MSG_USER_REMOVED = 7;
        private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
        private static final int MSG_EXEMPT_CHANGED = 9;

        public MyHandler(Looper looper) {
            super(looper);
@@ -652,6 +737,10 @@ public class ForceAppStandbyTracker {
            obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget();
        }

        public void notifyExemptChanged() {
            obtainMessage(MSG_EXEMPT_CHANGED).sendToTarget();
        }

        public void doUserRemoved(int userId) {
            obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
        }
@@ -672,37 +761,57 @@ public class ForceAppStandbyTracker {
            }
            final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;

            long start = mStatLogger.getTime();
            switch (msg.what) {
                case MSG_UID_STATE_CHANGED:
                    for (Listener l : cloneListeners()) {
                        l.onUidForegroundStateChanged(sender, msg.arg1);
                    }
                    mStatLogger.logDurationStat(Stats.UID_STATE_CHANGED, start);
                    return;

                case MSG_RUN_ANY_CHANGED:
                    for (Listener l : cloneListeners()) {
                        l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
                    }
                    mStatLogger.logDurationStat(Stats.RUN_ANY_CHANGED, start);
                    return;

                case MSG_ALL_UNWHITELISTED:
                    for (Listener l : cloneListeners()) {
                        l.onPowerSaveUnwhitelisted(sender);
                    }
                    mStatLogger.logDurationStat(Stats.ALL_UNWHITELISTED, start);
                    return;

                case MSG_ALL_WHITELIST_CHANGED:
                    for (Listener l : cloneListeners()) {
                        l.onPowerSaveWhitelistedChanged(sender);
                    }
                    mStatLogger.logDurationStat(Stats.ALL_WHITELIST_CHANGED, start);
                    return;

                case MSG_TEMP_WHITELIST_CHANGED:
                    for (Listener l : cloneListeners()) {
                        l.onTempPowerSaveWhitelistChanged(sender);
                    }
                    mStatLogger.logDurationStat(Stats.TEMP_WHITELIST_CHANGED, start);
                    return;

                case MSG_EXEMPT_CHANGED:
                    for (Listener l : cloneListeners()) {
                        l.onExemptChanged(sender);
                    }
                    mStatLogger.logDurationStat(Stats.EXEMPT_CHANGED, start);
                    return;

                case MSG_FORCE_ALL_CHANGED:
                    for (Listener l : cloneListeners()) {
                        l.onForceAllAppsStandbyChanged(sender);
                    }
                    mStatLogger.logDurationStat(Stats.FORCE_ALL_CHANGED, start);
                    return;

                case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED:
                    // Feature flag for forced app standby changed.
                    final boolean unblockAlarms;
@@ -715,7 +824,10 @@ public class ForceAppStandbyTracker {
                            l.unblockAllUnrestrictedAlarms();
                        }
                    }
                    mStatLogger.logDurationStat(
                            Stats.FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED, start);
                    return;

                case MSG_USER_REMOVED:
                    handleUserRemoved(msg.arg1);
                    return;
@@ -742,6 +854,7 @@ public class ForceAppStandbyTracker {
                    mForegroundUids.removeAt(i);
                }
            }
            mExemptedPackages.remove(removedUserId);
        }
    }

@@ -860,6 +973,10 @@ public class ForceAppStandbyTracker {
            if (exemptOnBatterySaver) {
                return false;
            }
            final int userId = UserHandle.getUserId(uid);
            if (mExemptedPackages.contains(userId, packageName)) {
                return false;
            }
            return mForceAllAppsStandby;
        }
    }
@@ -965,6 +1082,23 @@ public class ForceAppStandbyTracker {
            pw.print("Temp whitelist appids: ");
            pw.println(Arrays.toString(mTempWhitelistedAppIds));

            pw.print(indent);
            pw.println("Exempted packages:");
            for (int i = 0; i < mExemptedPackages.size(); i++) {
                pw.print(indent);
                pw.print("  User ");
                pw.print(mExemptedPackages.keyAt(i));
                pw.println();

                for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) {
                    pw.print(indent);
                    pw.print("    ");
                    pw.print(mExemptedPackages.valueAt(i, j));
                    pw.println();
                }
            }
            pw.println();

            pw.print(indent);
            pw.println("Restricted packages:");
            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
@@ -975,6 +1109,8 @@ public class ForceAppStandbyTracker {
                pw.print(uidAndPackage.second);
                pw.println();
            }

            mStatLogger.dump(pw, indent);
        }
    }

@@ -987,7 +1123,7 @@ public class ForceAppStandbyTracker {
                    isSmallBatteryDevice());
            proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY,
                    mForceAllAppStandbyForSmallBattery);
            proto.write(ForceAppStandbyTrackerProto.IS_CHARGING, mIsPluggedIn);
            proto.write(ForceAppStandbyTrackerProto.IS_PLUGGED_IN, mIsPluggedIn);

            for (int i = 0; i < mForegroundUids.size(); i++) {
                if (mForegroundUids.valueAt(i)) {
@@ -1004,6 +1140,18 @@ public class ForceAppStandbyTracker {
                proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
            }

            for (int i = 0; i < mExemptedPackages.size(); i++) {
                for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) {
                    final long token2 = proto.start(
                            ForceAppStandbyTrackerProto.EXEMPTED_PACKAGES);

                    proto.write(ExemptedPackage.USER_ID, mExemptedPackages.keyAt(i));
                    proto.write(ExemptedPackage.PACKAGE_NAME, mExemptedPackages.valueAt(i, j));

                    proto.end(token2);
                }
            }

            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
                final long token2 = proto.start(
                        ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
@@ -1012,6 +1160,9 @@ public class ForceAppStandbyTracker {
                        uidAndPackage.second);
                proto.end(token2);
            }

            mStatLogger.dumpProto(proto, ForceAppStandbyTrackerProto.STATS);

            proto.end(token);
        }
    }
+108 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.server.StatLoggerProto.Event;

import java.io.PrintWriter;

/**
 * Simple class to keep track of the number of times certain events happened and their durations for
 * benchmarking.
 *
 * TODO Update shortcut service to switch to it.
 *
 * @hide
 */
public class StatLogger {
    private final Object mLock = new Object();

    private final int SIZE;

    @GuardedBy("mLock")
    private final int[] mCountStats;

    @GuardedBy("mLock")
    private final long[] mDurationStats;

    private final String[] mLabels;

    public StatLogger(String[] eventLabels) {
        SIZE = eventLabels.length;
        mCountStats = new int[SIZE];
        mDurationStats = new long[SIZE];
        mLabels = eventLabels;
    }

    /**
     * Return the current time in the internal time unit.
     * Call it before an event happens, and
     * give it back to the {@link #logDurationStat(int, long)}} after the event.
     */
    public long getTime() {
        return SystemClock.elapsedRealtimeNanos() / 1000;
    }

    /**
     * @see {@link #getTime()}
     */
    public void logDurationStat(int eventId, long start) {
        synchronized (mLock) {
            mCountStats[eventId]++;
            mDurationStats[eventId] += (getTime() - start);
        }
    }

    public void dump(PrintWriter pw, String prefix) {
        synchronized (mLock) {
            pw.print(prefix);
            pw.println("Stats:");
            for (int i = 0; i < SIZE; i++) {
                pw.print(prefix);
                pw.print("  ");
                final int count = mCountStats[i];
                final double durationMs = mDurationStats[i] / 1000.0;
                pw.println(String.format("%s: count=%d, total=%.1fms, avg=%.3fms",
                        mLabels[i], count, durationMs,
                        (count == 0 ? 0 : ((double) durationMs) / count)));
            }
        }
    }

    public void dumpProto(ProtoOutputStream proto, long fieldId) {
        synchronized (mLock) {
            final long outer = proto.start(fieldId);

            for (int i = 0; i < mLabels.length; i++) {
                final long inner = proto.start(StatLoggerProto.EVENTS);

                proto.write(Event.EVENT_ID, i);
                proto.write(Event.LABEL, mLabels[i]);
                proto.write(Event.COUNT, mCountStats[i]);
                proto.write(Event.TOTAL_DURATION_MICROS, mDurationStats[i]);

                proto.end(inner);
            }

            proto.end(outer);
        }
    }
}
Loading