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