1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.am; 18 19 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; 20 21 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; 22 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; 23 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 24 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 25 import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED; 26 import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED; 27 28 import android.annotation.Nullable; 29 import android.app.Activity; 30 import android.app.ActivityManagerInternal; 31 import android.app.ActivityOptions; 32 import android.app.AppGlobals; 33 import android.app.PendingIntent; 34 import android.app.PendingIntentStats; 35 import android.app.compat.CompatChanges; 36 import android.content.IIntentSender; 37 import android.content.Intent; 38 import android.os.Binder; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.PowerWhitelistManager; 45 import android.os.RemoteCallbackList; 46 import android.os.RemoteException; 47 import android.os.UserHandle; 48 import android.util.ArrayMap; 49 import android.util.Slog; 50 import android.util.SparseArray; 51 import android.util.SparseIntArray; 52 53 import com.android.internal.annotations.GuardedBy; 54 import com.android.internal.os.IResultReceiver; 55 import com.android.internal.util.RingBuffer; 56 import com.android.internal.util.function.pooled.PooledLambda; 57 import com.android.server.AlarmManagerInternal; 58 import com.android.server.LocalServices; 59 import com.android.server.am.PendingIntentRecord.CancellationReason; 60 import com.android.server.wm.ActivityTaskManagerInternal; 61 import com.android.server.wm.SafeActivityOptions; 62 63 import java.io.PrintWriter; 64 import java.lang.ref.WeakReference; 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.HashMap; 68 import java.util.Iterator; 69 import java.util.List; 70 71 /** 72 * Helper class for {@link ActivityManagerService} responsible for managing pending intents. 73 * 74 * <p>This class uses {@link #mLock} to synchronize access to internal state and doesn't make use of 75 * {@link ActivityManagerService} lock since there can be direct calls into this class from outside 76 * AM. This helps avoid deadlocks. 77 */ 78 public class PendingIntentController { 79 private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentController" : TAG_AM; 80 private static final String TAG_MU = TAG + POSTFIX_MU; 81 82 /** @see {@link #mRecentIntentsPerUid}. */ 83 private static final int RECENT_N = 10; 84 85 /** Lock for internal state. */ 86 final Object mLock = new Object(); 87 final Handler mH; 88 ActivityManagerInternal mAmInternal; 89 final UserController mUserController; 90 final ActivityTaskManagerInternal mAtmInternal; 91 92 /** Set of IntentSenderRecord objects that are currently active. */ 93 final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords 94 = new HashMap<>(); 95 96 /** The number of PendingIntentRecord per uid */ 97 @GuardedBy("mLock") 98 private final SparseIntArray mIntentsPerUid = new SparseIntArray(); 99 100 /** The recent PendingIntentRecord, up to {@link #RECENT_N} per uid */ 101 @GuardedBy("mLock") 102 private final SparseArray<RingBuffer<String>> mRecentIntentsPerUid = new SparseArray<>(); 103 104 private final ActivityManagerConstants mConstants; 105 PendingIntentController(Looper looper, UserController userController, ActivityManagerConstants constants)106 PendingIntentController(Looper looper, UserController userController, 107 ActivityManagerConstants constants) { 108 mH = new Handler(looper); 109 mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); 110 mUserController = userController; 111 mConstants = constants; 112 } 113 onActivityManagerInternalAdded()114 void onActivityManagerInternalAdded() { 115 synchronized (mLock) { 116 mAmInternal = LocalServices.getService(ActivityManagerInternal.class); 117 } 118 } 119 getIntentSender(int type, String packageName, @Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions)120 public PendingIntentRecord getIntentSender(int type, String packageName, 121 @Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho, 122 int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions) { 123 synchronized (mLock) { 124 if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSender(): uid=" + callingUid); 125 126 // We're going to be splicing together extras before sending, so we're 127 // okay poking into any contained extras. 128 if (intents != null) { 129 for (int i = 0; i < intents.length; i++) { 130 intents[i].setDefusable(true); 131 } 132 } 133 Bundle.setDefusable(bOptions, true); 134 ActivityOptions opts = ActivityOptions.fromBundle(bOptions); 135 if (opts != null && opts.getPendingIntentBackgroundActivityStartMode() 136 != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { 137 Slog.wtf(TAG, "Resetting option setPendingIntentBackgroundActivityStartMode(" 138 + opts.getPendingIntentBackgroundActivityStartMode() 139 + ") to SYSTEM_DEFINED from the options provided by the pending " 140 + "intent creator (" 141 + packageName 142 + ") because this option is meant for the pending intent sender"); 143 if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK, 144 callingUid)) { 145 throw new IllegalArgumentException("pendingIntentBackgroundActivityStartMode " 146 + "must not be set when creating a PendingIntent"); 147 } 148 opts.setPendingIntentBackgroundActivityStartMode( 149 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); 150 } 151 152 if (opts != null && opts.isPendingIntentBackgroundActivityLaunchAllowedByPermission()) { 153 Slog.wtf(TAG, 154 "Resetting option pendingIntentBackgroundActivityLaunchAllowedByPermission" 155 + " which is set by the pending intent creator (" 156 + packageName 157 + ") because this option is meant for the pending intent sender"); 158 if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK, 159 callingUid)) { 160 throw new IllegalArgumentException( 161 "pendingIntentBackgroundActivityLaunchAllowedByPermission " 162 + "can not be set by creator of a PendingIntent"); 163 } 164 opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(false); 165 } 166 167 final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0; 168 final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0; 169 final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0; 170 flags &= ~(PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_CANCEL_CURRENT 171 | PendingIntent.FLAG_UPDATE_CURRENT); 172 173 PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId, 174 token, resultWho, requestCode, intents, resolvedTypes, flags, 175 new SafeActivityOptions(opts), userId); 176 WeakReference<PendingIntentRecord> ref; 177 ref = mIntentSenderRecords.get(key); 178 PendingIntentRecord rec = ref != null ? ref.get() : null; 179 if (rec != null) { 180 if (!cancelCurrent) { 181 if (updateCurrent) { 182 if (rec.key.requestIntent != null) { 183 rec.key.requestIntent.replaceExtras(intents != null ? 184 intents[intents.length - 1] : null); 185 } 186 if (intents != null) { 187 intents[intents.length - 1] = rec.key.requestIntent; 188 rec.key.allIntents = intents; 189 rec.key.allResolvedTypes = resolvedTypes; 190 } else { 191 rec.key.allIntents = null; 192 rec.key.allResolvedTypes = null; 193 } 194 } 195 return rec; 196 } 197 makeIntentSenderCanceled(rec, CANCEL_REASON_SUPERSEDED); 198 mIntentSenderRecords.remove(key); 199 decrementUidStatLocked(rec); 200 } 201 if (noCreate) { 202 return rec; 203 } 204 rec = new PendingIntentRecord(this, key, callingUid); 205 mIntentSenderRecords.put(key, rec.ref); 206 incrementUidStatLocked(rec); 207 return rec; 208 } 209 } 210 removePendingIntentsForPackage(String packageName, int userId, int appId, boolean doIt, @CancellationReason int cancelReason)211 boolean removePendingIntentsForPackage(String packageName, int userId, int appId, 212 boolean doIt, @CancellationReason int cancelReason) { 213 214 boolean didSomething = false; 215 synchronized (mLock) { 216 217 // Remove pending intents. For now we only do this when force stopping users, because 218 // we have some problems when doing this for packages -- app widgets are not currently 219 // cleaned up for such packages, so they can be left with bad pending intents. 220 if (mIntentSenderRecords.size() <= 0) { 221 return false; 222 } 223 224 Iterator<WeakReference<PendingIntentRecord>> it 225 = mIntentSenderRecords.values().iterator(); 226 while (it.hasNext()) { 227 WeakReference<PendingIntentRecord> wpir = it.next(); 228 if (wpir == null) { 229 it.remove(); 230 continue; 231 } 232 PendingIntentRecord pir = wpir.get(); 233 if (pir == null) { 234 it.remove(); 235 continue; 236 } 237 if (packageName == null) { 238 // Stopping user, remove all objects for the user. 239 if (pir.key.userId != userId) { 240 // Not the same user, skip it. 241 continue; 242 } 243 } else { 244 if (UserHandle.getAppId(pir.uid) != appId) { 245 // Different app id, skip it. 246 continue; 247 } 248 if (userId != UserHandle.USER_ALL && pir.key.userId != userId) { 249 // Different user, skip it. 250 continue; 251 } 252 if (!pir.key.packageName.equals(packageName)) { 253 // Different package, skip it. 254 continue; 255 } 256 } 257 if (!doIt) { 258 return true; 259 } 260 didSomething = true; 261 it.remove(); 262 makeIntentSenderCanceled(pir, cancelReason); 263 decrementUidStatLocked(pir); 264 if (pir.key.activity != null) { 265 final Message m = PooledLambda.obtainMessage( 266 PendingIntentController::clearPendingResultForActivity, this, 267 pir.key.activity, pir.ref); 268 mH.sendMessage(m); 269 } 270 } 271 } 272 273 return didSomething; 274 } 275 cancelIntentSender(IIntentSender sender)276 public void cancelIntentSender(IIntentSender sender) { 277 if (!(sender instanceof PendingIntentRecord)) { 278 return; 279 } 280 synchronized (mLock) { 281 final PendingIntentRecord rec = (PendingIntentRecord) sender; 282 try { 283 final int uid = AppGlobals.getPackageManager().getPackageUid(rec.key.packageName, 284 MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getCallingUserId()); 285 if (!UserHandle.isSameApp(uid, Binder.getCallingUid())) { 286 String msg = "Permission Denial: cancelIntentSender() from pid=" 287 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 288 + " is not allowed to cancel package " + rec.key.packageName; 289 Slog.w(TAG, msg); 290 throw new SecurityException(msg); 291 } 292 } catch (RemoteException e) { 293 throw new SecurityException(e); 294 } 295 cancelIntentSender(rec, true, CANCEL_REASON_OWNER_CANCELED); 296 } 297 } 298 cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity, @CancellationReason int cancelReason)299 public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity, 300 @CancellationReason int cancelReason) { 301 synchronized (mLock) { 302 makeIntentSenderCanceled(rec, cancelReason); 303 mIntentSenderRecords.remove(rec.key); 304 decrementUidStatLocked(rec); 305 if (cleanActivity && rec.key.activity != null) { 306 final Message m = PooledLambda.obtainMessage( 307 PendingIntentController::clearPendingResultForActivity, this, 308 rec.key.activity, rec.ref); 309 mH.sendMessage(m); 310 } 311 } 312 } 313 registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver)314 boolean registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) { 315 if (!(sender instanceof PendingIntentRecord)) { 316 Slog.w(TAG, "registerIntentSenderCancelListener called on non-PendingIntentRecord"); 317 // In this case, it's not "success", but we don't know if it's canceld either. 318 return true; 319 } 320 boolean isCancelled; 321 synchronized (mLock) { 322 PendingIntentRecord pendingIntent = (PendingIntentRecord) sender; 323 isCancelled = pendingIntent.canceled; 324 if (!isCancelled) { 325 pendingIntent.registerCancelListenerLocked(receiver); 326 return true; 327 } else { 328 return false; 329 } 330 } 331 } 332 unregisterIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver)333 void unregisterIntentSenderCancelListener(IIntentSender sender, 334 IResultReceiver receiver) { 335 if (!(sender instanceof PendingIntentRecord)) { 336 return; 337 } 338 synchronized (mLock) { 339 ((PendingIntentRecord) sender).unregisterCancelListenerLocked(receiver); 340 } 341 } 342 setPendingIntentAllowlistDuration(IIntentSender target, IBinder allowlistToken, long duration, int type, @PowerWhitelistManager.ReasonCode int reasonCode, @Nullable String reason)343 void setPendingIntentAllowlistDuration(IIntentSender target, IBinder allowlistToken, 344 long duration, int type, @PowerWhitelistManager.ReasonCode int reasonCode, 345 @Nullable String reason) { 346 if (!(target instanceof PendingIntentRecord)) { 347 Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target); 348 return; 349 } 350 synchronized (mLock) { 351 ((PendingIntentRecord) target).setAllowlistDurationLocked(allowlistToken, duration, 352 type, reasonCode, reason); 353 } 354 } 355 getPendingIntentFlags(IIntentSender target)356 int getPendingIntentFlags(IIntentSender target) { 357 if (!(target instanceof PendingIntentRecord)) { 358 Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target); 359 return 0; 360 } 361 synchronized (mLock) { 362 return ((PendingIntentRecord) target).key.flags; 363 } 364 } 365 makeIntentSenderCanceled(PendingIntentRecord rec, @CancellationReason int cancelReason)366 private void makeIntentSenderCanceled(PendingIntentRecord rec, 367 @CancellationReason int cancelReason) { 368 rec.canceled = true; 369 rec.cancelReason = cancelReason; 370 final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked(); 371 if (callbacks != null) { 372 final Message m = PooledLambda.obtainMessage( 373 PendingIntentController::handlePendingIntentCancelled, this, callbacks); 374 mH.sendMessage(m); 375 } 376 final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class); 377 ami.remove(new PendingIntent(rec)); 378 } 379 handlePendingIntentCancelled(RemoteCallbackList<IResultReceiver> callbacks)380 private void handlePendingIntentCancelled(RemoteCallbackList<IResultReceiver> callbacks) { 381 int N = callbacks.beginBroadcast(); 382 for (int i = 0; i < N; i++) { 383 try { 384 callbacks.getBroadcastItem(i).send(Activity.RESULT_CANCELED, null); 385 } catch (RemoteException e) { 386 // Process is not longer running...whatever. 387 } 388 } 389 callbacks.finishBroadcast(); 390 // We have to clean up the RemoteCallbackList here, because otherwise it will 391 // needlessly hold the enclosed callbacks until the remote process dies. 392 callbacks.kill(); 393 } 394 clearPendingResultForActivity(IBinder activityToken, WeakReference<PendingIntentRecord> pir)395 private void clearPendingResultForActivity(IBinder activityToken, 396 WeakReference<PendingIntentRecord> pir) { 397 mAtmInternal.clearPendingResultForActivity(activityToken, pir); 398 } 399 dumpPendingIntents(PrintWriter pw, boolean dumpAll, String dumpPackage)400 void dumpPendingIntents(PrintWriter pw, boolean dumpAll, String dumpPackage) { 401 synchronized (mLock) { 402 boolean printed = false; 403 404 pw.println("ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)"); 405 406 if (mIntentSenderRecords.size() > 0) { 407 // Organize these by package name, so they are easier to read. 408 final ArrayMap<String, ArrayList<PendingIntentRecord>> byPackage = new ArrayMap<>(); 409 final ArrayList<WeakReference<PendingIntentRecord>> weakRefs = new ArrayList<>(); 410 final Iterator<WeakReference<PendingIntentRecord>> it 411 = mIntentSenderRecords.values().iterator(); 412 while (it.hasNext()) { 413 WeakReference<PendingIntentRecord> ref = it.next(); 414 PendingIntentRecord rec = ref != null ? ref.get() : null; 415 if (rec == null) { 416 weakRefs.add(ref); 417 continue; 418 } 419 if (dumpPackage != null && !dumpPackage.equals(rec.key.packageName)) { 420 continue; 421 } 422 ArrayList<PendingIntentRecord> list = byPackage.get(rec.key.packageName); 423 if (list == null) { 424 list = new ArrayList<>(); 425 byPackage.put(rec.key.packageName, list); 426 } 427 list.add(rec); 428 } 429 for (int i = 0; i < byPackage.size(); i++) { 430 ArrayList<PendingIntentRecord> intents = byPackage.valueAt(i); 431 printed = true; 432 pw.print(" * "); pw.print(byPackage.keyAt(i)); 433 pw.print(": "); pw.print(intents.size()); pw.println(" items"); 434 for (int j = 0; j < intents.size(); j++) { 435 pw.print(" #"); pw.print(j); pw.print(": "); pw.println(intents.get(j)); 436 if (dumpAll) { 437 intents.get(j).dump(pw, " "); 438 } 439 } 440 } 441 if (weakRefs.size() > 0) { 442 printed = true; 443 pw.println(" * WEAK REFS:"); 444 for (int i = 0; i < weakRefs.size(); i++) { 445 pw.print(" #"); pw.print(i); pw.print(": "); pw.println(weakRefs.get(i)); 446 } 447 } 448 } 449 450 final int sizeOfIntentsPerUid = mIntentsPerUid.size(); 451 if (sizeOfIntentsPerUid > 0) { 452 for (int i = 0; i < sizeOfIntentsPerUid; i++) { 453 pw.print(" * UID: "); 454 pw.print(mIntentsPerUid.keyAt(i)); 455 pw.print(" total: "); 456 pw.println(mIntentsPerUid.valueAt(i)); 457 } 458 } 459 460 if (!printed) { 461 pw.println(" (nothing)"); 462 } 463 } 464 } 465 466 /** 467 * Provides some stats tracking of the current state of the PendingIntent queue. 468 * 469 * Data about the pending intent queue is intended to be used for memory impact tracking. 470 * Returned data (one per uid) will consist of instances of PendingIntentStats containing 471 * (I) number of PendingIntents and (II) total size of all bundled extras in the PIs. 472 * 473 * @hide 474 */ dumpPendingIntentStatsForStatsd()475 public List<PendingIntentStats> dumpPendingIntentStatsForStatsd() { 476 List<PendingIntentStats> pendingIntentStats = new ArrayList<>(); 477 478 synchronized (mLock) { 479 if (mIntentSenderRecords.size() > 0) { 480 // First, aggregate PendingIntent data by package uid. 481 final SparseIntArray countsByUid = new SparseIntArray(); 482 final SparseIntArray bundleSizesByUid = new SparseIntArray(); 483 484 for (WeakReference<PendingIntentRecord> reference : mIntentSenderRecords.values()) { 485 if (reference == null || reference.get() == null) { 486 continue; 487 } 488 PendingIntentRecord record = reference.get(); 489 int index = countsByUid.indexOfKey(record.uid); 490 491 if (index < 0) { // ie. the key was not found 492 countsByUid.put(record.uid, 1); 493 bundleSizesByUid.put(record.uid, 494 record.key.requestIntent.getExtrasTotalSize()); 495 } else { 496 countsByUid.put(record.uid, countsByUid.valueAt(index) + 1); 497 bundleSizesByUid.put(record.uid, 498 bundleSizesByUid.valueAt(index) 499 + record.key.requestIntent.getExtrasTotalSize()); 500 } 501 } 502 503 // Now generate the output. 504 for (int i = 0, size = countsByUid.size(); i < size; i++) { 505 pendingIntentStats.add(new PendingIntentStats( 506 countsByUid.keyAt(i), 507 countsByUid.valueAt(i), 508 /* NB: int conversion here */ bundleSizesByUid.valueAt(i) / 1024)); 509 } 510 } 511 } 512 return pendingIntentStats; 513 } 514 515 /** 516 * Increment the number of the PendingIntentRecord for the given uid, log a warning 517 * if there are too many for this uid already. 518 */ 519 @GuardedBy("mLock") incrementUidStatLocked(final PendingIntentRecord pir)520 void incrementUidStatLocked(final PendingIntentRecord pir) { 521 final int uid = pir.uid; 522 final int idx = mIntentsPerUid.indexOfKey(uid); 523 int newCount = 1; 524 if (idx >= 0) { 525 newCount = mIntentsPerUid.valueAt(idx) + 1; 526 mIntentsPerUid.setValueAt(idx, newCount); 527 } else { 528 mIntentsPerUid.put(uid, newCount); 529 } 530 531 // If the number is within the range [threshold - N + 1, threshold], log it into buffer 532 final int lowBound = mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N + 1; 533 RingBuffer<String> recentHistory = null; 534 if (newCount == lowBound) { 535 recentHistory = new RingBuffer(String.class, RECENT_N); 536 mRecentIntentsPerUid.put(uid, recentHistory); 537 } else if (newCount > lowBound && newCount <= mConstants.PENDINGINTENT_WARNING_THRESHOLD) { 538 recentHistory = mRecentIntentsPerUid.get(uid); 539 } 540 if (recentHistory == null) { 541 return; 542 } 543 544 recentHistory.append(pir.key.toString()); 545 546 // Output the log if we are hitting the threshold 547 if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD) { 548 Slog.wtf(TAG, "Too many PendingIntent created for uid " + uid 549 + ", recent " + RECENT_N + ": " + Arrays.toString(recentHistory.toArray())); 550 // Clear the buffer, as we don't want to spam the log when the numbers 551 // are jumping up and down around the threshold. 552 mRecentIntentsPerUid.remove(uid); 553 } 554 } 555 556 /** 557 * Decrement the number of the PendingIntentRecord for the given uid. 558 */ 559 @GuardedBy("mLock") decrementUidStatLocked(final PendingIntentRecord pir)560 void decrementUidStatLocked(final PendingIntentRecord pir) { 561 final int uid = pir.uid; 562 final int idx = mIntentsPerUid.indexOfKey(uid); 563 if (idx >= 0) { 564 final int newCount = mIntentsPerUid.valueAt(idx) - 1; 565 // If we are going below the low threshold, no need to keep logs. 566 if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N) { 567 mRecentIntentsPerUid.delete(uid); 568 } 569 if (newCount == 0) { 570 mIntentsPerUid.removeAt(idx); 571 } else { 572 mIntentsPerUid.setValueAt(idx, newCount); 573 } 574 } 575 } 576 } 577