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