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