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