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