1 /**
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 
17 package com.android.server.usage;
18 
19 import android.annotation.UserIdInt;
20 import android.app.PendingIntent;
21 import android.app.usage.UsageStatsManagerInternal;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.SystemClock;
26 import android.os.UserHandle;
27 import android.util.ArrayMap;
28 import android.util.ArraySet;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.io.PrintWriter;
36 import java.lang.ref.WeakReference;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 
40 /**
41  * Monitors and informs of any app time limits exceeded. It must be informed when an app
42  * enters the foreground and exits. Used by UsageStatsService. Manages multiple users.
43  *
44  * Test: atest FrameworksServicesTests:AppTimeLimitControllerTests
45  * Test: manual: frameworks/base/tests/UsageStatsTest
46  */
47 public class AppTimeLimitController {
48 
49     private static final String TAG = "AppTimeLimitController";
50 
51     private static final boolean DEBUG = false;
52 
53     /** Lock class for this object */
54     private static class Lock {}
55 
56     /** Lock object for the data in this class. */
57     private final Lock mLock = new Lock();
58 
59     private final MyHandler mHandler;
60 
61     private TimeLimitCallbackListener mListener;
62 
63     private static final long MAX_OBSERVER_PER_UID = 1000;
64 
65     private static final long ONE_MINUTE = 60_000L;
66 
67     private static final Integer ONE = new Integer(1);
68 
69     /** Collection of data for each user that has reported usage */
70     @GuardedBy("mLock")
71     private final SparseArray<UserData> mUsers = new SparseArray<>();
72 
73     /**
74      * Collection of data for each app that is registering observers
75      * WARNING: Entries are currently not removed, based on the assumption there are a small
76      *          fixed number of apps on device that can register observers.
77      */
78     @GuardedBy("mLock")
79     private final SparseArray<ObserverAppData> mObserverApps = new SparseArray<>();
80 
81     private class UserData {
82         /** userId of the user */
83         private @UserIdInt
84         int userId;
85 
86         /** Count of the currently active entities */
87         public final ArrayMap<String, Integer> currentlyActive = new ArrayMap<>();
88 
89         /** Map from entity name for quick lookup */
90         public final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>();
91 
UserData(@serIdInt int userId)92         private UserData(@UserIdInt int userId) {
93             this.userId = userId;
94         }
95 
96         @GuardedBy("mLock")
isActive(String[] entities)97         boolean isActive(String[] entities) {
98             // TODO: Consider using a bloom filter here if number of actives becomes large
99             final int size = entities.length;
100             for (int i = 0; i < size; i++) {
101                 if (currentlyActive.containsKey(entities[i])) {
102                     return true;
103                 }
104             }
105             return false;
106         }
107 
108         @GuardedBy("mLock")
addUsageGroup(UsageGroup group)109         void addUsageGroup(UsageGroup group) {
110             final int size = group.mObserved.length;
111             for (int i = 0; i < size; i++) {
112                 ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
113                 if (list == null) {
114                     list = new ArrayList<>();
115                     observedMap.put(group.mObserved[i], list);
116                 }
117                 list.add(group);
118             }
119         }
120 
121         @GuardedBy("mLock")
removeUsageGroup(UsageGroup group)122         void removeUsageGroup(UsageGroup group) {
123             final int size = group.mObserved.length;
124             for (int i = 0; i < size; i++) {
125                 final String observed = group.mObserved[i];
126                 final ArrayList<UsageGroup> list = observedMap.get(observed);
127                 if (list != null) {
128                     list.remove(group);
129                     if (list.isEmpty()) {
130                         // No more observers for this observed entity, remove from map
131                         observedMap.remove(observed);
132                     }
133                 }
134             }
135         }
136 
137         @GuardedBy("mLock")
dump(PrintWriter pw)138         void dump(PrintWriter pw) {
139             pw.print(" userId=");
140             pw.println(userId);
141             pw.print(" Currently Active:");
142             final int nActive = currentlyActive.size();
143             for (int i = 0; i < nActive; i++) {
144                 pw.print(currentlyActive.keyAt(i));
145                 pw.print(", ");
146             }
147             pw.println();
148             pw.print(" Observed Entities:");
149             final int nEntities = observedMap.size();
150             for (int i = 0; i < nEntities; i++) {
151                 pw.print(observedMap.keyAt(i));
152                 pw.print(", ");
153             }
154             pw.println();
155         }
156     }
157 
158 
159     private class ObserverAppData {
160         /** uid of the observing app */
161         private int uid;
162 
163         /** Map of observerId to details of the time limit group */
164         SparseArray<AppUsageGroup> appUsageGroups = new SparseArray<>();
165 
166         /** Map of observerId to details of the time limit group */
167         SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
168 
169         /** Map of observerId to details of the app usage limit group */
170         SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>();
171 
ObserverAppData(int uid)172         private ObserverAppData(int uid) {
173             this.uid = uid;
174         }
175 
176         @GuardedBy("mLock")
removeAppUsageGroup(int observerId)177         void removeAppUsageGroup(int observerId) {
178             appUsageGroups.remove(observerId);
179         }
180 
181         @GuardedBy("mLock")
removeSessionUsageGroup(int observerId)182         void removeSessionUsageGroup(int observerId) {
183             sessionUsageGroups.remove(observerId);
184         }
185 
186         @GuardedBy("mLock")
removeAppUsageLimitGroup(int observerId)187         void removeAppUsageLimitGroup(int observerId) {
188             appUsageLimitGroups.remove(observerId);
189         }
190 
191         @GuardedBy("mLock")
dump(PrintWriter pw)192         void dump(PrintWriter pw) {
193             pw.print(" uid=");
194             pw.println(uid);
195             pw.println("    App Usage Groups:");
196             final int nAppUsageGroups = appUsageGroups.size();
197             for (int i = 0; i < nAppUsageGroups; i++) {
198                 appUsageGroups.valueAt(i).dump(pw);
199                 pw.println();
200             }
201             pw.println("    Session Usage Groups:");
202             final int nSessionUsageGroups = sessionUsageGroups.size();
203             for (int i = 0; i < nSessionUsageGroups; i++) {
204                 sessionUsageGroups.valueAt(i).dump(pw);
205                 pw.println();
206             }
207             pw.println("    App Usage Limit Groups:");
208             final int nAppUsageLimitGroups = appUsageLimitGroups.size();
209             for (int i = 0; i < nAppUsageLimitGroups; i++) {
210                 appUsageLimitGroups.valueAt(i).dump(pw);
211                 pw.println();
212             }
213         }
214     }
215 
216     /**
217      * Listener interface for being informed when an app group's time limit is reached.
218      */
219     public interface TimeLimitCallbackListener {
220         /**
221          * Time limit for a group, keyed by the observerId, has been reached.
222          *
223          * @param observerId     The observerId of the group whose limit was reached
224          * @param userId         The userId
225          * @param timeLimit      The original time limit in milliseconds
226          * @param timeElapsed    How much time was actually spent on apps in the group, in
227          *                       milliseconds
228          * @param callbackIntent The PendingIntent to send when the limit is reached
229          */
onLimitReached(int observerId, @UserIdInt int userId, long timeLimit, long timeElapsed, PendingIntent callbackIntent)230         public void onLimitReached(int observerId, @UserIdInt int userId, long timeLimit,
231                 long timeElapsed, PendingIntent callbackIntent);
232 
233         /**
234          * Session ended for a group, keyed by the observerId, after limit was reached.
235          *
236          * @param observerId     The observerId of the group whose limit was reached
237          * @param userId         The userId
238          * @param timeElapsed    How much time was actually spent on apps in the group, in
239          *                       milliseconds
240          * @param callbackIntent The PendingIntent to send when the limit is reached
241          */
onSessionEnd(int observerId, @UserIdInt int userId, long timeElapsed, PendingIntent callbackIntent)242         public void onSessionEnd(int observerId, @UserIdInt int userId, long timeElapsed,
243                 PendingIntent callbackIntent);
244     }
245 
246     abstract class UsageGroup {
247         protected int mObserverId;
248         protected String[] mObserved;
249         protected long mTimeLimitMs;
250         protected long mUsageTimeMs;
251         protected int mActives;
252         protected long mLastKnownUsageTimeMs;
253         protected long mLastUsageEndTimeMs;
254         protected WeakReference<UserData> mUserRef;
255         protected WeakReference<ObserverAppData> mObserverAppRef;
256         protected PendingIntent mLimitReachedCallback;
257 
UsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed, long timeLimitMs, PendingIntent limitReachedCallback)258         UsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed,
259                 long timeLimitMs, PendingIntent limitReachedCallback) {
260             mUserRef = new WeakReference<>(user);
261             mObserverAppRef = new WeakReference<>(observerApp);
262             mObserverId = observerId;
263             mObserved = observed;
264             mTimeLimitMs = timeLimitMs;
265             mLimitReachedCallback = limitReachedCallback;
266         }
267 
268         @GuardedBy("mLock")
getTimeLimitMs()269         public long getTimeLimitMs() { return mTimeLimitMs; }
270 
271         @GuardedBy("mLock")
getUsageTimeMs()272         public long getUsageTimeMs() { return mUsageTimeMs; }
273 
274         @GuardedBy("mLock")
remove()275         public void remove() {
276             UserData user = mUserRef.get();
277             if (user != null) {
278                 user.removeUsageGroup(this);
279             }
280             // Clear the callback, so any racy inflight message will do nothing
281             mLimitReachedCallback = null;
282         }
283 
284         @GuardedBy("mLock")
noteUsageStart(long startTimeMs)285         void noteUsageStart(long startTimeMs) {
286             noteUsageStart(startTimeMs, startTimeMs);
287         }
288 
289         @GuardedBy("mLock")
noteUsageStart(long startTimeMs, long currentTimeMs)290         void noteUsageStart(long startTimeMs, long currentTimeMs) {
291             if (mActives++ == 0) {
292                 // If last known usage ended after the start of this usage, there is overlap
293                 // between the last usage session and this one. Avoid double counting by only
294                 // counting from the end of the last session. This has a rare side effect that some
295                 // usage will not be accounted for if the previous session started and stopped
296                 // within this current usage.
297                 startTimeMs = mLastUsageEndTimeMs > startTimeMs ? mLastUsageEndTimeMs : startTimeMs;
298                 mLastKnownUsageTimeMs = startTimeMs;
299                 final long timeRemaining =
300                         mTimeLimitMs - mUsageTimeMs - currentTimeMs + startTimeMs;
301                 if (timeRemaining > 0) {
302                     if (DEBUG) {
303                         Slog.d(TAG, "Posting timeout for " + mObserverId + " for "
304                                 + timeRemaining + "ms");
305                     }
306                     postCheckTimeoutLocked(this, timeRemaining);
307                 }
308             } else {
309                 if (mActives > mObserved.length) {
310                     // Try to get to a valid state and log the issue
311                     mActives = mObserved.length;
312                     final UserData user = mUserRef.get();
313                     if (user == null) return;
314                     final Object[] array = user.currentlyActive.keySet().toArray();
315                     Slog.e(TAG,
316                             "Too many noted usage starts! Observed entities: " + Arrays.toString(
317                                     mObserved) + "   Active Entities: " + Arrays.toString(array));
318                 }
319             }
320         }
321 
322         @GuardedBy("mLock")
noteUsageStop(long stopTimeMs)323         void noteUsageStop(long stopTimeMs) {
324             if (--mActives == 0) {
325                 final boolean limitNotCrossed = mUsageTimeMs < mTimeLimitMs;
326                 mUsageTimeMs += stopTimeMs - mLastKnownUsageTimeMs;
327 
328                 mLastUsageEndTimeMs = stopTimeMs;
329                 if (limitNotCrossed && mUsageTimeMs >= mTimeLimitMs) {
330                     // Crossed the limit
331                     if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + mObserverId);
332                     postInformLimitReachedListenerLocked(this);
333                 }
334                 cancelCheckTimeoutLocked(this);
335             } else {
336                 if (mActives < 0) {
337                     // Try to get to a valid state and log the issue
338                     mActives = 0;
339                     final UserData user = mUserRef.get();
340                     if (user == null) return;
341                     final Object[] array = user.currentlyActive.keySet().toArray();
342                     Slog.e(TAG,
343                             "Too many noted usage stops! Observed entities: " + Arrays.toString(
344                                     mObserved) + "   Active Entities: " + Arrays.toString(array));
345                 }
346             }
347         }
348 
349         @GuardedBy("mLock")
checkTimeout(long currentTimeMs)350         void checkTimeout(long currentTimeMs) {
351             final UserData user = mUserRef.get();
352             if (user == null) return;
353 
354             long timeRemainingMs = mTimeLimitMs - mUsageTimeMs;
355 
356             if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + timeRemainingMs);
357 
358             // Already reached the limit, no need to report again
359             if (timeRemainingMs <= 0) return;
360 
361             if (DEBUG) {
362                 Slog.d(TAG, "checkTimeout");
363             }
364 
365             // Double check that at least one entity in this group is currently active
366             if (user.isActive(mObserved)) {
367                 if (DEBUG) {
368                     Slog.d(TAG, "checkTimeout group is active");
369                 }
370                 final long timeUsedMs = currentTimeMs - mLastKnownUsageTimeMs;
371                 if (timeRemainingMs <= timeUsedMs) {
372                     if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
373                     // Hit the limit, set timeRemaining to zero to avoid checking again
374                     mUsageTimeMs += timeUsedMs;
375                     mLastKnownUsageTimeMs = currentTimeMs;
376                     AppTimeLimitController.this.postInformLimitReachedListenerLocked(this);
377                 } else {
378                     if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
379                     AppTimeLimitController.this.postCheckTimeoutLocked(this,
380                             timeRemainingMs - timeUsedMs);
381                 }
382             }
383         }
384 
385         @GuardedBy("mLock")
onLimitReached()386         public void onLimitReached() {
387             UserData user = mUserRef.get();
388             if (user == null) return;
389             if (mListener != null) {
390                 mListener.onLimitReached(mObserverId, user.userId, mTimeLimitMs, mUsageTimeMs,
391                         mLimitReachedCallback);
392             }
393         }
394 
395         @GuardedBy("mLock")
dump(PrintWriter pw)396         void dump(PrintWriter pw) {
397             pw.print("        Group id=");
398             pw.print(mObserverId);
399             pw.print(" timeLimit=");
400             pw.print(mTimeLimitMs);
401             pw.print(" used=");
402             pw.print(mUsageTimeMs);
403             pw.print(" lastKnownUsage=");
404             pw.print(mLastKnownUsageTimeMs);
405             pw.print(" mActives=");
406             pw.print(mActives);
407             pw.print(" observed=");
408             pw.print(Arrays.toString(mObserved));
409         }
410     }
411 
412     class AppUsageGroup extends UsageGroup {
AppUsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed, long timeLimitMs, PendingIntent limitReachedCallback)413         public AppUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
414                 String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
415             super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
416         }
417 
418         @Override
419         @GuardedBy("mLock")
remove()420         public void remove() {
421             super.remove();
422             ObserverAppData observerApp = mObserverAppRef.get();
423             if (observerApp != null) {
424                 observerApp.removeAppUsageGroup(mObserverId);
425             }
426         }
427 
428         @Override
429         @GuardedBy("mLock")
onLimitReached()430         public void onLimitReached() {
431             super.onLimitReached();
432             // Unregister since the limit has been met and observer was informed.
433             remove();
434         }
435     }
436 
437     class SessionUsageGroup extends UsageGroup {
438         private long mNewSessionThresholdMs;
439         private PendingIntent mSessionEndCallback;
440 
SessionUsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed, long timeLimitMs, PendingIntent limitReachedCallback, long newSessionThresholdMs, PendingIntent sessionEndCallback)441         public SessionUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
442                 String[] observed, long timeLimitMs, PendingIntent limitReachedCallback,
443                 long newSessionThresholdMs, PendingIntent sessionEndCallback) {
444             super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
445             this.mNewSessionThresholdMs = newSessionThresholdMs;
446             this.mSessionEndCallback = sessionEndCallback;
447         }
448 
449         @Override
450         @GuardedBy("mLock")
remove()451         public void remove() {
452             super.remove();
453             ObserverAppData observerApp = mObserverAppRef.get();
454             if (observerApp != null) {
455                 observerApp.removeSessionUsageGroup(mObserverId);
456             }
457             // Clear the callback, so any racy inflight messages will do nothing
458             mSessionEndCallback = null;
459         }
460 
461         @Override
462         @GuardedBy("mLock")
noteUsageStart(long startTimeMs, long currentTimeMs)463         public void noteUsageStart(long startTimeMs, long currentTimeMs) {
464             if (mActives == 0) {
465                 if (startTimeMs - mLastUsageEndTimeMs > mNewSessionThresholdMs) {
466                     // New session has started, clear usage time.
467                     mUsageTimeMs = 0;
468                 }
469                 AppTimeLimitController.this.cancelInformSessionEndListener(this);
470             }
471             super.noteUsageStart(startTimeMs, currentTimeMs);
472         }
473 
474         @Override
475         @GuardedBy("mLock")
noteUsageStop(long stopTimeMs)476         public void noteUsageStop(long stopTimeMs) {
477             super.noteUsageStop(stopTimeMs);
478             if (mActives == 0) {
479                 if (mUsageTimeMs >= mTimeLimitMs) {
480                     // Usage has ended. Schedule the session end callback to be triggered once
481                     // the new session threshold has been reached
482                     AppTimeLimitController.this.postInformSessionEndListenerLocked(this,
483                             mNewSessionThresholdMs);
484                 }
485 
486             }
487         }
488 
489         @GuardedBy("mLock")
onSessionEnd()490         public void onSessionEnd() {
491             UserData user = mUserRef.get();
492             if (user == null) return;
493             if (mListener != null) {
494                 mListener.onSessionEnd(mObserverId,
495                                        user.userId,
496                                        mUsageTimeMs,
497                                        mSessionEndCallback);
498             }
499         }
500 
501         @Override
502         @GuardedBy("mLock")
dump(PrintWriter pw)503         void dump(PrintWriter pw) {
504             super.dump(pw);
505             pw.print(" lastUsageEndTime=");
506             pw.print(mLastUsageEndTimeMs);
507             pw.print(" newSessionThreshold=");
508             pw.print(mNewSessionThresholdMs);
509         }
510     }
511 
512     class AppUsageLimitGroup extends UsageGroup {
AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed, long timeLimitMs, long timeUsedMs, PendingIntent limitReachedCallback)513         public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
514                 String[] observed, long timeLimitMs, long timeUsedMs,
515                 PendingIntent limitReachedCallback) {
516             super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
517             mUsageTimeMs = timeUsedMs;
518         }
519 
520         @Override
521         @GuardedBy("mLock")
remove()522         public void remove() {
523             super.remove();
524             ObserverAppData observerApp = mObserverAppRef.get();
525             if (observerApp != null) {
526                 observerApp.removeAppUsageLimitGroup(mObserverId);
527             }
528         }
529 
530         @GuardedBy("mLock")
getTotaUsageLimit()531         long getTotaUsageLimit() {
532             return mTimeLimitMs;
533         }
534 
535         @GuardedBy("mLock")
getUsageRemaining()536         long getUsageRemaining() {
537             // If there is currently an active session, account for its usage
538             if (mActives > 0) {
539                 return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs);
540             } else {
541                 return mTimeLimitMs - mUsageTimeMs;
542             }
543         }
544     }
545 
546 
547     private class MyHandler extends Handler {
548         static final int MSG_CHECK_TIMEOUT = 1;
549         static final int MSG_INFORM_LIMIT_REACHED_LISTENER = 2;
550         static final int MSG_INFORM_SESSION_END = 3;
551 
MyHandler(Looper looper)552         MyHandler(Looper looper) {
553             super(looper);
554         }
555 
556         @Override
handleMessage(Message msg)557         public void handleMessage(Message msg) {
558             switch (msg.what) {
559                 case MSG_CHECK_TIMEOUT:
560                     synchronized (mLock) {
561                         ((UsageGroup) msg.obj).checkTimeout(getUptimeMillis());
562                     }
563                     break;
564                 case MSG_INFORM_LIMIT_REACHED_LISTENER:
565                     synchronized (mLock) {
566                         ((UsageGroup) msg.obj).onLimitReached();
567                     }
568                     break;
569                 case MSG_INFORM_SESSION_END:
570                     synchronized (mLock) {
571                         ((SessionUsageGroup) msg.obj).onSessionEnd();
572                     }
573                     break;
574                 default:
575                     super.handleMessage(msg);
576                     break;
577             }
578         }
579     }
580 
AppTimeLimitController(TimeLimitCallbackListener listener, Looper looper)581     public AppTimeLimitController(TimeLimitCallbackListener listener, Looper looper) {
582         mHandler = new MyHandler(looper);
583         mListener = listener;
584     }
585 
586     /** Overrideable by a test */
587     @VisibleForTesting
getUptimeMillis()588     protected long getUptimeMillis() {
589         return SystemClock.uptimeMillis();
590     }
591 
592     /** Overrideable for testing purposes */
593     @VisibleForTesting
getAppUsageObserverPerUidLimit()594     protected long getAppUsageObserverPerUidLimit() {
595         return MAX_OBSERVER_PER_UID;
596     }
597 
598     /** Overrideable for testing purposes */
599     @VisibleForTesting
getUsageSessionObserverPerUidLimit()600     protected long getUsageSessionObserverPerUidLimit() {
601         return MAX_OBSERVER_PER_UID;
602     }
603 
604     /** Overrideable for testing purposes */
605     @VisibleForTesting
getAppUsageLimitObserverPerUidLimit()606     protected long getAppUsageLimitObserverPerUidLimit() {
607         return MAX_OBSERVER_PER_UID;
608     }
609 
610     /** Overrideable for testing purposes */
611     @VisibleForTesting
getMinTimeLimit()612     protected long getMinTimeLimit() {
613         return ONE_MINUTE;
614     }
615 
616     @VisibleForTesting
getAppUsageGroup(int observerAppUid, int observerId)617     AppUsageGroup getAppUsageGroup(int observerAppUid, int observerId) {
618         synchronized (mLock) {
619             return getOrCreateObserverAppDataLocked(observerAppUid).appUsageGroups.get(observerId);
620         }
621     }
622 
623     @VisibleForTesting
getSessionUsageGroup(int observerAppUid, int observerId)624     SessionUsageGroup getSessionUsageGroup(int observerAppUid, int observerId) {
625         synchronized (mLock) {
626             return getOrCreateObserverAppDataLocked(observerAppUid).sessionUsageGroups.get(
627                     observerId);
628         }
629     }
630 
631     @VisibleForTesting
getAppUsageLimitGroup(int observerAppUid, int observerId)632     AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) {
633         synchronized (mLock) {
634             return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get(
635                     observerId);
636         }
637     }
638 
639     /**
640      * Returns an object describing the app usage limit for the given package which was set via
641      * {@link #addAppUsageLimitObserver).
642      * If there are multiple limits that apply to the package, the one with the smallest
643      * time remaining will be returned.
644      */
getAppUsageLimit( String packageName, UserHandle user)645     public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(
646             String packageName, UserHandle user) {
647         synchronized (mLock) {
648             final UserData userData = getOrCreateUserDataLocked(user.getIdentifier());
649             if (userData == null) {
650                 return null;
651             }
652 
653             final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName);
654             if (usageGroups == null || usageGroups.isEmpty()) {
655                 return null;
656             }
657 
658             final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>();
659             for (int i = 0; i < usageGroups.size(); i++) {
660                 if (usageGroups.get(i) instanceof AppUsageLimitGroup) {
661                     final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i);
662                     for (int j = 0; j < group.mObserved.length; j++) {
663                         if (group.mObserved[j].equals(packageName)) {
664                             usageLimitGroups.add(group);
665                             break;
666                         }
667                     }
668                 }
669             }
670             if (usageLimitGroups.isEmpty()) {
671                 return null;
672             }
673 
674             AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0);
675             for (int i = 1; i < usageLimitGroups.size(); i++) {
676                 final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i);
677                 if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) {
678                     smallestGroup = otherGroup;
679                 }
680             }
681             return new UsageStatsManagerInternal.AppUsageLimitData(
682                     smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
683         }
684     }
685 
686     /** Returns an existing UserData object for the given userId, or creates one */
687     @GuardedBy("mLock")
getOrCreateUserDataLocked(int userId)688     private UserData getOrCreateUserDataLocked(int userId) {
689         UserData userData = mUsers.get(userId);
690         if (userData == null) {
691             userData = new UserData(userId);
692             mUsers.put(userId, userData);
693         }
694         return userData;
695     }
696 
697     /** Returns an existing ObserverAppData object for the given uid, or creates one */
698     @GuardedBy("mLock")
getOrCreateObserverAppDataLocked(int uid)699     private ObserverAppData getOrCreateObserverAppDataLocked(int uid) {
700         ObserverAppData appData = mObserverApps.get(uid);
701         if (appData == null) {
702             appData = new ObserverAppData(uid);
703             mObserverApps.put(uid, appData);
704         }
705         return appData;
706     }
707 
708     /** Clean up data if user is removed */
onUserRemoved(int userId)709     public void onUserRemoved(int userId) {
710         synchronized (mLock) {
711             // TODO: Remove any inflight delayed messages
712             mUsers.remove(userId);
713         }
714     }
715 
716     /**
717      * Check if group has any currently active entities.
718      */
719     @GuardedBy("mLock")
noteActiveLocked(UserData user, UsageGroup group, long currentTimeMs)720     private void noteActiveLocked(UserData user, UsageGroup group, long currentTimeMs) {
721         // TODO: Consider using a bloom filter here if number of actives becomes large
722         final int size = group.mObserved.length;
723         for (int i = 0; i < size; i++) {
724             if (user.currentlyActive.containsKey(group.mObserved[i])) {
725                 // Entity is currently active. Start group's usage.
726                 group.noteUsageStart(currentTimeMs);
727             }
728         }
729     }
730 
731     /**
732      * Registers an app usage observer with the given details.
733      * Existing app usage observer with the same observerId will be removed.
734      */
addAppUsageObserver(int requestingUid, int observerId, String[] observed, long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId)735     public void addAppUsageObserver(int requestingUid, int observerId, String[] observed,
736             long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
737         if (timeLimit < getMinTimeLimit()) {
738             throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
739         }
740         synchronized (mLock) {
741             UserData user = getOrCreateUserDataLocked(userId);
742             ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
743             AppUsageGroup group = observerApp.appUsageGroups.get(observerId);
744             if (group != null) {
745                 // Remove previous app usage group associated with observerId
746                 group.remove();
747             }
748 
749             final int observerIdCount = observerApp.appUsageGroups.size();
750             if (observerIdCount >= getAppUsageObserverPerUidLimit()) {
751                 throw new IllegalStateException(
752                         "Too many app usage observers added by uid " + requestingUid);
753             }
754             group = new AppUsageGroup(user, observerApp, observerId, observed, timeLimit,
755                     callbackIntent);
756             observerApp.appUsageGroups.append(observerId, group);
757 
758             if (DEBUG) {
759                 Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
760             }
761 
762             user.addUsageGroup(group);
763             noteActiveLocked(user, group, getUptimeMillis());
764         }
765     }
766 
767     /**
768      * Remove a registered observer by observerId and calling uid.
769      *
770      * @param requestingUid The calling uid
771      * @param observerId    The unique observer id for this user
772      * @param userId        The user id of the observer
773      */
removeAppUsageObserver(int requestingUid, int observerId, @UserIdInt int userId)774     public void removeAppUsageObserver(int requestingUid, int observerId, @UserIdInt int userId) {
775         synchronized (mLock) {
776             final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
777             final AppUsageGroup group = observerApp.appUsageGroups.get(observerId);
778             if (group != null) {
779                 // Remove previous app usage group associated with observerId
780                 group.remove();
781             }
782         }
783     }
784 
785 
786     /**
787      * Registers a usage session observer with the given details.
788      * Existing usage session observer with the same observerId will be removed.
789      */
addUsageSessionObserver(int requestingUid, int observerId, String[] observed, long timeLimit, long sessionThresholdTime, PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent, @UserIdInt int userId)790     public void addUsageSessionObserver(int requestingUid, int observerId, String[] observed,
791             long timeLimit, long sessionThresholdTime,
792             PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
793             @UserIdInt int userId) {
794         if (timeLimit < getMinTimeLimit()) {
795             throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
796         }
797         synchronized (mLock) {
798             UserData user = getOrCreateUserDataLocked(userId);
799             ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
800             SessionUsageGroup group = observerApp.sessionUsageGroups.get(observerId);
801             if (group != null) {
802                 // Remove previous session usage group associated with observerId
803                 group.remove();
804             }
805 
806             final int observerIdCount = observerApp.sessionUsageGroups.size();
807             if (observerIdCount >= getUsageSessionObserverPerUidLimit()) {
808                 throw new IllegalStateException(
809                         "Too many app usage observers added by uid " + requestingUid);
810             }
811             group = new SessionUsageGroup(user, observerApp, observerId, observed, timeLimit,
812                     limitReachedCallbackIntent, sessionThresholdTime, sessionEndCallbackIntent);
813             observerApp.sessionUsageGroups.append(observerId, group);
814 
815             user.addUsageGroup(group);
816             noteActiveLocked(user, group, getUptimeMillis());
817         }
818     }
819 
820     /**
821      * Remove a registered observer by observerId and calling uid.
822      *
823      * @param requestingUid The calling uid
824      * @param observerId    The unique observer id for this user
825      * @param userId        The user id of the observer
826      */
removeUsageSessionObserver(int requestingUid, int observerId, @UserIdInt int userId)827     public void removeUsageSessionObserver(int requestingUid, int observerId,
828             @UserIdInt int userId) {
829         synchronized (mLock) {
830             final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
831             final SessionUsageGroup group = observerApp.sessionUsageGroups.get(observerId);
832             if (group != null) {
833                 // Remove previous app usage group associated with observerId
834                 group.remove();
835             }
836         }
837     }
838 
839     /**
840      * Registers an app usage limit observer with the given details.
841      * Existing app usage limit observer with the same observerId will be removed.
842      */
addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed, long timeLimit, long timeUsed, PendingIntent callbackIntent, @UserIdInt int userId)843     public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
844             long timeLimit, long timeUsed, PendingIntent callbackIntent,
845             @UserIdInt int userId) {
846         if (timeLimit < getMinTimeLimit()) {
847             throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
848         }
849         synchronized (mLock) {
850             UserData user = getOrCreateUserDataLocked(userId);
851             ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
852             AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
853             if (group != null) {
854                 // Remove previous app usage group associated with observerId
855                 group.remove();
856             }
857 
858             final int observerIdCount = observerApp.appUsageLimitGroups.size();
859             if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) {
860                 throw new IllegalStateException(
861                         "Too many app usage observers added by uid " + requestingUid);
862             }
863             group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
864                     timeUsed, timeUsed >= timeLimit ? null : callbackIntent);
865             observerApp.appUsageLimitGroups.append(observerId, group);
866 
867             if (DEBUG) {
868                 Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
869             }
870 
871             user.addUsageGroup(group);
872             noteActiveLocked(user, group, getUptimeMillis());
873         }
874     }
875 
876     /**
877      * Remove a registered observer by observerId and calling uid.
878      *
879      * @param requestingUid The calling uid
880      * @param observerId    The unique observer id for this user
881      * @param userId        The user id of the observer
882      */
removeAppUsageLimitObserver(int requestingUid, int observerId, @UserIdInt int userId)883     public void removeAppUsageLimitObserver(int requestingUid, int observerId,
884             @UserIdInt int userId) {
885         synchronized (mLock) {
886             final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
887             final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
888             if (group != null) {
889                 // Remove previous app usage group associated with observerId
890                 group.remove();
891             }
892         }
893     }
894 
895     /**
896      * Called when an entity becomes active.
897      *
898      * @param name      The entity that became active
899      * @param userId    The user
900      * @param timeAgoMs Time since usage was started
901      */
noteUsageStart(String name, int userId, long timeAgoMs)902     public void noteUsageStart(String name, int userId, long timeAgoMs)
903             throws IllegalArgumentException {
904         synchronized (mLock) {
905             UserData user = getOrCreateUserDataLocked(userId);
906             if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became active");
907 
908             final int index = user.currentlyActive.indexOfKey(name);
909             if (index >= 0) {
910                 final Integer count = user.currentlyActive.valueAt(index);
911                 if (count != null) {
912                     // There are multiple instances of this entity. Just increment the count.
913                     user.currentlyActive.setValueAt(index, count + 1);
914                     return;
915                 }
916             }
917             final long currentTime = getUptimeMillis();
918 
919             user.currentlyActive.put(name, ONE);
920 
921             ArrayList<UsageGroup> groups = user.observedMap.get(name);
922             if (groups == null) return;
923 
924             final int size = groups.size();
925             for (int i = 0; i < size; i++) {
926                 UsageGroup group = groups.get(i);
927                 group.noteUsageStart(currentTime - timeAgoMs, currentTime);
928             }
929         }
930     }
931 
932     /**
933      * Called when an entity becomes active.
934      *
935      * @param name   The entity that became active
936      * @param userId The user
937      */
noteUsageStart(String name, int userId)938     public void noteUsageStart(String name, int userId) throws IllegalArgumentException {
939         noteUsageStart(name, userId, 0);
940     }
941 
942     /**
943      * Called when an entity becomes inactive.
944      *
945      * @param name   The entity that became inactive
946      * @param userId The user
947      */
noteUsageStop(String name, int userId)948     public void noteUsageStop(String name, int userId) throws IllegalArgumentException {
949         synchronized (mLock) {
950             UserData user = getOrCreateUserDataLocked(userId);
951             if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became inactive");
952 
953             final int index = user.currentlyActive.indexOfKey(name);
954             if (index < 0) {
955                 throw new IllegalArgumentException(
956                         "Unable to stop usage for " + name + ", not in use");
957             }
958 
959             final Integer count = user.currentlyActive.valueAt(index);
960             if (!count.equals(ONE)) {
961                 // There are multiple instances of this entity. Just decrement the count.
962                 user.currentlyActive.setValueAt(index, count - 1);
963                 return;
964             }
965 
966             user.currentlyActive.removeAt(index);
967             final long currentTime = getUptimeMillis();
968 
969             // Check if any of the groups need to watch for this entity
970             ArrayList<UsageGroup> groups = user.observedMap.get(name);
971             if (groups == null) return;
972 
973             final int size = groups.size();
974             for (int i = 0; i < size; i++) {
975                 UsageGroup group = groups.get(i);
976                 group.noteUsageStop(currentTime);
977             }
978 
979         }
980     }
981 
982     @GuardedBy("mLock")
postInformLimitReachedListenerLocked(UsageGroup group)983     private void postInformLimitReachedListenerLocked(UsageGroup group) {
984         mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LIMIT_REACHED_LISTENER,
985                 group));
986     }
987 
988     @GuardedBy("mLock")
postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout)989     private void postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout) {
990         mHandler.sendMessageDelayed(
991                 mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group),
992                 timeout);
993     }
994 
995     @GuardedBy("mLock")
cancelInformSessionEndListener(SessionUsageGroup group)996     private void cancelInformSessionEndListener(SessionUsageGroup group) {
997         mHandler.removeMessages(MyHandler.MSG_INFORM_SESSION_END, group);
998     }
999 
1000     @GuardedBy("mLock")
postCheckTimeoutLocked(UsageGroup group, long timeout)1001     private void postCheckTimeoutLocked(UsageGroup group, long timeout) {
1002         mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_CHECK_TIMEOUT, group),
1003                 timeout);
1004     }
1005 
1006     @GuardedBy("mLock")
cancelCheckTimeoutLocked(UsageGroup group)1007     private void cancelCheckTimeoutLocked(UsageGroup group) {
1008         mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
1009     }
1010 
dump(String[] args, PrintWriter pw)1011     void dump(String[] args, PrintWriter pw) {
1012         if (args != null) {
1013             for (int i = 0; i < args.length; i++) {
1014                 String arg = args[i];
1015                 if ("actives".equals(arg)) {
1016                     synchronized (mLock) {
1017                         final int nUsers = mUsers.size();
1018                         for (int user = 0; user < nUsers; user++) {
1019                             final ArrayMap<String, Integer> actives =
1020                                     mUsers.valueAt(user).currentlyActive;
1021                             final int nActive = actives.size();
1022                             for (int active = 0; active < nActive; active++) {
1023                                 pw.println(actives.keyAt(active));
1024                             }
1025                         }
1026                     }
1027                     return;
1028                 }
1029             }
1030         }
1031 
1032         synchronized (mLock) {
1033             pw.println("\n  App Time Limits");
1034             final int nUsers = mUsers.size();
1035             for (int i = 0; i < nUsers; i++) {
1036                 pw.print("   User ");
1037                 mUsers.valueAt(i).dump(pw);
1038             }
1039             pw.println();
1040             final int nObserverApps = mObserverApps.size();
1041             for (int i = 0; i < nObserverApps; i++) {
1042                 pw.print("   Observer App ");
1043                 mObserverApps.valueAt(i).dump(pw);
1044             }
1045         }
1046     }
1047 }
1048