blob: 147f8d1a1e3271a91f0e064ca3a13b7531f7eeca [file] [log] [blame]
/*
* Copyright (C) 2022 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.am;
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
import static android.app.ActivityManager.isLowRamDeviceStatic;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
import static android.os.BatteryConsumer.PROCESS_STATE_COUNT;
import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.util.TimeUtils.formatTime;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RestrictionLevel;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.AppBackgroundRestrictionsInfo;
import android.os.AppBatteryStatsProto;
import android.os.BatteryConsumer;
import android.os.BatteryConsumer.Dimensions;
import android.os.BatteryStatsInternal;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.Process;
import android.os.SystemClock;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
import com.android.server.am.AppRestrictionController.TrackerType;
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
import com.android.server.pm.UserManagerInternal;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* The battery usage tracker for apps, currently we are focusing on background + FGS battery here.
*/
final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
implements UidBatteryUsageProvider {
static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryTracker" : TAG_AM;
static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE =
DEBUG_BACKGROUND_BATTERY_TRACKER | false;
// As we don't support realtime per-UID battery usage stats yet, we're polling the stats
// in a regular time basis.
private final long mBatteryUsageStatsPollingIntervalMs;
static final long BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG = 30 * ONE_MINUTE; // 30 mins
static final long BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG = 2_000L; // 2s
private final long mBatteryUsageStatsPollingMinIntervalMs;
/**
* The battery stats query is expensive, so we'd throttle the query.
*/
static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG = 5 * ONE_MINUTE; // 5 mins
static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG = 2_000L; // 2s
static final ImmutableBatteryUsage BATTERY_USAGE_NONE = new ImmutableBatteryUsage();
private final Runnable mBgBatteryUsageStatsPolling = this::updateBatteryUsageStatsAndCheck;
private final Runnable mBgBatteryUsageStatsCheck = this::checkBatteryUsageStats;
/**
* This tracks the user ids which are or were active during the last polling window,
* the index is the user id, and the value is if it's still running or not by now.
*/
@GuardedBy("mLock")
private final SparseBooleanArray mActiveUserIdStates = new SparseBooleanArray();
/**
* When was the last battery usage sampled.
*/
@GuardedBy("mLock")
private long mLastBatteryUsageSamplingTs;
/**
* Whether or not there is an ongoing battery stats update.
*/
@GuardedBy("mLock")
private boolean mBatteryUsageStatsUpdatePending;
/**
* The current known battery usage data for each UID, since the system boots or
* the last battery stats reset prior to that (whoever is earlier).
*/
@GuardedBy("mLock")
private final SparseArray<BatteryUsage> mUidBatteryUsage = new SparseArray<>();
/**
* The battery usage for each UID, in the rolling window of the past.
*/
@GuardedBy("mLock")
private final SparseArray<ImmutableBatteryUsage> mUidBatteryUsageInWindow = new SparseArray<>();
/**
* The uid battery usage stats data from our last query, it consists of the data since
* last battery stats reset.
*/
@GuardedBy("mLock")
private final SparseArray<ImmutableBatteryUsage> mLastUidBatteryUsage = new SparseArray<>();
// No lock is needed.
private final SparseArray<BatteryUsage> mTmpUidBatteryUsage = new SparseArray<>();
// No lock is needed.
private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsage2 = new SparseArray<>();
// No lock is needed.
private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsageInWindow =
new SparseArray<>();
// No lock is needed.
private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
/**
* The start timestamp of the battery usage stats result from our last query.
*/
@GuardedBy("mLock")
private long mLastUidBatteryUsageStartTs;
/**
* elapseRealTime of last time the AppBatteryTracker is reported to statsd.
*/
@GuardedBy("mLock")
private long mLastReportTime = 0;
// For debug only.
private final SparseArray<ImmutableBatteryUsage> mDebugUidPercentages = new SparseArray<>();
AppBatteryTracker(Context context, AppRestrictionController controller) {
this(context, controller, null, null);
}
AppBatteryTracker(Context context, AppRestrictionController controller,
Constructor<? extends Injector<AppBatteryPolicy>> injector,
Object outerContext) {
super(context, controller, injector, outerContext);
if (injector == null) {
mBatteryUsageStatsPollingIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER
? BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG
: BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG;
mBatteryUsageStatsPollingMinIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER
? BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG
: BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG;
} else {
mBatteryUsageStatsPollingIntervalMs = BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG;
mBatteryUsageStatsPollingMinIntervalMs =
BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG;
}
mInjector.setPolicy(new AppBatteryPolicy(mInjector, this));
}
@Override
@TrackerType int getType() {
return AppRestrictionController.TRACKER_TYPE_BATTERY;
}
@Override
void onSystemReady() {
super.onSystemReady();
final UserManagerInternal um = mInjector.getUserManagerInternal();
final int[] userIds = um.getUserIds();
for (int userId : userIds) {
if (um.isUserRunning(userId)) {
synchronized (mLock) {
mActiveUserIdStates.put(userId, true);
}
}
}
scheduleBatteryUsageStatsUpdateIfNecessary(mBatteryUsageStatsPollingIntervalMs);
}
private void scheduleBatteryUsageStatsUpdateIfNecessary(long delay) {
if (mInjector.getPolicy().isEnabled()) {
synchronized (mLock) {
if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsPolling)) {
mBgHandler.postDelayed(mBgBatteryUsageStatsPolling, delay);
}
}
logAppBatteryTrackerIfNeeded();
}
}
/**
* Log per-uid BatteryTrackerInfo to statsd every 24 hours (as the window specified in
* {@link AppBatteryPolicy#mBgCurrentDrainWindowMs})
*/
private void logAppBatteryTrackerIfNeeded() {
final long now = SystemClock.elapsedRealtime();
synchronized (mLock) {
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
if (now - mLastReportTime < bgPolicy.mBgCurrentDrainWindowMs) {
return;
} else {
mLastReportTime = now;
}
}
updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
synchronized (mLock) {
for (int i = 0, size = mUidBatteryUsageInWindow.size(); i < size; i++) {
final int uid = mUidBatteryUsageInWindow.keyAt(i);
if (!UserHandle.isCore(uid) && !UserHandle.isApp(uid)) {
continue;
}
if (BATTERY_USAGE_NONE.equals(mUidBatteryUsageInWindow.valueAt(i))) {
continue;
}
FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO,
uid,
AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN, // RestrictionLevel
AppBackgroundRestrictionsInfo.THRESHOLD_UNKNOWN,
AppBackgroundRestrictionsInfo.UNKNOWN_TRACKER,
null, // FgsTrackerInfo
getTrackerInfoForStatsd(uid),
null, // BroadcastEventsTrackerInfo
null, // BindServiceEventsTrackerInfo
AppBackgroundRestrictionsInfo.REASON_UNKNOWN, // ExemptionReason
AppBackgroundRestrictionsInfo.UNKNOWN, // OptimizationLevel
AppBackgroundRestrictionsInfo.SDK_UNKNOWN, // TargetSdk
isLowRamDeviceStatic(),
AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN // previous RestrictionLevel
);
}
}
}
/**
* Get the BatteryTrackerInfo object of the given uid.
* @return byte array of the proto object.
*/
@Override
byte[] getTrackerInfoForStatsd(int uid) {
final ImmutableBatteryUsage temp;
synchronized (mLock) {
temp = mUidBatteryUsageInWindow.get(uid);
}
if (temp == null) {
return null;
}
final BatteryUsage bgUsage = temp.calcPercentage(uid, mInjector.getPolicy());
final double allUsage = bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_UNSPECIFIED]
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND]
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND]
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_CACHED];
final double usageBackground =
bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND];
final double usageFgs =
bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE];
final double usageForeground =
bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND];
final double usageCached =
bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_CACHED];
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
Slog.d(TAG, "getBatteryTrackerInfoProtoLocked uid:" + uid
+ " allUsage:" + String.format("%4.2f%%", allUsage)
+ " usageBackground:" + String.format("%4.2f%%", usageBackground)
+ " usageFgs:" + String.format("%4.2f%%", usageFgs)
+ " usageForeground:" + String.format("%4.2f%%", usageForeground)
+ " usageCached:" + String.format("%4.2f%%", usageCached));
}
final ProtoOutputStream proto = new ProtoOutputStream();
proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_24H,
allUsage * 10000);
proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_BACKGROUND,
usageBackground * 10000);
proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FGS,
usageFgs * 10000);
proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FOREGROUND,
usageForeground * 10000);
proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_CACHED,
usageCached * 10000);
proto.flush();
return proto.getBytes();
}
@Override
void onUserStarted(final @UserIdInt int userId) {
synchronized (mLock) {
mActiveUserIdStates.put(userId, true);
}
}
@Override
void onUserStopped(final @UserIdInt int userId) {
synchronized (mLock) {
mActiveUserIdStates.put(userId, false);
}
}
@Override
void onUserRemoved(final @UserIdInt int userId) {
synchronized (mLock) {
mActiveUserIdStates.delete(userId);
for (int i = mUidBatteryUsage.size() - 1; i >= 0; i--) {
if (UserHandle.getUserId(mUidBatteryUsage.keyAt(i)) == userId) {
mUidBatteryUsage.removeAt(i);
}
}
for (int i = mUidBatteryUsageInWindow.size() - 1; i >= 0; i--) {
if (UserHandle.getUserId(mUidBatteryUsageInWindow.keyAt(i)) == userId) {
mUidBatteryUsageInWindow.removeAt(i);
}
}
mInjector.getPolicy().onUserRemovedLocked(userId);
}
}
@Override
void onUidRemoved(final int uid) {
synchronized (mLock) {
mUidBatteryUsage.delete(uid);
mUidBatteryUsageInWindow.delete(uid);
mInjector.getPolicy().onUidRemovedLocked(uid);
}
}
@Override
void onUserInteractionStarted(String packageName, int uid) {
mInjector.getPolicy().onUserInteractionStarted(packageName, uid);
}
@Override
void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
mInjector.getPolicy().onBackgroundRestrictionChanged(uid, pkgName, restricted);
}
/**
* @return The total battery usage of the given UID since the system boots or last battery
* stats reset prior to that (whoever is earlier).
*
* <p>
* Note: as there are throttling in polling the battery usage stats by
* the {@link #mBatteryUsageStatsPollingMinIntervalMs}, the returned data here
* could be either from the most recent polling, or the very fresh one - if the most recent
* polling is outdated, it'll trigger an immediate update.
* </p>
*/
@Override
@NonNull
public ImmutableBatteryUsage getUidBatteryUsage(int uid) {
final long now = mInjector.currentTimeMillis();
final boolean updated = updateBatteryUsageStatsIfNecessary(now, false);
synchronized (mLock) {
if (updated) {
// We just got fresh data, schedule a check right a way.
mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
scheduleBgBatteryUsageStatsCheck();
}
final BatteryUsage usage = mUidBatteryUsage.get(uid);
return usage != null ? new ImmutableBatteryUsage(usage) : BATTERY_USAGE_NONE;
}
}
private void scheduleBgBatteryUsageStatsCheck() {
if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
mBgHandler.post(mBgBatteryUsageStatsCheck);
}
}
private void updateBatteryUsageStatsAndCheck() {
final long now = mInjector.currentTimeMillis();
if (updateBatteryUsageStatsIfNecessary(now, false)) {
checkBatteryUsageStats();
} else {
// We didn't do the battery stats update above, schedule a check later.
synchronized (mLock) {
scheduleBatteryUsageStatsUpdateIfNecessary(
mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
}
}
}
private void checkBatteryUsageStats() {
final long now = SystemClock.elapsedRealtime();
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
try {
final SparseArray<ImmutableBatteryUsage> uidConsumers = mTmpUidBatteryUsageInWindow;
synchronized (mLock) {
copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers);
}
final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
for (int i = 0, size = uidConsumers.size(); i < size; i++) {
final int uid = uidConsumers.keyAt(i);
final ImmutableBatteryUsage actualUsage = uidConsumers.valueAt(i);
final ImmutableBatteryUsage exemptedUsage = mAppRestrictionController
.getUidBatteryExemptedUsageSince(uid, since, now,
bgPolicy.mBgCurrentDrainExemptedTypes);
// It's possible the exemptedUsage could be larger than actualUsage,
// as the former one is an approximate value.
final ImmutableBatteryUsage bgUsage = actualUsage.mutate()
.subtract(exemptedUsage)
.calcPercentage(uid, bgPolicy)
.unmutate();
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE
&& !BATTERY_USAGE_NONE.equals(actualUsage)) {
Slog.i(TAG, String.format(
"UID %d: %s (%s) | %s | %s over the past %s",
uid,
bgUsage.toString(),
bgUsage.percentageToString(),
exemptedUsage.toString(),
actualUsage.toString(),
TimeUtils.formatDuration(bgPolicy.mBgCurrentDrainWindowMs)));
}
bgPolicy.handleUidBatteryUsage(uid, bgUsage);
}
// For debugging only.
for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) {
bgPolicy.handleUidBatteryUsage(mDebugUidPercentages.keyAt(i),
mDebugUidPercentages.valueAt(i));
}
} finally {
scheduleBatteryUsageStatsUpdateIfNecessary(mBatteryUsageStatsPollingIntervalMs);
}
}
/**
* Update the battery usage stats data, if it's allowed to do so.
*
* @return {@code true} if the battery stats is up to date.
*/
private boolean updateBatteryUsageStatsIfNecessary(long now, boolean forceUpdate) {
boolean needUpdate = false;
boolean updated = false;
synchronized (mLock) {
if (mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs < now
|| forceUpdate) {
// The data we have is outdated.
if (mBatteryUsageStatsUpdatePending) {
// An update is ongoing in parallel, just wait for it.
try {
mLock.wait();
} catch (InterruptedException e) {
}
} else {
mBatteryUsageStatsUpdatePending = true;
needUpdate = true;
}
updated = true;
} else {
// The data is still fresh, no need to update it.
return false;
}
}
if (needUpdate) {
// We don't want to query the battery usage stats with mLock held.
updateBatteryUsageStatsOnce(now);
synchronized (mLock) {
mLastBatteryUsageSamplingTs = now;
mBatteryUsageStatsUpdatePending = false;
mLock.notifyAll();
}
}
return updated;
}
private void updateBatteryUsageStatsOnce(long now) {
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
final ArraySet<UserHandle> userIds = mTmpUserIds;
final SparseArray<BatteryUsage> buf = mTmpUidBatteryUsage;
final BatteryStatsInternal batteryStatsInternal = mInjector.getBatteryStatsInternal();
final long windowSize = bgPolicy.mBgCurrentDrainWindowMs;
buf.clear();
userIds.clear();
synchronized (mLock) {
for (int i = mActiveUserIdStates.size() - 1; i >= 0; i--) {
userIds.add(UserHandle.of(mActiveUserIdStates.keyAt(i)));
if (!mActiveUserIdStates.valueAt(i)) {
mActiveUserIdStates.removeAt(i);
}
}
}
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
Slog.i(TAG, "updateBatteryUsageStatsOnce");
}
// Query the current battery usage stats.
BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
.setMaxStatsAgeMs(0);
final BatteryUsageStats stats = updateBatteryUsageStatsOnceInternal(0,
buf, builder, userIds, batteryStatsInternal);
final long curStart = stats != null ? stats.getStatsStartTimestamp() : 0L;
final long curEnd = stats != null ? stats.getStatsEndTimestamp() : now;
long curDuration = curEnd - curStart;
boolean needUpdateUidBatteryUsageInWindow = true;
if (curDuration >= windowSize) {
// If we do have long enough data for the window, save it.
synchronized (mLock) {
copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
}
needUpdateUidBatteryUsageInWindow = false;
}
// Save the current data, which includes the battery usage since last reset.
mTmpUidBatteryUsage2.clear();
copyUidBatteryUsage(buf, mTmpUidBatteryUsage2);
final long lastUidBatteryUsageStartTs;
synchronized (mLock) {
lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
mLastUidBatteryUsageStartTs = curStart;
}
if (curStart > lastUidBatteryUsageStartTs && lastUidBatteryUsageStartTs > 0) {
// The battery usage stats committed data since our last query,
// let's query the snapshots to get the data since last start.
builder = new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
.aggregateSnapshots(lastUidBatteryUsageStartTs, curStart);
final BatteryUsageStats statsCommit =
updateBatteryUsageStatsOnceInternal(0,
buf,
builder,
userIds,
batteryStatsInternal);
curDuration += curStart - lastUidBatteryUsageStartTs;
try {
if (statsCommit != null) {
statsCommit.close();
} else {
Slog.w(TAG, "Stat was null");
}
} catch (IOException e) {
Slog.w(TAG, "Failed to close a stat");
}
}
if (needUpdateUidBatteryUsageInWindow && curDuration >= windowSize) {
// If we do have long enough data for the window, save it.
synchronized (mLock) {
copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
}
needUpdateUidBatteryUsageInWindow = false;
}
// Add the delta into the global records.
synchronized (mLock) {
for (int i = 0, size = buf.size(); i < size; i++) {
final int uid = buf.keyAt(i);
final int index = mUidBatteryUsage.indexOfKey(uid);
final BatteryUsage lastUsage = mLastUidBatteryUsage.get(uid, BATTERY_USAGE_NONE);
final BatteryUsage curUsage = buf.valueAt(i);
final BatteryUsage before;
final BatteryUsage totalUsage;
if (index >= 0) {
totalUsage = mUidBatteryUsage.valueAt(index);
before = DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE
? new BatteryUsage(totalUsage) : BATTERY_USAGE_NONE;
totalUsage.subtract(lastUsage).add(curUsage);
} else {
before = totalUsage = BATTERY_USAGE_NONE;
mUidBatteryUsage.put(uid, curUsage);
}
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
final BatteryUsage actualDelta = new BatteryUsage(curUsage).subtract(lastUsage);
String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
+ ", after=" + mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE)
+ ", delta=" + actualDelta
+ ", last=" + lastUsage
+ ", curStart=" + curStart
+ ", lastLastStart=" + lastUidBatteryUsageStartTs
+ ", thisLastStart=" + mLastUidBatteryUsageStartTs;
if (!actualDelta.isValid()) {
// Something is wrong, the battery usage shouldn't be negative.
Slog.e(TAG, msg);
} else if (!BATTERY_USAGE_NONE.equals(actualDelta)) {
Slog.i(TAG, msg);
}
}
}
// Now update the mLastUidBatteryUsage with the data we just saved above.
copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
}
mTmpUidBatteryUsage2.clear();
if (needUpdateUidBatteryUsageInWindow) {
// No sufficient data for the full window still, query snapshots again.
final long start = now - windowSize;
final long end = lastUidBatteryUsageStartTs - 1;
builder = new BatteryUsageStatsQuery.Builder()
.includeProcessStateData()
.aggregateSnapshots(start, end);
updateBatteryUsageStatsOnceInternal(end - start,
buf, builder, userIds, batteryStatsInternal);
synchronized (mLock) {
copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
}
}
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
synchronized (mLock) {
for (int i = 0, size = mUidBatteryUsageInWindow.size(); i < size; i++) {
final ImmutableBatteryUsage usage = mUidBatteryUsageInWindow.valueAt(i);
if (BATTERY_USAGE_NONE.equals(usage)) {
// Skip the logging to avoid spamming.
continue;
}
Slog.i(TAG, "mUidBatteryUsageInWindow uid=" + mUidBatteryUsageInWindow.keyAt(i)
+ " usage=" + usage);
}
}
}
try {
if (stats != null) {
stats.close();
} else {
Slog.w(TAG, "Stat was null");
}
} catch (IOException e) {
Slog.w(TAG, "Failed to close a stat");
}
}
// The BatteryUsageStats object MUST BE CLOSED when finished using
private BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration,
SparseArray<BatteryUsage> buf, BatteryUsageStatsQuery.Builder builder,
ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) {
for (int i = 0, size = userIds.size(); i < size; i++) {
builder.addUser(userIds.valueAt(i));
}
final List<BatteryUsageStats> statsList = batteryStatsInternal
.getBatteryUsageStats(Arrays.asList(builder.build()));
if (ArrayUtils.isEmpty(statsList)) {
// Shouldn't happen unless in test.
return null;
}
// We need the first stat in the list, so we should
// close out the others.
final BatteryUsageStats stats = statsList.get(0);
for (int i = 1; i < statsList.size(); i++) {
try {
if (statsList.get(i) != null) {
statsList.get(i).close();
} else {
Slog.w(TAG, "Stat was null");
}
} catch (IOException e) {
Slog.w(TAG, "Failed to close a stat in BatteryUsageStats List");
}
}
final List<UidBatteryConsumer> uidConsumers = stats.getUidBatteryConsumers();
if (uidConsumers != null) {
final long start = stats.getStatsStartTimestamp();
final long end = stats.getStatsEndTimestamp();
final double scale = expectedDuration > 0
? Math.min((expectedDuration * 1.0d) / (end - start), 1.0d) : 1.0d;
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
for (UidBatteryConsumer uidConsumer : uidConsumers) {
// TODO: b/200326767 - as we are not supporting per proc state attribution yet,
// we couldn't distinguish between a real FGS vs. a bound FGS proc state.
final int rawUid = uidConsumer.getUid();
if (UserHandle.isIsolated(rawUid)) {
// Isolated processes should have been attributed to their parent processes.
continue;
}
int uid = rawUid;
// Keep the logic in sync with BatteryAppListPreferenceController.java
// Check if this UID is a shared GID. If so, we combine it with the OWNER's
// actual app UID.
final int sharedAppId = UserHandle.getAppIdFromSharedAppGid(uid);
if (sharedAppId > 0) {
uid = UserHandle.getUid(UserHandle.USER_SYSTEM, sharedAppId);
}
final BatteryUsage bgUsage = new BatteryUsage(uidConsumer, bgPolicy)
.scale(scale);
int index = buf.indexOfKey(uid);
if (index < 0) {
buf.put(uid, bgUsage);
} else {
final BatteryUsage before = buf.valueAt(index);
before.add(bgUsage);
}
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE
&& !BATTERY_USAGE_NONE.equals(bgUsage)) {
Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + rawUid
+ ", bgUsage=" + bgUsage
+ (rawUid == uid ? ""
: ", realUid=" + uid
+ ", realUsage=" + buf.get(uid))
+ ", start=" + start
+ ", end=" + end);
}
}
}
return stats;
}
private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source,
SparseArray<ImmutableBatteryUsage> dest) {
dest.clear();
for (int i = source.size() - 1; i >= 0; i--) {
dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i)));
}
}
private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source,
SparseArray<ImmutableBatteryUsage> dest, double scale) {
dest.clear();
for (int i = source.size() - 1; i >= 0; i--) {
dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i), scale));
}
}
private void onCurrentDrainMonitorEnabled(boolean enabled) {
if (enabled) {
if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsPolling)) {
mBgHandler.postDelayed(mBgBatteryUsageStatsPolling,
mBatteryUsageStatsPollingIntervalMs);
}
} else {
mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
synchronized (mLock) {
if (mBatteryUsageStatsUpdatePending) {
// An update is ongoing in parallel, just wait for it.
try {
mLock.wait();
} catch (InterruptedException e) {
}
}
mUidBatteryUsage.clear();
mUidBatteryUsageInWindow.clear();
mLastUidBatteryUsage.clear();
mLastUidBatteryUsageStartTs = mLastBatteryUsageSamplingTs = 0L;
}
}
}
void setDebugUidPercentage(int[] uids, double[][] percentages) {
mDebugUidPercentages.clear();
for (int i = 0; i < uids.length; i++) {
mDebugUidPercentages.put(uids[i],
new BatteryUsage().setPercentage(percentages[i]).unmutate());
}
scheduleBgBatteryUsageStatsCheck();
}
void clearDebugUidPercentage() {
mDebugUidPercentages.clear();
scheduleBgBatteryUsageStatsCheck();
}
@VisibleForTesting
void reset() {
synchronized (mLock) {
mUidBatteryUsage.clear();
mUidBatteryUsageInWindow.clear();
mLastUidBatteryUsage.clear();
mLastUidBatteryUsageStartTs = mLastBatteryUsageSamplingTs = 0L;
}
mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
updateBatteryUsageStatsAndCheck();
}
@Override
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("APP BATTERY STATE TRACKER:");
// Force an update.
updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
// Force a check.
scheduleBgBatteryUsageStatsCheck();
// Wait for its completion (as it runs in handler thread for the sake of thread safe)
final CountDownLatch latch = new CountDownLatch(1);
mBgHandler.getLooper().getQueue().addIdleHandler(() -> {
latch.countDown();
return false;
});
try {
latch.await();
} catch (InterruptedException e) {
}
synchronized (mLock) {
final SparseArray<ImmutableBatteryUsage> uidConsumers = mUidBatteryUsageInWindow;
pw.print(" " + prefix);
pw.print(" Last battery usage start=");
TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
pw.println();
pw.print(" " + prefix);
pw.print("Battery usage over last ");
final String newPrefix = " " + prefix;
final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
final long now = SystemClock.elapsedRealtime();
final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
pw.println(TimeUtils.formatDuration(now - since));
if (uidConsumers.size() == 0) {
pw.print(newPrefix);
pw.println("(none)");
} else {
for (int i = 0, size = uidConsumers.size(); i < size; i++) {
final int uid = uidConsumers.keyAt(i);
final BatteryUsage bgUsage = uidConsumers.valueAt(i)
.calcPercentage(uid, bgPolicy);
final BatteryUsage exemptedUsage = mAppRestrictionController
.getUidBatteryExemptedUsageSince(uid, since, now,
bgPolicy.mBgCurrentDrainExemptedTypes)
.calcPercentage(uid, bgPolicy);
final BatteryUsage reportedUsage = new BatteryUsage(bgUsage)
.subtract(exemptedUsage)
.calcPercentage(uid, bgPolicy);
pw.format("%s%s: [%s] %s (%s) | %s (%s) | %s (%s) | %s\n",
newPrefix, UserHandle.formatUid(uid),
PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
bgUsage.toString(),
bgUsage.percentageToString(),
exemptedUsage.toString(),
exemptedUsage.percentageToString(),
reportedUsage.toString(),
reportedUsage.percentageToString(),
mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE).toString());
}
}
}
super.dump(pw, prefix);
}
@Override
void dumpAsProto(ProtoOutputStream proto, int uid) {
// Force an update.
updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
synchronized (mLock) {
final SparseArray<ImmutableBatteryUsage> uidConsumers = mUidBatteryUsageInWindow;
if (uid != android.os.Process.INVALID_UID) {
final BatteryUsage usage = uidConsumers.get(uid);
if (usage != null) {
dumpUidStats(proto, uid, usage);
}
} else {
for (int i = 0, size = uidConsumers.size(); i < size; i++) {
final int aUid = uidConsumers.keyAt(i);
final BatteryUsage usage = uidConsumers.valueAt(i);
dumpUidStats(proto, aUid, usage);
}
}
}
}
private void dumpUidStats(ProtoOutputStream proto, int uid, BatteryUsage usage) {
if (usage.mUsage == null) {
return;
}
final double foregroundUsage = usage.getUsagePowerMah(PROCESS_STATE_FOREGROUND);
final double backgroundUsage = usage.getUsagePowerMah(PROCESS_STATE_BACKGROUND);
final double fgsUsage = usage.getUsagePowerMah(PROCESS_STATE_FOREGROUND_SERVICE);
final double cachedUsage = usage.getUsagePowerMah(PROCESS_STATE_CACHED);
if (foregroundUsage == 0 && backgroundUsage == 0 && fgsUsage == 0) {
return;
}
final long token = proto.start(AppBatteryStatsProto.UID_STATS);
proto.write(AppBatteryStatsProto.UidStats.UID, uid);
dumpProcessStateStats(proto,
AppBatteryStatsProto.UidStats.ProcessStateStats.FOREGROUND,
foregroundUsage);
dumpProcessStateStats(proto,
AppBatteryStatsProto.UidStats.ProcessStateStats.BACKGROUND,
backgroundUsage);
dumpProcessStateStats(proto,
AppBatteryStatsProto.UidStats.ProcessStateStats.FOREGROUND_SERVICE,
fgsUsage);
dumpProcessStateStats(proto,
AppBatteryStatsProto.UidStats.ProcessStateStats.CACHED,
cachedUsage);
proto.end(token);
}
private void dumpProcessStateStats(ProtoOutputStream proto, int processState, double powerMah) {
if (powerMah == 0) {
return;
}
final long token = proto.start(AppBatteryStatsProto.UidStats.PROCESS_STATE_STATS);
proto.write(AppBatteryStatsProto.UidStats.ProcessStateStats.PROCESS_STATE, processState);
proto.write(AppBatteryStatsProto.UidStats.ProcessStateStats.POWER_MAH, powerMah);
proto.end(token);
}
static class BatteryUsage {
static final int BATTERY_USAGE_INDEX_UNSPECIFIED = PROCESS_STATE_UNSPECIFIED;
static final int BATTERY_USAGE_INDEX_FOREGROUND = PROCESS_STATE_FOREGROUND;
static final int BATTERY_USAGE_INDEX_BACKGROUND = PROCESS_STATE_BACKGROUND;
static final int BATTERY_USAGE_INDEX_FOREGROUND_SERVICE = PROCESS_STATE_FOREGROUND_SERVICE;
static final int BATTERY_USAGE_INDEX_CACHED = PROCESS_STATE_CACHED;
static final int BATTERY_USAGE_COUNT = PROCESS_STATE_COUNT;
static final Dimensions[] BATT_DIMENS = new Dimensions[] {
new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
PROCESS_STATE_UNSPECIFIED),
new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
PROCESS_STATE_FOREGROUND),
new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
PROCESS_STATE_BACKGROUND),
new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
PROCESS_STATE_FOREGROUND_SERVICE),
new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS,
PROCESS_STATE_CACHED),
};
@NonNull double[] mUsage;
@Nullable double[] mPercentage;
BatteryUsage() {
this(0.0d, 0.0d, 0.0d, 0.0d, 0.0d);
}
BatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage, double fgsUsage,
double cachedUsage) {
mUsage = new double[] {unspecifiedUsage, fgUsage, bgUsage, fgsUsage, cachedUsage};
}
BatteryUsage(@NonNull double[] usage) {
mUsage = usage;
}
BatteryUsage(@NonNull BatteryUsage other, double scale) {
this(other);
scaleInternal(scale);
}
BatteryUsage(@NonNull BatteryUsage other) {
mUsage = new double[other.mUsage.length];
setToInternal(other);
}
BatteryUsage(@NonNull UidBatteryConsumer consumer, @NonNull AppBatteryPolicy policy) {
final Dimensions[] dims = policy.mBatteryDimensions;
mUsage = new double[] {
getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_UNSPECIFIED]),
getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND]),
getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_BACKGROUND]),
getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]),
getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_CACHED]),
};
}
BatteryUsage setTo(@NonNull BatteryUsage other) {
return setToInternal(other);
}
private BatteryUsage setToInternal(@NonNull BatteryUsage other) {
System.arraycopy(other.mUsage, 0, mUsage, 0, other.mUsage.length);
if (other.mPercentage != null) {
mPercentage = new double[other.mPercentage.length];
System.arraycopy(other.mPercentage, 0, mPercentage, 0, other.mPercentage.length);
} else {
mPercentage = null;
}
return this;
}
BatteryUsage add(@NonNull BatteryUsage other) {
for (int i = 0; i < other.mUsage.length; i++) {
mUsage[i] += other.mUsage[i];
}
return this;
}
BatteryUsage subtract(@NonNull BatteryUsage other) {
for (int i = 0; i < other.mUsage.length; i++) {
mUsage[i] = Math.max(0.0d, mUsage[i] - other.mUsage[i]);
}
return this;
}
BatteryUsage scale(double scale) {
return scaleInternal(scale);
}
private BatteryUsage scaleInternal(double scale) {
for (int i = 0; i < mUsage.length; i++) {
mUsage[i] *= scale;
}
return this;
}
ImmutableBatteryUsage unmutate() {
return new ImmutableBatteryUsage(this);
}
BatteryUsage calcPercentage(int uid, @NonNull AppBatteryPolicy policy) {
if (mPercentage == null || mPercentage.length != mUsage.length) {
mPercentage = new double[mUsage.length];
}
policy.calcPercentage(uid, mUsage, mPercentage);
return this;
}
BatteryUsage setPercentage(@NonNull double[] percentage) {
mPercentage = percentage;
return this;
}
double[] getPercentage() {
return mPercentage;
}
String percentageToString() {
return formatBatteryUsagePercentage(mPercentage);
}
@Override
public String toString() {
return formatBatteryUsage(mUsage);
}
double getUsagePowerMah(@BatteryConsumer.ProcessState int processState) {
switch (processState) {
case PROCESS_STATE_FOREGROUND:
return mUsage[BATTERY_USAGE_INDEX_FOREGROUND];
case PROCESS_STATE_BACKGROUND:
return mUsage[BATTERY_USAGE_INDEX_BACKGROUND];
case PROCESS_STATE_FOREGROUND_SERVICE:
return mUsage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE];
case PROCESS_STATE_CACHED:
return mUsage[BATTERY_USAGE_INDEX_CACHED];
}
return 0;
}
boolean isValid() {
for (int i = 0; i < mUsage.length; i++) {
if (mUsage[i] < 0.0d) {
return false;
}
}
return true;
}
boolean isEmpty() {
for (int i = 0; i < mUsage.length; i++) {
if (mUsage[i] > 0.0d) {
return false;
}
}
return true;
}
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
final BatteryUsage otherUsage = (BatteryUsage) other;
for (int i = 0; i < mUsage.length; i++) {
if (Double.compare(mUsage[i], otherUsage.mUsage[i]) != 0) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hashCode = 0;
for (int i = 0; i < mUsage.length; i++) {
hashCode = Double.hashCode(mUsage[i]) + hashCode * 31;
}
return hashCode;
}
private static String formatBatteryUsage(double[] usage) {
return String.format("%.3f %.3f %.3f %.3f %.3f mAh",
usage[BATTERY_USAGE_INDEX_UNSPECIFIED],
usage[BATTERY_USAGE_INDEX_FOREGROUND],
usage[BATTERY_USAGE_INDEX_BACKGROUND],
usage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE],
usage[BATTERY_USAGE_INDEX_CACHED]);
}
static String formatBatteryUsagePercentage(double[] percentage) {
return String.format("%4.2f%% %4.2f%% %4.2f%% %4.2f%% %4.2f%%",
percentage[BATTERY_USAGE_INDEX_UNSPECIFIED],
percentage[BATTERY_USAGE_INDEX_FOREGROUND],
percentage[BATTERY_USAGE_INDEX_BACKGROUND],
percentage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE],
percentage[BATTERY_USAGE_INDEX_CACHED]);
}
private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
final Dimensions dimens) {
try {
return uidConsumer.getConsumedPower(dimens);
} catch (IllegalArgumentException e) {
return 0.0d;
}
}
}
static final class ImmutableBatteryUsage extends BatteryUsage {
ImmutableBatteryUsage() {
super();
}
ImmutableBatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage,
double fgsUsage, double cachedUsage) {
super(unspecifiedUsage, fgUsage, bgUsage, fgsUsage, cachedUsage);
}
ImmutableBatteryUsage(@NonNull double[] usage) {
super(usage);
}
ImmutableBatteryUsage(@NonNull BatteryUsage other, double scale) {
super(other, scale);
}
ImmutableBatteryUsage(@NonNull BatteryUsage other) {
super(other);
}
ImmutableBatteryUsage(@NonNull UidBatteryConsumer consumer,
@NonNull AppBatteryPolicy policy) {
super(consumer, policy);
}
@Override
BatteryUsage setTo(@NonNull BatteryUsage other) {
throw new RuntimeException("Readonly");
}
@Override
BatteryUsage add(@NonNull BatteryUsage other) {
throw new RuntimeException("Readonly");
}
@Override
BatteryUsage subtract(@NonNull BatteryUsage other) {
throw new RuntimeException("Readonly");
}
@Override
BatteryUsage scale(double scale) {
throw new RuntimeException("Readonly");
}
@Override
BatteryUsage setPercentage(@NonNull double[] percentage) {
throw new RuntimeException("Readonly");
}
BatteryUsage mutate() {
return new BatteryUsage(this);
}
}
static final class AppBatteryPolicy extends BaseAppStatePolicy<AppBatteryTracker> {
/**
* The type of battery usage we could choose to apply the policy on.
*
* Must be in sync with android.os.BatteryConsumer.PROCESS_STATE_*.
*/
static final int BATTERY_USAGE_TYPE_UNSPECIFIED = 1;
static final int BATTERY_USAGE_TYPE_FOREGROUND = 1 << 1;
static final int BATTERY_USAGE_TYPE_BACKGROUND = 1 << 2;
static final int BATTERY_USAGE_TYPE_FOREGROUND_SERVICE = 1 << 3;
static final int BATTERY_USAGE_TYPE_CACHED = 1 << 4;
/**
* Whether or not we should enable the monitoring on background current drains.
*/
static final String KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_monitor_enabled";
/**
* The threshold of the background current drain (in percentage) to the restricted
* standby bucket. In conjunction with the {@link #KEY_BG_CURRENT_DRAIN_WINDOW},
* the app could be moved to more restricted standby bucket when its background current
* drain rate is over this limit.
*/
static final String KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_threshold_to_restricted_bucket";
/**
* The threshold of the background current drain (in percentage) to the background
* restricted level. In conjunction with the {@link #KEY_BG_CURRENT_DRAIN_WINDOW},
* the app could be moved to more restricted level when its background current
* drain rate is over this limit.
*/
static final String KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_threshold_to_bg_restricted";
/**
* The background current drain window size. In conjunction with the
* {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, the app could be moved to
* more restrictive bucket when its background current drain rate is over this limit.
*/
static final String KEY_BG_CURRENT_DRAIN_WINDOW =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window";
/**
* The grace period after an interaction event with the app, if the background current
* drain goes beyond the threshold within that period, the system won't apply the
* restrictions.
*/
static final String KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_interaction_grace_period";
/**
* Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher
* value for the legitimate cases with higher background current drain.
*/
static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+ "current_drain_high_threshold_to_restricted_bucket";
/**
* Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED}, but a higher value
* for the legitimate cases with higher background current drain.
*/
static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_to_bg_restricted";
/**
* The threshold of minimal time of hosting a foreground service with type "mediaPlayback"
* or a media session, over the given window, so it'd subject towards the higher
* background current drain threshold as defined in
* {@link #mBgCurrentDrainBgRestrictedHighThreshold}.
*/
static final String KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_media_playback_min_duration";
/**
* Similar to {@link #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION} but for foreground
* service with type "location".
*/
static final String KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_location_min_duration";
/**
* Whether or not we should enable the different threshold based on the durations of
* certain event type.
*/
static final String KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+ "current_drain_event_duration_based_threshold_enabled";
/**
* Whether or not we should move the app into the restricted bucket level if its background
* battery usage goes beyond the threshold. Note this different from the flag
* {@link AppRestrictionController.ConstantsObserver#KEY_BG_AUTO_RESTRICT_ABUSIVE_APPS}
* which is to control the overall auto bg restrictions.
*/
static final String KEY_BG_CURRENT_DRAIN_AUTO_RESTRICT_ABUSIVE_APPS_ENABLED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+ "current_drain_auto_restrict_abusive_apps_enabled";
/**
* The types of battery drain we're checking on each app; if the sum of the battery drain
* exceeds the threshold, it'll be moved to restricted standby bucket; the type here
* must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND},
* {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE} and {@link #BATTERY_USAGE_TYPE_CACHED}.
*/
static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_restricted_bucket";
/**
* The types of battery drain we're checking on each app; if the sum of the battery drain
* exceeds the threshold, it'll be moved to background restricted level; the type here
* must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND},
* {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE} and {@link #BATTERY_USAGE_TYPE_CACHED}.
*/
static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_bg_restricted";
/**
* The power usage components we're monitoring.
*/
static final String KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_power_components";
/**
* The types of state where we'll exempt its battery usage when it's in that state.
* The state here must be one or a combination of STATE_TYPE_* in BaseAppStateTracker.
*/
static final String KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_exempted_types";
/**
* The behavior when an app has the permission ACCESS_BACKGROUND_LOCATION granted,
* whether or not the system will use a higher threshold towards its background battery
* usage because of it.
*/
static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_by_bg_location";
/**
* Whether or not the battery usage of the offending app should fulfill the 1st threshold
* before taking actions for the 2nd threshold.
*/
static final String KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_decouple_thresholds";
/**
* Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
*/
final float mDefaultBgCurrentDrainRestrictedBucket;
/**
* Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainBgRestrictedThreshold}.
*/
final float mDefaultBgCurrentDrainBgRestrictedThreshold;
/**
* Default value to {@link #mBgCurrentDrainWindowMs}.
*/
final long mDefaultBgCurrentDrainWindowMs;
/**
* Default value to {@link #mBgCurrentDrainInteractionGracePeriodMs}.
*/
final long mDefaultBgCurrentDrainInteractionGracePeriodMs;
/**
* Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
*/
final float mDefaultBgCurrentDrainRestrictedBucketHighThreshold;
/**
* Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
* the {@link #mBgCurrentDrainBgRestrictedThreshold}.
*/
final float mDefaultBgCurrentDrainBgRestrictedHighThreshold;
/**
* Default value to {@link #mBgCurrentDrainMediaPlaybackMinDuration}.
*/
final long mDefaultBgCurrentDrainMediaPlaybackMinDuration;
/**
* Default value to {@link #mBgCurrentDrainLocationMinDuration}.
*/
final long mDefaultBgCurrentDrainLocationMinDuration;
/**
* Default value to {@link #mBgCurrentDrainEventDurationBasedThresholdEnabled}.
*/
final boolean mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled;
/**
* Default value to {@link #mBgCurrentDrainAutoRestrictAbusiveAppsEnabled}.
*/
final boolean mDefaultBgCurrentDrainAutoRestrictAbusiveAppsEnabled;
/**
* Default value to {@link #mBgCurrentDrainRestrictedBucketTypes}.
*/
final int mDefaultCurrentDrainTypesToRestrictedBucket;
/**
* Default value to {@link #mBgCurrentDrainBgRestrictedTypes}.
*/
final int mDefaultBgCurrentDrainTypesToBgRestricted;
/**
* Default value to {@link #mBgCurrentDrainPowerComponents}.
**/
@BatteryConsumer.PowerComponent
static final int DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS = POWER_COMPONENT_ANY;
final int mDefaultBgCurrentDrainPowerComponent;
/**
* Default value to {@link #mBgCurrentDrainExemptedTypes}.
**/
final int mDefaultBgCurrentDrainExemptedTypes;
/**
* Default value to {@link #mBgCurrentDrainHighThresholdByBgLocation}.
*/
final boolean mDefaultBgCurrentDrainHighThresholdByBgLocation;
/**
* Default value to {@link #mBgCurrentDrainDecoupleThresholds}.
*/
static final boolean DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD = true;
/**
* The index to {@link #mBgCurrentDrainRestrictedBucketThreshold}
* and {@link #mBgCurrentDrainBgRestrictedThreshold}.
*/
static final int INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD = 0;
static final int INDEX_HIGH_CURRENT_DRAIN_THRESHOLD = 1;
/**
* @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET.
* @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET.
*/
volatile float[] mBgCurrentDrainRestrictedBucketThreshold = new float[2];
/**
* @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED.
* @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED.
*/
volatile float[] mBgCurrentDrainBgRestrictedThreshold = new float[2];
/**
* @see #KEY_BG_CURRENT_DRAIN_WINDOW.
*/
volatile long mBgCurrentDrainWindowMs;
/**
* @see #KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD.
*/
volatile long mBgCurrentDrainInteractionGracePeriodMs;
/**
* @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
*/
volatile long mBgCurrentDrainMediaPlaybackMinDuration;
/**
* @see #KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION.
*/
volatile long mBgCurrentDrainLocationMinDuration;
/**
* @see #KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED.
*/
volatile boolean mBgCurrentDrainEventDurationBasedThresholdEnabled;
/**
* @see #KEY_BG_CURRENT_DRAIN_AUTO_RESTRICT_ABUSIVE_APPS_ENABLED.
*/
volatile boolean mBgCurrentDrainAutoRestrictAbusiveAppsEnabled;
/**
* @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET.
*/
volatile int mBgCurrentDrainRestrictedBucketTypes;
/**
* @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED.
*/
volatile int mBgCurrentDrainBgRestrictedTypes;
/**
* @see #KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS.
*/
@BatteryConsumer.PowerComponent
volatile int mBgCurrentDrainPowerComponents;
volatile Dimensions[] mBatteryDimensions;
/**
* @see #KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES.
*/
volatile int mBgCurrentDrainExemptedTypes;
/**
* @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION.
*/
volatile boolean mBgCurrentDrainHighThresholdByBgLocation;
/**
* @see #KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS.
*/
volatile boolean mBgCurrentDrainDecoupleThresholds;
/**
* The capacity of the battery when fully charged in mAh.
*/
private int mBatteryFullChargeMah;
/**
* List of the packages with significant background battery usage, key is the UID of
* the package and value is the pair of {timestamp[], battery usage snapshot[]}
* when the UID is found guilty and should be moved to the next level of restriction.
*/
@GuardedBy("mLock")
private final SparseArray<Pair<long[], ImmutableBatteryUsage[]>> mHighBgBatteryPackages =
new SparseArray<>();
/**
* The timestamp of the last interaction, key is the UID.
*/
@GuardedBy("mLock")
private final SparseLongArray mLastInteractionTime = new SparseLongArray();
@NonNull
private final Object mLock;
private static final int TIME_STAMP_INDEX_RESTRICTED_BUCKET = 0;
private static final int TIME_STAMP_INDEX_BG_RESTRICTED = 1;
private static final int TIME_STAMP_INDEX_LAST = 2;
AppBatteryPolicy(@NonNull Injector injector, @NonNull AppBatteryTracker tracker) {
super(injector, tracker, KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
tracker.mContext.getResources()
.getBoolean(R.bool.config_bg_current_drain_monitor_enabled));
mLock = tracker.mLock;
final Resources resources = tracker.mContext.getResources();
float[] val = getFloatArray(resources.obtainTypedArray(
R.array.config_bg_current_drain_threshold_to_restricted_bucket));
mDefaultBgCurrentDrainRestrictedBucket =
isLowRamDeviceStatic() ? val[1] : val[0];
val = getFloatArray(resources.obtainTypedArray(
R.array.config_bg_current_drain_threshold_to_bg_restricted));
mDefaultBgCurrentDrainBgRestrictedThreshold =
isLowRamDeviceStatic() ? val[1] : val[0];
mDefaultBgCurrentDrainWindowMs = resources.getInteger(
R.integer.config_bg_current_drain_window) * 1_000;
mDefaultBgCurrentDrainInteractionGracePeriodMs = mDefaultBgCurrentDrainWindowMs;
val = getFloatArray(resources.obtainTypedArray(
R.array.config_bg_current_drain_high_threshold_to_restricted_bucket));
mDefaultBgCurrentDrainRestrictedBucketHighThreshold =
isLowRamDeviceStatic() ? val[1] : val[0];
val = getFloatArray(resources.obtainTypedArray(
R.array.config_bg_current_drain_high_threshold_to_bg_restricted));
mDefaultBgCurrentDrainBgRestrictedHighThreshold =
isLowRamDeviceStatic() ? val[1] : val[0];
mDefaultBgCurrentDrainMediaPlaybackMinDuration = resources.getInteger(
R.integer.config_bg_current_drain_media_playback_min_duration) * 1_000;
mDefaultBgCurrentDrainLocationMinDuration = resources.getInteger(
R.integer.config_bg_current_drain_location_min_duration) * 1_000;
mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled = resources.getBoolean(
R.bool.config_bg_current_drain_event_duration_based_threshold_enabled);
mDefaultBgCurrentDrainAutoRestrictAbusiveAppsEnabled = resources.getBoolean(
R.bool.config_bg_current_drain_auto_restrict_abusive_apps);
mDefaultCurrentDrainTypesToRestrictedBucket = resources.getInteger(
R.integer.config_bg_current_drain_types_to_restricted_bucket);
mDefaultBgCurrentDrainTypesToBgRestricted = resources.getInteger(
R.integer.config_bg_current_drain_types_to_bg_restricted);
mDefaultBgCurrentDrainPowerComponent = resources.getInteger(
R.integer.config_bg_current_drain_power_components);
mDefaultBgCurrentDrainExemptedTypes = resources.getInteger(
R.integer.config_bg_current_drain_exempted_types);
mDefaultBgCurrentDrainHighThresholdByBgLocation = resources.getBoolean(
R.bool.config_bg_current_drain_high_threshold_by_bg_location);
mBgCurrentDrainRestrictedBucketThreshold[0] =
mDefaultBgCurrentDrainRestrictedBucket;
mBgCurrentDrainRestrictedBucketThreshold[1] =
mDefaultBgCurrentDrainRestrictedBucketHighThreshold;
mBgCurrentDrainBgRestrictedThreshold[0] =
mDefaultBgCurrentDrainBgRestrictedThreshold;
mBgCurrentDrainBgRestrictedThreshold[1] =
mDefaultBgCurrentDrainBgRestrictedHighThreshold;
mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs;
mBgCurrentDrainInteractionGracePeriodMs =
mDefaultBgCurrentDrainInteractionGracePeriodMs;
mBgCurrentDrainMediaPlaybackMinDuration =
mDefaultBgCurrentDrainMediaPlaybackMinDuration;
mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration;
}
static float[] getFloatArray(TypedArray array) {
int length = array.length();
float[] floatArray = new float[length];
for (int i = 0; i < length; i++) {
floatArray[i] = array.getFloat(i, Float.NaN);
}
array.recycle();
return floatArray;
}
@Override
public void onPropertiesChanged(String name) {
switch (name) {
case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET:
case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED:
case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION:
case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET:
case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED:
case KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET:
case KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED:
case KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS:
updateCurrentDrainThreshold();
break;
case KEY_BG_CURRENT_DRAIN_AUTO_RESTRICT_ABUSIVE_APPS_ENABLED:
updateBgCurrentDrainAutoRestrictAbusiveAppsEnabled();
break;
case KEY_BG_CURRENT_DRAIN_WINDOW:
updateCurrentDrainWindow();
break;
case KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD:
updateCurrentDrainInteractionGracePeriod();
break;
case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION:
updateCurrentDrainMediaPlaybackMinDuration();
break;
case KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION:
updateCurrentDrainLocationMinDuration();
break;
case KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED:
updateCurrentDrainEventDurationBasedThresholdEnabled();
break;
case KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES:
updateCurrentDrainExemptedTypes();
break;
case KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS:
updateCurrentDrainDecoupleThresholds();
break;
default:
super.onPropertiesChanged(name);
break;
}
}
void updateTrackerEnabled() {
if (mBatteryFullChargeMah > 0) {
super.updateTrackerEnabled();
} else {
mTrackerEnabled = false;
onTrackerEnabled(false);
}
}
public void onTrackerEnabled(boolean enabled) {
mTracker.onCurrentDrainMonitorEnabled(enabled);
}
private void updateCurrentDrainThreshold() {
mBgCurrentDrainRestrictedBucketThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
mDefaultBgCurrentDrainRestrictedBucket);
mBgCurrentDrainRestrictedBucketThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
mDefaultBgCurrentDrainRestrictedBucketHighThreshold);
mBgCurrentDrainBgRestrictedThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
mDefaultBgCurrentDrainBgRestrictedThreshold);
mBgCurrentDrainBgRestrictedThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
mDefaultBgCurrentDrainBgRestrictedHighThreshold);
mBgCurrentDrainRestrictedBucketTypes =
DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET,
mDefaultCurrentDrainTypesToRestrictedBucket);
mBgCurrentDrainBgRestrictedTypes =
DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED,
mDefaultBgCurrentDrainTypesToBgRestricted);
mBgCurrentDrainPowerComponents =
DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS,
mDefaultBgCurrentDrainPowerComponent);
if (mBgCurrentDrainPowerComponents == DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS) {
mBatteryDimensions = BatteryUsage.BATT_DIMENS;
} else {
mBatteryDimensions = new Dimensions[BatteryUsage.BATTERY_USAGE_COUNT];
for (int i = 0; i < BatteryUsage.BATTERY_USAGE_COUNT; i++) {
mBatteryDimensions[i] = new Dimensions(mBgCurrentDrainPowerComponents, i);
}
}
mBgCurrentDrainHighThresholdByBgLocation =
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION,
mDefaultBgCurrentDrainHighThresholdByBgLocation);
}
private void updateCurrentDrainWindow() {
mBgCurrentDrainWindowMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_WINDOW,
mDefaultBgCurrentDrainWindowMs);
}
private void updateCurrentDrainInteractionGracePeriod() {
mBgCurrentDrainInteractionGracePeriodMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
mDefaultBgCurrentDrainInteractionGracePeriodMs);
}
private void updateCurrentDrainMediaPlaybackMinDuration() {
mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
mDefaultBgCurrentDrainMediaPlaybackMinDuration);
}
private void updateCurrentDrainLocationMinDuration() {
mBgCurrentDrainLocationMinDuration = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
mDefaultBgCurrentDrainLocationMinDuration);
}
private void updateCurrentDrainEventDurationBasedThresholdEnabled() {
mBgCurrentDrainEventDurationBasedThresholdEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled);
}
private void updateCurrentDrainExemptedTypes() {
mBgCurrentDrainExemptedTypes = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES,
mDefaultBgCurrentDrainExemptedTypes);
}
private void updateCurrentDrainDecoupleThresholds() {
mBgCurrentDrainDecoupleThresholds = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS,
DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD);
}
private void updateBgCurrentDrainAutoRestrictAbusiveAppsEnabled() {
mBgCurrentDrainAutoRestrictAbusiveAppsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_CURRENT_DRAIN_AUTO_RESTRICT_ABUSIVE_APPS_ENABLED,
mDefaultBgCurrentDrainAutoRestrictAbusiveAppsEnabled);
}
@Override
public void onSystemReady() {
mBatteryFullChargeMah =
mInjector.getBatteryManagerInternal().getBatteryFullCharge() / 1000;
super.onSystemReady();
updateCurrentDrainThreshold();
updateCurrentDrainWindow();
updateCurrentDrainInteractionGracePeriod();
updateCurrentDrainMediaPlaybackMinDuration();
updateCurrentDrainLocationMinDuration();
updateCurrentDrainEventDurationBasedThresholdEnabled();
updateCurrentDrainExemptedTypes();
updateCurrentDrainDecoupleThresholds();
updateBgCurrentDrainAutoRestrictAbusiveAppsEnabled();
}
@Override
@RestrictionLevel
public int getProposedRestrictionLevel(String packageName, int uid,
@RestrictionLevel int maxLevel) {
if (maxLevel <= RESTRICTION_LEVEL_ADAPTIVE_BUCKET) {
return RESTRICTION_LEVEL_UNKNOWN;
}
synchronized (mLock) {
final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid);
if (pair != null) {
final long lastInteractionTime = mLastInteractionTime.get(uid, 0L);
final long[] ts = pair.first;
final boolean noInteractionRecently = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
> (lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs);
final boolean canRestrict =
mTracker.mAppRestrictionController.isAutoRestrictAbusiveAppEnabled()
&& mBgCurrentDrainAutoRestrictAbusiveAppsEnabled;
final int restrictedLevel = noInteractionRecently && canRestrict
? RESTRICTION_LEVEL_RESTRICTED_BUCKET
: RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
if (maxLevel > RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
return ts[TIME_STAMP_INDEX_BG_RESTRICTED] > 0
? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED : restrictedLevel;
} else if (maxLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
return restrictedLevel;
}
}
return RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
}
}
double[] calcPercentage(final int uid, final double[] usage, double[] percentage) {
final BatteryUsage debugUsage = uid > 0 ? mTracker.mDebugUidPercentages.get(uid) : null;
final double[] forced = debugUsage != null ? debugUsage.getPercentage() : null;
for (int i = 0; i < usage.length; i++) {
percentage[i] = forced != null ? forced[i] : usage[i] / mBatteryFullChargeMah * 100;
}
return percentage;
}
private double sumPercentageOfTypes(double[] percentage, int types) {
double result = 0.0d;
for (int type = Integer.highestOneBit(types); type != 0;
type = Integer.highestOneBit(types)) {
final int index = Integer.numberOfTrailingZeros(type);
result += percentage[index];
types &= ~type;
}
return result;
}
private static String batteryUsageTypesToString(int types) {
final StringBuilder sb = new StringBuilder("[");
boolean needDelimiter = false;
for (int type = Integer.highestOneBit(types); type != 0;
type = Integer.highestOneBit(types)) {
if (needDelimiter) {
sb.append('|');
}
needDelimiter = true;
switch (type) {
case BATTERY_USAGE_TYPE_UNSPECIFIED:
sb.append("UNSPECIFIED");
break;
case BATTERY_USAGE_TYPE_FOREGROUND:
sb.append("FOREGROUND");
break;
case BATTERY_USAGE_TYPE_BACKGROUND:
sb.append("BACKGROUND");
break;
case BATTERY_USAGE_TYPE_FOREGROUND_SERVICE:
sb.append("FOREGROUND_SERVICE");
break;
case BATTERY_USAGE_TYPE_CACHED:
sb.append("CACHED");
break;
default:
return "[UNKNOWN(" + Integer.toHexString(types) + ")]";
}
types &= ~type;
}
sb.append("]");
return sb.toString();
}
void handleUidBatteryUsage(final int uid, final ImmutableBatteryUsage usage) {
final @ReasonCode int reason = shouldExemptUid(uid);
if (reason != REASON_DENIED) {
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE && !BATTERY_USAGE_NONE.equals(usage)) {
Slog.i(TAG, "Exempting battery usage in " + UserHandle.formatUid(uid)
+ " " + PowerExemptionManager.reasonCodeToString(reason));
}
return;
}
boolean notifyController = false;
boolean excessive = false;
int index = 0;
final double rbPercentage = sumPercentageOfTypes(usage.getPercentage(),
mBgCurrentDrainRestrictedBucketTypes);
final double brPercentage = sumPercentageOfTypes(usage.getPercentage(),
mBgCurrentDrainBgRestrictedTypes);
synchronized (mLock) {
final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(uid);
if (curLevel >= RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
// We're already in the background restricted level, nothing more we could do.
return;
}
final long lastInteractionTime = mLastInteractionTime.get(uid, 0L);
final long now = SystemClock.elapsedRealtime();
final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
mBgCurrentDrainWindowMs);
index = mHighBgBatteryPackages.indexOfKey(uid);
final boolean decoupleThresholds = mBgCurrentDrainDecoupleThresholds;
final double rbThreshold = mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex];
final double brThreshold = mBgCurrentDrainBgRestrictedThreshold[thresholdIndex];
if (index < 0) {
long[] ts = null;
ImmutableBatteryUsage[] usages = null;
if (rbPercentage >= rbThreshold) {
if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) {
// New findings to us, track it and let the controller know.
ts = new long[TIME_STAMP_INDEX_LAST];
ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
// It's beeen long enough since last interaction with this app.
notifyController = true;
}
excessive = true;
}
if (decoupleThresholds && brPercentage >= brThreshold) {
if (ts == null) {
ts = new long[TIME_STAMP_INDEX_LAST];
usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
}
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
usages[TIME_STAMP_INDEX_BG_RESTRICTED] = usage;
notifyController = excessive = true;
}
} else {
final Pair<long[], ImmutableBatteryUsage[]> pair =
mHighBgBatteryPackages.valueAt(index);
final long[] ts = pair.first;
final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET];
if (rbPercentage >= rbThreshold) {
if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) {
if (lastRestrictBucketTs == 0) {
ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
}
// It's been long enough since last interaction with this app.
notifyController = true;
}
excessive = true;
} else {
// It's actually back to normal, but we don't untrack it until
// explicit user interactions, because the restriction could be the cause
// of going back to normal.
}
if (brPercentage >= brThreshold) {
// If either
// a) It's configured to goto threshold 2 directly without threshold 1;
// b) It's already in the restricted standby bucket, but still seeing
// high current drains, and it's been a while since it's restricted;
// tell the controller.
notifyController = decoupleThresholds
|| (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
&& (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs));
if (notifyController) {
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = usage;
}
excessive = true;
} else {
// Reset the track now - if it's already background restricted, it requires
// user consent to unrestrict it; or if it's in restricted bucket level,
// resetting this won't lift it from that level.
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null;
// Now need to notify the controller.
}
}
}
if (excessive) {
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE) {
Slog.i(TAG, "Excessive background current drain " + uid + " "
+ usage + " (" + usage.percentageToString() + " ) over "
+ TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
}
if (notifyController) {
mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(
uid, REASON_MAIN_FORCED_BY_SYSTEM,
REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, true);
}
} else {
if (DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE && index >= 0) {
Slog.i(TAG, "Background current drain backs to normal " + uid + " "
+ usage + " (" + usage.percentageToString() + " ) over "
+ TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
}
// For now, we're not lifting the restrictions if the bg current drain backs to
// normal util an explicit user interaction.
}
}
private int getCurrentDrainThresholdIndex(int uid, long now, long window) {
return (hasMediaPlayback(uid, now, window) || hasLocation(uid, now, window))
? INDEX_HIGH_CURRENT_DRAIN_THRESHOLD
: INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD;
}
private boolean hasMediaPlayback(int uid, long now, long window) {
return mBgCurrentDrainEventDurationBasedThresholdEnabled
&& mTracker.mAppRestrictionController.getCompositeMediaPlaybackDurations(
uid, now, window) >= mBgCurrentDrainMediaPlaybackMinDuration;
}
private boolean hasLocation(int uid, long now, long window) {
if (!mBgCurrentDrainHighThresholdByBgLocation) {
return false;
}
if (mTracker.mContext.checkPermission(ACCESS_BACKGROUND_LOCATION,
Process.INVALID_PID, uid) == PERMISSION_GRANTED) {
return true;
}
if (!mBgCurrentDrainEventDurationBasedThresholdEnabled) {
return false;
}
final long since = Math.max(0, now - window);
final AppRestrictionController controller = mTracker.mAppRestrictionController;
final long locationDuration = controller.getForegroundServiceTotalDurationsSince(
uid, since, now, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
return locationDuration >= mBgCurrentDrainLocationMinDuration;
}
void onUserInteractionStarted(String packageName, int uid) {
boolean changed = false;
synchronized (mLock) {
mLastInteractionTime.put(uid, SystemClock.elapsedRealtime());
final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(
uid, packageName);
if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
// It's a sticky state, user interaction won't change it, still track it.
} else {
// Remove the given UID from our tracking list, as user interacted with it.
final int index = mHighBgBatteryPackages.indexOfKey(uid);
if (index >= 0) {
mHighBgBatteryPackages.removeAt(index);
changed = true;
}
}
}
if (changed) {
// Request to refresh the app restriction level.
mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(uid,
REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION, true);
}
}
void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
if (restricted) {
return;
}
synchronized (mLock) {
// User has explicitly removed it from background restricted level,
// clear the timestamp of the background-restricted
final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid);
if (pair != null) {
pair.first[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null;
}
}
}
@VisibleForTesting
void reset() {
mHighBgBatteryPackages.clear();
mLastInteractionTime.clear();
mTracker.reset();
}
@GuardedBy("mLock")
void onUserRemovedLocked(final @UserIdInt int userId) {
for (int i = mHighBgBatteryPackages.size() - 1; i >= 0; i--) {
if (UserHandle.getUserId(mHighBgBatteryPackages.keyAt(i)) == userId) {
mHighBgBatteryPackages.removeAt(i);
}
}
for (int i = mLastInteractionTime.size() - 1; i >= 0; i--) {
if (UserHandle.getUserId(mLastInteractionTime.keyAt(i)) == userId) {
mLastInteractionTime.removeAt(i);
}
}
}
@GuardedBy("mLock")
void onUidRemovedLocked(final int uid) {
mHighBgBatteryPackages.remove(uid);
mLastInteractionTime.delete(uid);
}
@Override
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("APP BATTERY TRACKER POLICY SETTINGS:");
final String indent = " ";
prefix = indent + prefix;
super.dump(pw, prefix);
if (isEnabled()) {
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET);
pw.print('=');
pw.println(mBgCurrentDrainRestrictedBucketThreshold[
INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD]);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET);
pw.print('=');
pw.println(mBgCurrentDrainRestrictedBucketThreshold[
INDEX_HIGH_CURRENT_DRAIN_THRESHOLD]);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED);
pw.print('=');
pw.println(mBgCurrentDrainBgRestrictedThreshold[
INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD]);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED);
pw.print('=');
pw.println(mBgCurrentDrainBgRestrictedThreshold[
INDEX_HIGH_CURRENT_DRAIN_THRESHOLD]);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_WINDOW);
pw.print('=');
pw.println(mBgCurrentDrainWindowMs);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD);
pw.print('=');
pw.println(mBgCurrentDrainInteractionGracePeriodMs);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
pw.print('=');
pw.println(mBgCurrentDrainMediaPlaybackMinDuration);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
pw.print('=');
pw.println(mBgCurrentDrainLocationMinDuration);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
pw.print('=');
pw.println(mBgCurrentDrainEventDurationBasedThresholdEnabled);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_AUTO_RESTRICT_ABUSIVE_APPS_ENABLED);
pw.print('=');
pw.println(mBgCurrentDrainAutoRestrictAbusiveAppsEnabled);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET);
pw.print('=');
pw.println(batteryUsageTypesToString(mBgCurrentDrainRestrictedBucketTypes));
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED);
pw.print('=');
pw.println(batteryUsageTypesToString(mBgCurrentDrainBgRestrictedTypes));
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS);
pw.print('=');
pw.println(mBgCurrentDrainPowerComponents);
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES);
pw.print('=');
pw.println(BaseAppStateTracker.stateTypesToString(mBgCurrentDrainExemptedTypes));
pw.print(prefix);
pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION);
pw.print('=');
pw.println(mBgCurrentDrainHighThresholdByBgLocation);
pw.print(prefix);
pw.print("Full charge capacity=");
pw.print(mBatteryFullChargeMah);
pw.println(" mAh");
pw.print(prefix);
pw.println("Excessive current drain detected:");
synchronized (mLock) {
final int size = mHighBgBatteryPackages.size();
prefix = indent + prefix;
if (size > 0) {
final long now = SystemClock.elapsedRealtime();
for (int i = 0; i < size; i++) {
final int uid = mHighBgBatteryPackages.keyAt(i);
final Pair<long[], ImmutableBatteryUsage[]> pair =
mHighBgBatteryPackages.valueAt(i);
final long[] ts = pair.first;
final ImmutableBatteryUsage[] usages = pair.second;
final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
mBgCurrentDrainWindowMs);
pw.format("%s%s: (threshold=%4.2f%%/%4.2f%%) %s / %s\n",
prefix,
UserHandle.formatUid(uid),
mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex],
mBgCurrentDrainBgRestrictedThreshold[thresholdIndex],
formatHighBgBatteryRecord(
ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET], now,
usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET]),
formatHighBgBatteryRecord(
ts[TIME_STAMP_INDEX_BG_RESTRICTED], now,
usages[TIME_STAMP_INDEX_BG_RESTRICTED])
);
}
} else {
pw.print(prefix);
pw.println("(none)");
}
}
}
}
private String formatHighBgBatteryRecord(long ts, long now, ImmutableBatteryUsage usage) {
if (ts > 0 && usage != null) {
return String.format("%s %s (%s)",
formatTime(ts, now), usage.toString(), usage.percentageToString());
} else {
return "0";
}
}
}
}