1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.am; 18 19 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 20 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 21 import static com.android.server.am.AppBatteryTracker.BATTERY_USAGE_NONE; 22 import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; 23 import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_NUM; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.content.Context; 28 import android.os.SystemClock; 29 import android.util.ArrayMap; 30 import android.util.Pair; 31 import android.util.Slog; 32 import android.util.SparseArray; 33 34 import com.android.internal.R; 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy; 38 import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates; 39 import com.android.server.am.AppBatteryTracker.AppBatteryPolicy; 40 import com.android.server.am.AppBatteryTracker.BatteryUsage; 41 import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage; 42 import com.android.server.am.AppRestrictionController.TrackerType; 43 import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; 44 import com.android.server.am.BaseAppStateTracker.Injector; 45 import com.android.server.am.BaseAppStateTracker.StateListener; 46 47 import java.io.PrintWriter; 48 import java.lang.reflect.Constructor; 49 import java.util.Iterator; 50 import java.util.LinkedList; 51 52 /** 53 * A helper class to track the current drains that should be excluded from the current drain 54 * accounting, examples are media playback, location sharing, etc. 55 * 56 * <p> 57 * Note: as the {@link AppBatteryTracker#getUidBatteryUsage} could return the battery usage data 58 * from most recent polling due to throttling, the battery usage of a certain event here 59 * would NOT be the exactly same amount that it actually costs. 60 * </p> 61 */ 62 final class AppBatteryExemptionTracker 63 extends BaseAppStateDurationsTracker<AppBatteryExemptionPolicy, UidBatteryStates> 64 implements BaseAppStateEvents.Factory<UidBatteryStates>, StateListener { 65 private static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryExemptionTracker" : TAG_AM; 66 67 private static final boolean DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER = false; 68 69 // As it's a UID-based tracker, anywhere which requires a package name, use this default name. 70 static final String DEFAULT_NAME = ""; 71 72 // As it's a UID-based tracker, while the state change event it receives could be 73 // in the combination of UID + package name, we'd have to leverage each package's state. 74 @GuardedBy("mLock") 75 private UidProcessMap<Integer> mUidPackageStates = new UidProcessMap<>(); 76 AppBatteryExemptionTracker(Context context, AppRestrictionController controller)77 AppBatteryExemptionTracker(Context context, AppRestrictionController controller) { 78 this(context, controller, null, null); 79 } 80 AppBatteryExemptionTracker(Context context, AppRestrictionController controller, Constructor<? extends Injector<AppBatteryExemptionPolicy>> injector, Object outerContext)81 AppBatteryExemptionTracker(Context context, AppRestrictionController controller, 82 Constructor<? extends Injector<AppBatteryExemptionPolicy>> injector, 83 Object outerContext) { 84 super(context, controller, injector, outerContext); 85 mInjector.setPolicy(new AppBatteryExemptionPolicy(mInjector, this)); 86 } 87 88 @Override getType()89 @TrackerType int getType() { 90 return AppRestrictionController.TRACKER_TYPE_BATTERY_EXEMPTION; 91 } 92 93 @Override onSystemReady()94 void onSystemReady() { 95 super.onSystemReady(); 96 mAppRestrictionController.forEachTracker(tracker -> { 97 tracker.registerStateListener(this); 98 }); 99 } 100 101 @Override createAppStateEvents(int uid, String packageName)102 public UidBatteryStates createAppStateEvents(int uid, String packageName) { 103 return new UidBatteryStates(uid, TAG, mInjector.getPolicy()); 104 } 105 106 @Override createAppStateEvents(UidBatteryStates other)107 public UidBatteryStates createAppStateEvents(UidBatteryStates other) { 108 return new UidBatteryStates(other); 109 } 110 111 @Override onStateChange(int uid, String packageName, boolean start, long now, int stateType)112 public void onStateChange(int uid, String packageName, boolean start, long now, int stateType) { 113 if (!mInjector.getPolicy().isEnabled()) { 114 return; 115 } 116 final ImmutableBatteryUsage batteryUsage = mAppRestrictionController 117 .getUidBatteryUsage(uid); 118 final int stateTypeIndex = stateTypeToIndex(stateType); 119 synchronized (mLock) { 120 final SparseArray<ArrayMap<String, Integer>> map = mUidPackageStates.getMap(); 121 ArrayMap<String, Integer> pkgsStates = map.get(uid); 122 if (pkgsStates == null) { 123 pkgsStates = new ArrayMap<>(); 124 map.put(uid, pkgsStates); 125 } 126 int states = 0; 127 int indexOfPkg = pkgsStates.indexOfKey(packageName); 128 if (indexOfPkg >= 0) { 129 states = pkgsStates.valueAt(indexOfPkg); 130 } else { 131 pkgsStates.put(packageName, 0); 132 indexOfPkg = pkgsStates.indexOfKey(packageName); 133 } 134 boolean addEvent = false; 135 if (start) { 136 // Check if there is another package within this UID with this type of event start. 137 boolean alreadyStarted = false; 138 for (int i = pkgsStates.size() - 1; i >= 0; i--) { 139 final int s = pkgsStates.valueAt(i); 140 if ((s & stateType) != 0) { 141 alreadyStarted = true; 142 break; 143 } 144 } 145 pkgsStates.setValueAt(indexOfPkg, states | stateType); 146 if (!alreadyStarted) { 147 // This is the first package within this UID with this type of event start. 148 addEvent = true; 149 } 150 } else { 151 states &= ~stateType; 152 pkgsStates.setValueAt(indexOfPkg, states); 153 boolean allStopped = true; 154 for (int i = pkgsStates.size() - 1; i >= 0; i--) { 155 final int s = pkgsStates.valueAt(i); 156 if ((s & stateType) != 0) { 157 allStopped = false; 158 break; 159 } 160 } 161 if (allStopped) { 162 // None of the packages in this UID has an active event of this type. 163 addEvent = true; 164 } 165 if (states == 0) { // None of the states of this package are active, prune it. 166 pkgsStates.removeAt(indexOfPkg); 167 if (pkgsStates.size() == 0) { 168 map.remove(uid); 169 } 170 } 171 } 172 if (addEvent) { 173 UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME); 174 if (pkg == null) { 175 pkg = createAppStateEvents(uid, DEFAULT_NAME); 176 mPkgEvents.put(uid, DEFAULT_NAME, pkg); 177 } 178 pkg.addEvent(start, now, batteryUsage, stateTypeIndex); 179 } 180 } 181 } 182 183 @VisibleForTesting 184 @Override reset()185 void reset() { 186 super.reset(); 187 synchronized (mLock) { 188 mUidPackageStates.clear(); 189 } 190 } 191 onTrackerEnabled(boolean enabled)192 private void onTrackerEnabled(boolean enabled) { 193 if (!enabled) { 194 synchronized (mLock) { 195 mPkgEvents.clear(); 196 mUidPackageStates.clear(); 197 } 198 } 199 } 200 201 /** 202 * @return The to-be-exempted battery usage of the given UID in the given duration; it could 203 * be considered as "exempted" due to various use cases, i.e. media playback. 204 */ getUidBatteryExemptedUsageSince(int uid, long since, long now, int types)205 ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now, 206 int types) { 207 if (!mInjector.getPolicy().isEnabled()) { 208 return BATTERY_USAGE_NONE; 209 } 210 Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> result; 211 synchronized (mLock) { 212 final UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME); 213 if (pkg == null) { 214 return BATTERY_USAGE_NONE; 215 } 216 result = pkg.getBatteryUsageSince(since, now, types); 217 } 218 if (!result.second.isEmpty()) { 219 // We have an open event (just start, no stop), get the battery usage till now. 220 final ImmutableBatteryUsage batteryUsage = mAppRestrictionController 221 .getUidBatteryUsage(uid); 222 return result.first.mutate().add(batteryUsage).subtract(result.second).unmutate(); 223 } 224 return result.first; 225 } 226 227 static final class UidBatteryStates extends BaseAppStateDurations<UidStateEventWithBattery> { UidBatteryStates(int uid, @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig)228 UidBatteryStates(int uid, @NonNull String tag, 229 @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) { 230 super(uid, DEFAULT_NAME, STATE_TYPE_NUM, tag, maxTrackingDurationConfig); 231 } 232 UidBatteryStates(@onNull UidBatteryStates other)233 UidBatteryStates(@NonNull UidBatteryStates other) { 234 super(other); 235 } 236 237 /** 238 * @param start {@code true} if it's a start event. 239 * @param now The timestamp when this event occurred. 240 * @param batteryUsage The background current drain since the system boots. 241 * @param eventType One of STATE_TYPE_INDEX_* defined in the class BaseAppStateTracker. 242 */ addEvent(boolean start, long now, ImmutableBatteryUsage batteryUsage, int eventType)243 void addEvent(boolean start, long now, ImmutableBatteryUsage batteryUsage, int eventType) { 244 if (start) { 245 addEvent(start, new UidStateEventWithBattery(start, now, batteryUsage, null), 246 eventType); 247 } else { 248 final UidStateEventWithBattery last = getLastEvent(eventType); 249 if (last == null || !last.isStart()) { 250 if (DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER) { 251 Slog.wtf(TAG, "Unexpected stop event " + eventType); 252 } 253 return; 254 } 255 addEvent(start, new UidStateEventWithBattery(start, now, 256 batteryUsage.mutate().subtract(last.getBatteryUsage()).unmutate(), last), 257 eventType); 258 } 259 } 260 getLastEvent(int eventType)261 UidStateEventWithBattery getLastEvent(int eventType) { 262 return mEvents[eventType] != null ? mEvents[eventType].peekLast() : null; 263 } 264 getBatteryUsageSince(long since, long now, LinkedList<UidStateEventWithBattery> events)265 private Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since, 266 long now, LinkedList<UidStateEventWithBattery> events) { 267 if (events == null || events.size() == 0) { 268 return Pair.create(BATTERY_USAGE_NONE, BATTERY_USAGE_NONE); 269 } 270 final BatteryUsage batteryUsage = new BatteryUsage(); 271 UidStateEventWithBattery lastEvent = null; 272 for (UidStateEventWithBattery event : events) { 273 lastEvent = event; 274 if (event.getTimestamp() < since || event.isStart()) { 275 continue; 276 } 277 batteryUsage.add(event.getBatteryUsage(since, Math.min(now, event.getTimestamp()))); 278 if (now <= event.getTimestamp()) { 279 break; 280 } 281 } 282 return Pair.create(batteryUsage.unmutate(), lastEvent.isStart() 283 ? lastEvent.getBatteryUsage() : BATTERY_USAGE_NONE); 284 } 285 286 /** 287 * @return The pair of bg battery usage of given duration; the first value in the pair 288 * is the aggregated battery usage of selected events in this duration; while 289 * the second value is the battery usage since the system boots, if there is 290 * an open event(just start, no stop) at the end of the duration. 291 */ getBatteryUsageSince(long since, long now, int types)292 Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since, 293 long now, int types) { 294 LinkedList<UidStateEventWithBattery> result = new LinkedList<>(); 295 for (int i = 0; i < mEvents.length; i++) { 296 if ((types & stateIndexToType(i)) != 0) { 297 result = add(result, mEvents[i]); 298 } 299 } 300 return getBatteryUsageSince(since, now, result); 301 } 302 303 /** 304 * Merge the two given duration table and return the result. 305 */ 306 @VisibleForTesting 307 @Override add(LinkedList<UidStateEventWithBattery> durations, LinkedList<UidStateEventWithBattery> otherDurations)308 LinkedList<UidStateEventWithBattery> add(LinkedList<UidStateEventWithBattery> durations, 309 LinkedList<UidStateEventWithBattery> otherDurations) { 310 if (otherDurations == null || otherDurations.size() == 0) { 311 return durations; 312 } 313 if (durations == null || durations.size() == 0) { 314 return (LinkedList<UidStateEventWithBattery>) otherDurations.clone(); 315 } 316 final Iterator<UidStateEventWithBattery> itl = durations.iterator(); 317 final Iterator<UidStateEventWithBattery> itr = otherDurations.iterator(); 318 UidStateEventWithBattery l = itl.next(), r = itr.next(); 319 LinkedList<UidStateEventWithBattery> dest = new LinkedList<>(); 320 boolean actl = false, actr = false, overlapping = false; 321 final BatteryUsage batteryUsage = new BatteryUsage(); 322 long recentActTs = 0, overlappingDuration = 0; 323 for (long lts = l.getTimestamp(), rts = r.getTimestamp(); 324 lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) { 325 final boolean actCur = actl || actr; 326 final UidStateEventWithBattery earliest; 327 if (lts == rts) { 328 earliest = l; 329 // we'll deal with the double counting problem later. 330 if (actl) batteryUsage.add(l.getBatteryUsage()); 331 if (actr) batteryUsage.add(r.getBatteryUsage()); 332 overlappingDuration += overlapping && (actl || actr) 333 ? (lts - recentActTs) : 0; 334 actl = !actl; 335 actr = !actr; 336 lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE; 337 rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE; 338 } else if (lts < rts) { 339 earliest = l; 340 if (actl) batteryUsage.add(l.getBatteryUsage()); 341 overlappingDuration += overlapping && actl ? (lts - recentActTs) : 0; 342 actl = !actl; 343 lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE; 344 } else { 345 earliest = r; 346 if (actr) batteryUsage.add(r.getBatteryUsage()); 347 overlappingDuration += overlapping && actr ? (rts - recentActTs) : 0; 348 actr = !actr; 349 rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE; 350 } 351 overlapping = actl && actr; 352 if (actl || actr) { 353 recentActTs = earliest.getTimestamp(); 354 } 355 if (actCur != (actl || actr)) { 356 final UidStateEventWithBattery event = 357 (UidStateEventWithBattery) earliest.clone(); 358 if (actCur) { 359 // It's an stop/end event, update the start timestamp and batteryUsage. 360 final UidStateEventWithBattery lastEvent = dest.peekLast(); 361 final long startTs = lastEvent.getTimestamp(); 362 final long duration = event.getTimestamp() - startTs; 363 final long durationWithOverlapping = duration + overlappingDuration; 364 // Get the proportional batteryUsage. 365 if (durationWithOverlapping != 0) { 366 batteryUsage.scale(duration * 1.0d / durationWithOverlapping); 367 event.update(lastEvent, new ImmutableBatteryUsage(batteryUsage)); 368 } else { 369 event.update(lastEvent, BATTERY_USAGE_NONE); 370 } 371 batteryUsage.setTo(BATTERY_USAGE_NONE); 372 overlappingDuration = 0; 373 } 374 dest.add(event); 375 } 376 } 377 return dest; 378 } 379 } 380 trimDurations()381 private void trimDurations() { 382 final long now = SystemClock.elapsedRealtime(); 383 trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration())); 384 } 385 386 @Override dump(PrintWriter pw, String prefix)387 void dump(PrintWriter pw, String prefix) { 388 // We're dumping the data in AppBatteryTracker actually, so just dump the policy here. 389 mInjector.getPolicy().dump(pw, prefix); 390 } 391 392 /** 393 * A basic event marking a certain event, i.e., a FGS start/stop; 394 * it'll record the background battery usage data over the start/stop. 395 */ 396 static final class UidStateEventWithBattery extends BaseTimeEvent { 397 /** 398 * Whether or not this is a start event. 399 */ 400 private boolean mIsStart; 401 402 /** 403 * The known background battery usage; it will be the total bg battery usage since 404 * the system boots if the {@link #mIsStart} is true, but will be the delta of the bg 405 * battery usage since the start event if the {@link #mIsStart} is false. 406 */ 407 private @NonNull ImmutableBatteryUsage mBatteryUsage; 408 409 /** 410 * The peer event of this pair (a pair of start/stop events). 411 */ 412 private @Nullable UidStateEventWithBattery mPeer; 413 UidStateEventWithBattery(boolean isStart, long now, @NonNull ImmutableBatteryUsage batteryUsage, @Nullable UidStateEventWithBattery peer)414 UidStateEventWithBattery(boolean isStart, long now, 415 @NonNull ImmutableBatteryUsage batteryUsage, 416 @Nullable UidStateEventWithBattery peer) { 417 super(now); 418 mIsStart = isStart; 419 mBatteryUsage = batteryUsage; 420 mPeer = peer; 421 if (peer != null) { 422 peer.mPeer = this; 423 } 424 } 425 UidStateEventWithBattery(UidStateEventWithBattery other)426 UidStateEventWithBattery(UidStateEventWithBattery other) { 427 super(other); 428 mIsStart = other.mIsStart; 429 mBatteryUsage = other.mBatteryUsage; 430 // Don't copy the peer object though. 431 } 432 433 @Override trimTo(long timestamp)434 void trimTo(long timestamp) { 435 // We don't move the stop event. 436 if (!mIsStart || timestamp < mTimestamp) { 437 return; 438 } 439 if (mPeer != null) { 440 // Reduce the bg battery usage proportionally. 441 final ImmutableBatteryUsage batteryUsage = mPeer.getBatteryUsage(); 442 mPeer.mBatteryUsage = mPeer.getBatteryUsage(timestamp, mPeer.mTimestamp); 443 // Update the battery data of the start event too. 444 mBatteryUsage = mBatteryUsage.mutate() 445 .add(batteryUsage) 446 .subtract(mPeer.mBatteryUsage) 447 .unmutate(); 448 } 449 mTimestamp = timestamp; 450 } 451 update(@onNull UidStateEventWithBattery peer, @NonNull ImmutableBatteryUsage batteryUsage)452 void update(@NonNull UidStateEventWithBattery peer, 453 @NonNull ImmutableBatteryUsage batteryUsage) { 454 mPeer = peer; 455 peer.mPeer = this; 456 mBatteryUsage = batteryUsage; 457 } 458 isStart()459 boolean isStart() { 460 return mIsStart; 461 } 462 getBatteryUsage(long start, long end)463 @NonNull ImmutableBatteryUsage getBatteryUsage(long start, long end) { 464 if (mIsStart || start >= mTimestamp || end <= start) { 465 return BATTERY_USAGE_NONE; 466 } 467 start = Math.max(start, mPeer.mTimestamp); 468 end = Math.min(end, mTimestamp); 469 final long totalDur = mTimestamp - mPeer.mTimestamp; 470 final long inputDur = end - start; 471 return totalDur != 0 ? (totalDur == inputDur ? mBatteryUsage : mBatteryUsage.mutate() 472 .scale((1.0d * inputDur) / totalDur).unmutate()) : BATTERY_USAGE_NONE; 473 } 474 getBatteryUsage()475 @NonNull ImmutableBatteryUsage getBatteryUsage() { 476 return mBatteryUsage; 477 } 478 479 @Override clone()480 public Object clone() { 481 return new UidStateEventWithBattery(this); 482 } 483 484 @Override equals(Object other)485 public boolean equals(Object other) { 486 if (other == null) { 487 return false; 488 } 489 if (other.getClass() != UidStateEventWithBattery.class) { 490 return false; 491 } 492 final UidStateEventWithBattery otherEvent = (UidStateEventWithBattery) other; 493 return otherEvent.mIsStart == mIsStart 494 && otherEvent.mTimestamp == mTimestamp 495 && mBatteryUsage.equals(otherEvent.mBatteryUsage); 496 } 497 498 @Override toString()499 public String toString() { 500 return "UidStateEventWithBattery(" + mIsStart + ", " + mTimestamp 501 + ", " + mBatteryUsage + ")"; 502 } 503 504 @Override hashCode()505 public int hashCode() { 506 return (Boolean.hashCode(mIsStart) * 31 507 + Long.hashCode(mTimestamp)) * 31 508 + mBatteryUsage.hashCode(); 509 } 510 } 511 512 static final class AppBatteryExemptionPolicy 513 extends BaseAppStateEventsPolicy<AppBatteryExemptionTracker> { 514 /** 515 * Whether or not we should enable the exemption of certain battery drains. 516 */ 517 static final String KEY_BG_BATTERY_EXEMPTION_ENABLED = 518 DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "battery_exemption_enabled"; 519 520 /** 521 * Default value to {@link #mTrackerEnabled}. 522 */ 523 static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true; 524 AppBatteryExemptionPolicy(@onNull Injector injector, @NonNull AppBatteryExemptionTracker tracker)525 AppBatteryExemptionPolicy(@NonNull Injector injector, 526 @NonNull AppBatteryExemptionTracker tracker) { 527 super(injector, tracker, 528 KEY_BG_BATTERY_EXEMPTION_ENABLED, DEFAULT_BG_BATTERY_EXEMPTION_ENABLED, 529 AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW, 530 tracker.mContext.getResources() 531 .getInteger(R.integer.config_bg_current_drain_window)); 532 } 533 534 @Override onMaxTrackingDurationChanged(long maxDuration)535 public void onMaxTrackingDurationChanged(long maxDuration) { 536 mTracker.mBgHandler.post(mTracker::trimDurations); 537 } 538 539 @Override onTrackerEnabled(boolean enabled)540 public void onTrackerEnabled(boolean enabled) { 541 mTracker.onTrackerEnabled(enabled); 542 } 543 544 @Override dump(PrintWriter pw, String prefix)545 void dump(PrintWriter pw, String prefix) { 546 pw.print(prefix); 547 pw.println("APP BATTERY EXEMPTION TRACKER POLICY SETTINGS:"); 548 final String indent = " "; 549 prefix = indent + prefix; 550 super.dump(pw, prefix); 551 } 552 } 553 } 554