| /* |
| * 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"; |
| } |
| } |
| } |
| } |