1 /*
2  * Copyright (C) 2022 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.appop;
18 
19 import static android.app.AppOpsManager.MODE_ALLOWED;
20 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
21 import static android.app.AppOpsManager.makeKey;
22 
23 import android.annotation.IntRange;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.AppOpsManager;
27 import android.os.IBinder;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.SystemClock;
31 import android.util.ArrayMap;
32 import android.util.LongSparseArray;
33 import android.util.Pools;
34 import android.util.Slog;
35 
36 import com.android.internal.util.function.pooled.PooledLambda;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.NoSuchElementException;
41 
42 final class AttributedOp {
43     private final @NonNull AppOpsService mAppOpsService;
44     public final @Nullable String tag;
45     public final @NonNull AppOpsService.Op parent;
46 
47     /**
48      * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
49      *
50      * <p>Key is {@link AppOpsManager#makeKey}
51      */
52     // TODO(b/248108338)
53     // @GuardedBy("mAppOpsService")
54     private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
55 
56     /**
57      * Last rejected accesses for each uidState/opFlag combination
58      *
59      * <p>Key is {@link AppOpsManager#makeKey}
60      */
61     // TODO(b/248108338)
62     // @GuardedBy("mAppOpsService")
63     private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
64 
65     /**
66      * Currently in progress startOp events
67      *
68      * <p>Key is clientId
69      */
70     // TODO(b/248108338)
71     // @GuardedBy("mAppOpsService")
72     @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
73 
74     /**
75      * Currently paused startOp events
76      *
77      * <p>Key is clientId
78      */
79     // TODO(b/248108338)
80     // @GuardedBy("mAppOpsService")
81     @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
82 
AttributedOp(@onNull AppOpsService appOpsService, @Nullable String tag, @NonNull AppOpsService.Op parent)83     AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
84             @NonNull AppOpsService.Op parent) {
85         mAppOpsService = appOpsService;
86         this.tag = tag;
87         this.parent = parent;
88     }
89 
90     /**
91      * Update state when noteOp was rejected or startOp->finishOp event finished
92      *
93      * @param proxyUid            The uid of the proxy
94      * @param proxyPackageName    The package name of the proxy
95      * @param proxyAttributionTag the attributionTag in the proxies package
96      * @param uidState            UID state of the app noteOp/startOp was called for
97      * @param flags               OpFlags of the call
98      */
accessed(int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags)99     public void accessed(int proxyUid, @Nullable String proxyPackageName,
100             @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
101             @AppOpsManager.OpFlags int flags) {
102         long accessTime = System.currentTimeMillis();
103         accessed(accessTime, -1, proxyUid, proxyPackageName,
104                 proxyAttributionTag, uidState, flags);
105 
106         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
107                 parent.packageName, tag, uidState, flags, accessTime,
108                 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
109     }
110 
111     /**
112      * Add an access that was previously collected.
113      *
114      * @param noteTime            The time of the event
115      * @param duration            The duration of the event
116      * @param proxyUid            The uid of the proxy
117      * @param proxyPackageName    The package name of the proxy
118      * @param proxyAttributionTag the attributionTag in the proxies package
119      * @param uidState            UID state of the app noteOp/startOp was called for
120      * @param flags               OpFlags of the call
121      */
122     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
accessed(long noteTime, long duration, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags)123     public void accessed(long noteTime, long duration, int proxyUid,
124             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
125             @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
126         long key = makeKey(uidState, flags);
127 
128         if (mAccessEvents == null) {
129             mAccessEvents = new LongSparseArray<>(1);
130         }
131 
132         AppOpsManager.OpEventProxyInfo proxyInfo = null;
133         if (proxyUid != Process.INVALID_UID) {
134             proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
135                     proxyAttributionTag);
136         }
137 
138         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
139         if (existingEvent != null) {
140             existingEvent.reinit(noteTime, duration, proxyInfo,
141                     mAppOpsService.mOpEventProxyInfoPool);
142         } else {
143             mAccessEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, duration, proxyInfo));
144         }
145     }
146 
147     /**
148      * Update state when noteOp/startOp was rejected.
149      *
150      * @param uidState UID state of the app noteOp is called for
151      * @param flags    OpFlags of the call
152      */
rejected(@ppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags)153     public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
154         rejected(System.currentTimeMillis(), uidState, flags);
155 
156         mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid,
157                 parent.packageName, tag, uidState, flags);
158     }
159 
160     /**
161      * Add an rejection that was previously collected
162      *
163      * @param noteTime The time of the event
164      * @param uidState UID state of the app noteOp/startOp was called for
165      * @param flags    OpFlags of the call
166      */
167     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
rejected(long noteTime, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags)168     public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
169             @AppOpsManager.OpFlags int flags) {
170         long key = makeKey(uidState, flags);
171 
172         if (mRejectEvents == null) {
173             mRejectEvents = new LongSparseArray<>(1);
174         }
175 
176         // We do not collect proxy information for rejections yet
177         AppOpsManager.NoteOpEvent existingEvent = mRejectEvents.get(key);
178         if (existingEvent != null) {
179             existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool);
180         } else {
181             mRejectEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, -1, null));
182         }
183     }
184 
185     /**
186      * Update state when start was called
187      *
188      * @param clientId            Id of the startOp caller
189      * @param proxyUid            The UID of the proxy app
190      * @param proxyPackageName    The package name of the proxy app
191      * @param proxyAttributionTag The attribution tag of the proxy app
192      * @param uidState            UID state of the app startOp is called for
193      * @param flags               The proxy flags
194      * @param attributionFlags    The attribution flags associated with this operation.
195      * @param attributionChainId  The if of the attribution chain this operations is a part of.
196      */
started(@onNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)197     public void started(@NonNull IBinder clientId, int proxyUid,
198             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
199             @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
200             @AppOpsManager.AttributionFlags
201                     int attributionFlags, int attributionChainId) throws RemoteException {
202         started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
203                 uidState, flags, /*triggerCallbackIfNeeded*/ true, attributionFlags,
204                 attributionChainId);
205     }
206 
started(@onNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)207     private void started(@NonNull IBinder clientId, int proxyUid,
208             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
209             @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
210             boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags,
211             int attributionChainId) throws RemoteException {
212         startedOrPaused(clientId, proxyUid, proxyPackageName,
213                 proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
214                 /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
215     }
216 
217     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
startedOrPaused(@onNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)218     private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
219             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
220             @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
221             boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags
222             int attributionFlags, int attributionChainId) throws RemoteException {
223         if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
224             mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
225                     parent.packageName, tag, true, attributionFlags, attributionChainId);
226         }
227 
228         if (isStarted && mInProgressEvents == null) {
229             mInProgressEvents = new ArrayMap<>(1);
230         } else if (!isStarted && mPausedInProgressEvents == null) {
231             mPausedInProgressEvents = new ArrayMap<>(1);
232         }
233         ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
234                 ? mInProgressEvents : mPausedInProgressEvents;
235 
236         long startTime = System.currentTimeMillis();
237         InProgressStartOpEvent event = events.get(clientId);
238         if (event == null) {
239             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
240                     SystemClock.elapsedRealtime(), clientId, tag,
241                     PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
242                     proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
243                     attributionFlags, attributionChainId);
244             events.put(clientId, event);
245         } else {
246             if (uidState != event.getUidState()) {
247                 onUidStateChanged(uidState);
248             }
249         }
250 
251         event.mNumUnfinishedStarts++;
252 
253         if (isStarted) {
254             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
255                     parent.packageName, tag, uidState, flags, startTime, attributionFlags,
256                     attributionChainId);
257         }
258     }
259 
260     /**
261      * Update state when finishOp was called. Will finish started ops, and delete paused ops.
262      *
263      * @param clientId Id of the finishOp caller
264      */
finished(@onNull IBinder clientId)265     public void finished(@NonNull IBinder clientId) {
266         finished(clientId, true);
267     }
268 
finished(@onNull IBinder clientId, boolean triggerCallbackIfNeeded)269     private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
270         finishOrPause(clientId, triggerCallbackIfNeeded, false);
271     }
272 
273     /**
274      * Update state when paused or finished is called. If pausing, it records the op as
275      * stopping in the HistoricalRegistry, but does not delete it.
276      */
277     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
finishOrPause(@onNull IBinder clientId, boolean triggerCallbackIfNeeded, boolean isPausing)278     private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
279             boolean isPausing) {
280         int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
281         if (indexOfToken < 0) {
282             finishPossiblyPaused(clientId, isPausing);
283             return;
284         }
285 
286         InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
287         if (!isPausing) {
288             event.mNumUnfinishedStarts--;
289         }
290         // If we are pausing, create a NoteOpEvent, but don't change the InProgress event
291         if (event.mNumUnfinishedStarts == 0 || isPausing) {
292             if (!isPausing) {
293                 event.finish();
294                 mInProgressEvents.removeAt(indexOfToken);
295             }
296 
297             if (mAccessEvents == null) {
298                 mAccessEvents = new LongSparseArray<>(1);
299             }
300 
301             AppOpsManager.OpEventProxyInfo proxyCopy = event.getProxy() != null
302                     ? new AppOpsManager.OpEventProxyInfo(event.getProxy()) : null;
303 
304             long accessDurationMillis =
305                     SystemClock.elapsedRealtime() - event.getStartElapsedTime();
306             AppOpsManager.NoteOpEvent finishedEvent = new AppOpsManager.NoteOpEvent(
307                     event.getStartTime(),
308                     accessDurationMillis, proxyCopy);
309             mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
310                     finishedEvent);
311 
312             mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
313                     parent.packageName, tag, event.getUidState(),
314                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
315                     event.getAttributionFlags(), event.getAttributionChainId());
316 
317             if (!isPausing) {
318                 mAppOpsService.mInProgressStartOpEventPool.release(event);
319                 if (mInProgressEvents.isEmpty()) {
320                     mInProgressEvents = null;
321 
322                     // TODO ntmyren: Also callback for single attribution tag activity changes
323                     if (triggerCallbackIfNeeded && !parent.isRunning()) {
324                         mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
325                                 parent.uid, parent.packageName, tag, false,
326                                 event.getAttributionFlags(), event.getAttributionChainId());
327                     }
328                 }
329             }
330         }
331     }
332 
333     // Finish or pause (no-op) an already paused op
334     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
finishPossiblyPaused(@onNull IBinder clientId, boolean isPausing)335     private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
336         if (!isPaused()) {
337             Slog.wtf(AppOpsService.TAG, "No ops running or paused");
338             return;
339         }
340 
341         int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
342         if (indexOfToken < 0) {
343             Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
344             return;
345         } else if (isPausing) {
346             // already paused
347             return;
348         }
349 
350         // no need to record a paused event finishing.
351         InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken);
352         event.mNumUnfinishedStarts--;
353         if (event.mNumUnfinishedStarts == 0) {
354             mPausedInProgressEvents.removeAt(indexOfToken);
355             mAppOpsService.mInProgressStartOpEventPool.release(event);
356             if (mPausedInProgressEvents.isEmpty()) {
357                 mPausedInProgressEvents = null;
358             }
359         }
360     }
361 
362     /**
363      * Create an event that will be started, if the op is unpaused.
364      */
createPaused(@onNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)365     public void createPaused(@NonNull IBinder clientId, int proxyUid,
366             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
367             @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
368             @AppOpsManager.AttributionFlags
369                     int attributionFlags, int attributionChainId) throws RemoteException {
370         startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
371                 uidState, flags, true, false, attributionFlags, attributionChainId);
372     }
373 
374     /**
375      * Pause all currently started ops. This will create a HistoricalRegistry
376      */
pause()377     public void pause() {
378         if (!isRunning()) {
379             return;
380         }
381 
382         if (mPausedInProgressEvents == null) {
383             mPausedInProgressEvents = new ArrayMap<>(1);
384         }
385 
386         for (int i = 0; i < mInProgressEvents.size(); i++) {
387             InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
388             mPausedInProgressEvents.put(event.getClientId(), event);
389             finishOrPause(event.getClientId(), true, true);
390 
391             mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
392                     parent.packageName, tag, false,
393                     event.getAttributionFlags(), event.getAttributionChainId());
394         }
395         mInProgressEvents = null;
396     }
397 
398     /**
399      * Unpause all currently paused ops. This will reinitialize their start and duration
400      * times, but keep all other values the same
401      */
resume()402     public void resume() {
403         if (!isPaused()) {
404             return;
405         }
406 
407         if (mInProgressEvents == null) {
408             mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size());
409         }
410         boolean shouldSendActive = !mPausedInProgressEvents.isEmpty()
411                 && mInProgressEvents.isEmpty();
412 
413         long startTime = System.currentTimeMillis();
414         for (int i = 0; i < mPausedInProgressEvents.size(); i++) {
415             InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
416             mInProgressEvents.put(event.getClientId(), event);
417             event.setStartElapsedTime(SystemClock.elapsedRealtime());
418             event.setStartTime(startTime);
419             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
420                     parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
421                     event.getAttributionFlags(), event.getAttributionChainId());
422             if (shouldSendActive) {
423                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
424                         parent.packageName, tag, true, event.getAttributionFlags(),
425                         event.getAttributionChainId());
426             }
427             // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
428             // TODO ntmyren: figure out how to get the real mode.
429             mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
430                     parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
431                     event.getAttributionFlags(), event.getAttributionChainId());
432         }
433         mPausedInProgressEvents = null;
434     }
435 
436     /**
437      * Called in the case the client dies without calling finish first
438      *
439      * @param clientId The client that died
440      */
onClientDeath(@onNull IBinder clientId)441     void onClientDeath(@NonNull IBinder clientId) {
442         synchronized (mAppOpsService) {
443             if (!isPaused() && !isRunning()) {
444                 return;
445             }
446 
447             ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused()
448                     ? mPausedInProgressEvents : mInProgressEvents;
449             InProgressStartOpEvent deadEvent = events.get(clientId);
450             if (deadEvent != null) {
451                 deadEvent.mNumUnfinishedStarts = 1;
452             }
453 
454             finished(clientId);
455         }
456     }
457 
458     /**
459      * Notify that the state of the uid changed
460      *
461      * @param newState The new state
462      */
onUidStateChanged(@ppOpsManager.UidState int newState)463     public void onUidStateChanged(@AppOpsManager.UidState int newState) {
464         if (!isPaused() && !isRunning()) {
465             return;
466         }
467 
468         boolean isRunning = isRunning();
469         ArrayMap<IBinder, InProgressStartOpEvent> events =
470                 isRunning ? mInProgressEvents : mPausedInProgressEvents;
471 
472         int numInProgressEvents = events.size();
473         List<IBinder> binders = new ArrayList<>(events.keySet());
474         for (int i = 0; i < numInProgressEvents; i++) {
475             InProgressStartOpEvent event = events.get(binders.get(i));
476 
477             if (event != null && event.getUidState() != newState) {
478                 try {
479                     // Remove all but one unfinished start count and then call finished() to
480                     // remove start event object
481                     int numPreviousUnfinishedStarts = event.mNumUnfinishedStarts;
482                     event.mNumUnfinishedStarts = 1;
483                     AppOpsManager.OpEventProxyInfo proxy = event.getProxy();
484 
485                     finished(event.getClientId(), false);
486 
487                     // Call started() to add a new start event object and then add the
488                     // previously removed unfinished start counts back
489                     if (proxy != null) {
490                         startedOrPaused(event.getClientId(), proxy.getUid(),
491                                 proxy.getPackageName(), proxy.getAttributionTag(), newState,
492                                 event.getFlags(), false, isRunning,
493                                 event.getAttributionFlags(), event.getAttributionChainId());
494                     } else {
495                         startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
496                                 newState, event.getFlags(), false, isRunning,
497                                 event.getAttributionFlags(), event.getAttributionChainId());
498                     }
499 
500                     events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
501                     InProgressStartOpEvent newEvent = events.get(binders.get(i));
502                     if (newEvent != null) {
503                         newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
504                     }
505                 } catch (RemoteException e) {
506                     if (AppOpsService.DEBUG) {
507                         Slog.e(AppOpsService.TAG,
508                                 "Cannot switch to new uidState " + newState);
509                     }
510                 }
511             }
512         }
513     }
514 
515     /**
516      * Combine {@code a} and {@code b} and return the result. The result might be {@code a}
517      * or {@code b}. If there is an event for the same key in both the later event is retained.
518      */
add( @ullable LongSparseArray<AppOpsManager.NoteOpEvent> a, @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> b)519     private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> add(
520             @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> a,
521             @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> b) {
522         if (a == null) {
523             return b;
524         }
525 
526         if (b == null) {
527             return a;
528         }
529 
530         int numEventsToAdd = b.size();
531         for (int i = 0; i < numEventsToAdd; i++) {
532             long keyOfEventToAdd = b.keyAt(i);
533             AppOpsManager.NoteOpEvent bEvent = b.valueAt(i);
534             AppOpsManager.NoteOpEvent aEvent = a.get(keyOfEventToAdd);
535 
536             if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) {
537                 a.put(keyOfEventToAdd, bEvent);
538             }
539         }
540 
541         return a;
542     }
543 
544     /**
545      * Add all data from the {@code opToAdd} to this op.
546      *
547      * <p>If there is an event for the same key in both the later event is retained.
548      * <p>{@code opToAdd} should not be used after this method is called.
549      *
550      * @param opToAdd The op to add
551      */
552     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
add(@onNull AttributedOp opToAdd)553     public void add(@NonNull AttributedOp opToAdd) {
554         if (opToAdd.isRunning() || opToAdd.isPaused()) {
555             ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
556                     opToAdd.isRunning()
557                             ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
558             Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
559                     + opToAdd.isRunning());
560 
561             int numInProgressEvents = ignoredEvents.size();
562             for (int i = 0; i < numInProgressEvents; i++) {
563                 InProgressStartOpEvent event = ignoredEvents.valueAt(i);
564 
565                 event.finish();
566                 mAppOpsService.mInProgressStartOpEventPool.release(event);
567             }
568         }
569 
570         mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
571         mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
572     }
573 
isRunning()574     public boolean isRunning() {
575         return mInProgressEvents != null && !mInProgressEvents.isEmpty();
576     }
577 
isPaused()578     public boolean isPaused() {
579         return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
580     }
581 
hasAnyTime()582     boolean hasAnyTime() {
583         return (mAccessEvents != null && mAccessEvents.size() > 0)
584                 || (mRejectEvents != null && mRejectEvents.size() > 0);
585     }
586 
587     /**
588      * Clone a {@link LongSparseArray} and clone all values.
589      */
deepClone( @ullable LongSparseArray<AppOpsManager.NoteOpEvent> original)590     private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> deepClone(
591             @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> original) {
592         if (original == null) {
593             return original;
594         }
595 
596         int size = original.size();
597         LongSparseArray<AppOpsManager.NoteOpEvent> clone = new LongSparseArray<>(size);
598         for (int i = 0; i < size; i++) {
599             clone.put(original.keyAt(i), new AppOpsManager.NoteOpEvent(original.valueAt(i)));
600         }
601 
602         return clone;
603     }
604 
createAttributedOpEntryLocked()605     @NonNull AppOpsManager.AttributedOpEntry createAttributedOpEntryLocked() {
606         LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = deepClone(mAccessEvents);
607 
608         // Add in progress events as access events
609         if (isRunning()) {
610             long now = SystemClock.elapsedRealtime();
611             int numInProgressEvents = mInProgressEvents.size();
612 
613             if (accessEvents == null) {
614                 accessEvents = new LongSparseArray<>(numInProgressEvents);
615             }
616 
617             for (int i = 0; i < numInProgressEvents; i++) {
618                 InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
619 
620                 accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
621                         new AppOpsManager.NoteOpEvent(event.getStartTime(),
622                                 now - event.getStartElapsedTime(),
623                                 event.getProxy()));
624             }
625         }
626 
627         LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = deepClone(mRejectEvents);
628 
629         return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents,
630                 rejectEvents);
631     }
632 
633     /** A in progress startOp->finishOp event */
634     static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
635         /** Wall clock time of startOp event (not monotonic) */
636         private long mStartTime;
637 
638         /** Elapsed time since boot of startOp event */
639         private long mStartElapsedTime;
640 
641         /** Id of the client that started the event */
642         private @NonNull IBinder mClientId;
643 
644         /** The attribution tag for this operation */
645         private @Nullable String mAttributionTag;
646 
647         /** To call when client dies */
648         private @NonNull Runnable mOnDeath;
649 
650         /** uidstate used when calling startOp */
651         private @AppOpsManager.UidState int mUidState;
652 
653         /** Proxy information of the startOp event */
654         private @Nullable AppOpsManager.OpEventProxyInfo mProxy;
655 
656         /** Proxy flag information */
657         private @AppOpsManager.OpFlags int mFlags;
658 
659         /** How many times the op was started but not finished yet */
660         int mNumUnfinishedStarts;
661 
662         /** The attribution flags related to this event */
663         private @AppOpsManager.AttributionFlags int mAttributionFlags;
664 
665         /** The id of the attribution chain this even is a part of */
666         private int mAttributionChainId;
667 
668         /**
669          * Create a new {@link InProgressStartOpEvent}.
670          *
671          * @param startTime          The time {@link #startOperation} was called
672          * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
673          * @param clientId           The client id of the caller of {@link #startOperation}
674          * @param attributionTag     The attribution tag for the operation.
675          * @param onDeath            The code to execute on client death
676          * @param uidState           The uidstate of the app {@link #startOperation} was called for
677          * @param attributionFlags   the attribution flags for this operation.
678          * @param attributionChainId the unique id of the attribution chain this op is a part of.
679          * @param proxy              The proxy information, if {@link #startProxyOperation} was
680          *                           called
681          * @param flags              The trusted/nontrusted/self flags.
682          * @throws RemoteException If the client is dying
683          */
InProgressStartOpEvent(long startTime, long startElapsedTime, @NonNull IBinder clientId, @Nullable String attributionTag, @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)684         InProgressStartOpEvent(long startTime, long startElapsedTime,
685                 @NonNull IBinder clientId, @Nullable String attributionTag,
686                 @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
687                 @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
688                 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
689                 throws RemoteException {
690             mStartTime = startTime;
691             mStartElapsedTime = startElapsedTime;
692             mClientId = clientId;
693             mAttributionTag = attributionTag;
694             mOnDeath = onDeath;
695             mUidState = uidState;
696             mProxy = proxy;
697             mFlags = flags;
698             mAttributionFlags = attributionFlags;
699             mAttributionChainId = attributionChainId;
700 
701             clientId.linkToDeath(this, 0);
702         }
703 
704         /** Clean up event */
finish()705         public void finish() {
706             try {
707                 mClientId.unlinkToDeath(this, 0);
708             } catch (NoSuchElementException e) {
709                 // Either not linked, or already unlinked. Either way, nothing to do.
710             }
711         }
712 
713         @Override
binderDied()714         public void binderDied() {
715             mOnDeath.run();
716         }
717 
718         /**
719          * Reinit existing object with new state.
720          *
721          * @param startTime          The time {@link #startOperation} was called
722          * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
723          * @param clientId           The client id of the caller of {@link #startOperation}
724          * @param attributionTag     The attribution tag for this operation.
725          * @param onDeath            The code to execute on client death
726          * @param uidState           The uidstate of the app {@link #startOperation} was called for
727          * @param flags              The flags relating to the proxy
728          * @param proxy              The proxy information, if {@link #startProxyOperation}
729          *                           was called
730          * @param attributionFlags   the attribution flags for this operation.
731          * @param attributionChainId the unique id of the attribution chain this op is a part of.
732          * @param proxyPool          The pool to release
733          *                           previous {@link AppOpsManager.OpEventProxyInfo} to
734          * @throws RemoteException If the client is dying
735          */
reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, @Nullable String attributionTag, @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, @NonNull Pools.Pool<AppOpsManager.OpEventProxyInfo> proxyPool )736         public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
737                 @Nullable String attributionTag, @NonNull Runnable onDeath,
738                 @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
739                 @Nullable AppOpsManager.OpEventProxyInfo proxy,
740                 @AppOpsManager.AttributionFlags int attributionFlags,
741                 int attributionChainId,
742                 @NonNull Pools.Pool<AppOpsManager.OpEventProxyInfo> proxyPool
743         ) throws RemoteException {
744             mStartTime = startTime;
745             mStartElapsedTime = startElapsedTime;
746             mClientId = clientId;
747             mAttributionTag = attributionTag;
748             mOnDeath = onDeath;
749             mUidState = uidState;
750             mFlags = flags;
751 
752             if (mProxy != null) {
753                 proxyPool.release(mProxy);
754             }
755             mProxy = proxy;
756             mAttributionFlags = attributionFlags;
757             mAttributionChainId = attributionChainId;
758 
759             clientId.linkToDeath(this, 0);
760         }
761 
762         /** @return Wall clock time of startOp event */
getStartTime()763         public long getStartTime() {
764             return mStartTime;
765         }
766 
767         /** @return Elapsed time since boot of startOp event */
getStartElapsedTime()768         public long getStartElapsedTime() {
769             return mStartElapsedTime;
770         }
771 
772         /** @return Id of the client that started the event */
getClientId()773         public @NonNull IBinder getClientId() {
774             return mClientId;
775         }
776 
777         /** @return uidstate used when calling startOp */
getUidState()778         public @AppOpsManager.UidState int getUidState() {
779             return mUidState;
780         }
781 
782         /** @return proxy tag for the access */
getProxy()783         public @Nullable AppOpsManager.OpEventProxyInfo getProxy() {
784             return mProxy;
785         }
786 
787         /** @return flags used for the access */
getFlags()788         public @AppOpsManager.OpFlags int getFlags() {
789             return mFlags;
790         }
791 
792         /** @return attributoin flags used for the access */
getAttributionFlags()793         public @AppOpsManager.AttributionFlags int getAttributionFlags() {
794             return mAttributionFlags;
795         }
796 
797         /** @return attribution chain id for the access */
getAttributionChainId()798         public int getAttributionChainId() {
799             return mAttributionChainId;
800         }
801 
setStartTime(long startTime)802         public void setStartTime(long startTime) {
803             mStartTime = startTime;
804         }
805 
setStartElapsedTime(long startElapsedTime)806         public void setStartElapsedTime(long startElapsedTime) {
807             mStartElapsedTime = startElapsedTime;
808         }
809     }
810 
811     /**
812      * An unsynchronized pool of {@link InProgressStartOpEvent} objects.
813      */
814     static class InProgressStartOpEventPool extends Pools.SimplePool<InProgressStartOpEvent> {
815         private OpEventProxyInfoPool mOpEventProxyInfoPool;
816 
InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool, int maxUnusedPooledObjects)817         InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool,
818                 int maxUnusedPooledObjects) {
819             super(maxUnusedPooledObjects);
820             this.mOpEventProxyInfoPool = opEventProxyInfoPool;
821         }
822 
acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)823         InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
824                 @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
825                 @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
826                 @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
827                 @AppOpsManager.AttributionFlags
828                         int attributionFlags, int attributionChainId) throws RemoteException {
829 
830             InProgressStartOpEvent recycled = acquire();
831 
832             AppOpsManager.OpEventProxyInfo proxyInfo = null;
833             if (proxyUid != Process.INVALID_UID) {
834                 proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
835                         proxyAttributionTag);
836             }
837 
838             if (recycled != null) {
839                 recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
840                         uidState, flags, proxyInfo, attributionFlags, attributionChainId,
841                         mOpEventProxyInfoPool);
842                 return recycled;
843             }
844 
845             return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
846                     onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
847         }
848     }
849 
850     /**
851      * An unsynchronized pool of {@link AppOpsManager.OpEventProxyInfo} objects.
852      */
853     static class OpEventProxyInfoPool extends Pools.SimplePool<AppOpsManager.OpEventProxyInfo> {
OpEventProxyInfoPool(int maxUnusedPooledObjects)854         OpEventProxyInfoPool(int maxUnusedPooledObjects) {
855             super(maxUnusedPooledObjects);
856         }
857 
acquire(@ntRangefrom = 0) int uid, @Nullable String packageName, @Nullable String attributionTag)858         AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
859                 @Nullable String packageName,
860                 @Nullable String attributionTag) {
861             AppOpsManager.OpEventProxyInfo recycled = acquire();
862             if (recycled != null) {
863                 recycled.reinit(uid, packageName, attributionTag);
864                 return recycled;
865             }
866 
867             return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
868         }
869     }
870 }
871