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