• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 package com.android.server.am;
17 
18 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
19 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
20 
21 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
22 import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager;
27 import android.app.ActivityManagerProto;
28 import android.app.IUidObserver;
29 import android.content.pm.PackageManager;
30 import android.os.Handler;
31 import android.os.RemoteCallbackList;
32 import android.os.RemoteException;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.util.Slog;
36 import android.util.SparseIntArray;
37 import android.util.proto.ProtoOutputStream;
38 import android.util.proto.ProtoUtils;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 
47 public class UidObserverController {
48     /** If a UID observer takes more than this long, send a WTF. */
49     private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
50 
51     private final Handler mHandler;
52 
53     private final Object mLock = new Object();
54 
55     @GuardedBy("mLock")
56     final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
57 
58     @GuardedBy("mLock")
59     private final ArrayList<ChangeRecord> mPendingUidChanges = new ArrayList<>();
60     @GuardedBy("mLock")
61     private final ArrayList<ChangeRecord> mAvailUidChanges = new ArrayList<>();
62 
63     private ChangeRecord[] mActiveUidChanges = new ChangeRecord[5];
64 
65     /** Total # of UID change events dispatched, shown in dumpsys. */
66     @GuardedBy("mLock")
67     private int mUidChangeDispatchCount;
68 
69     private final Runnable mDispatchRunnable = this::dispatchUidsChanged;
70 
71     /**
72      * This is for verifying the UID report flow.
73      */
74     private static final boolean VALIDATE_UID_STATES = true;
75     private final ActiveUids mValidateUids;
76 
UidObserverController(@onNull Handler handler)77     UidObserverController(@NonNull Handler handler) {
78         mHandler = handler;
79         mValidateUids = new ActiveUids(null /* service */, false /* postChangesToAtm */);
80     }
81 
register(@onNull IUidObserver observer, int which, int cutpoint, @NonNull String callingPackage, int callingUid)82     void register(@NonNull IUidObserver observer, int which, int cutpoint,
83             @NonNull String callingPackage, int callingUid) {
84         synchronized (mLock) {
85             mUidObservers.register(observer, new UidObserverRegistration(callingUid,
86                     callingPackage, which, cutpoint,
87                     ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
88                     == PackageManager.PERMISSION_GRANTED));
89         }
90     }
91 
unregister(@onNull IUidObserver observer)92     void unregister(@NonNull IUidObserver observer) {
93         synchronized (mLock) {
94             mUidObservers.unregister(observer);
95         }
96     }
97 
enqueueUidChange(@ullable ChangeRecord currentRecord, int uid, int change, int procState, long procStateSeq, int capability, boolean ephemeral)98     int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
99             long procStateSeq, int capability, boolean ephemeral) {
100         synchronized (mLock) {
101             if (mPendingUidChanges.size() == 0) {
102                 if (DEBUG_UID_OBSERVERS) {
103                     Slog.i(TAG_UID_OBSERVERS, "*** Enqueueing dispatch uid changed!");
104                 }
105                 mHandler.post(mDispatchRunnable);
106             }
107 
108             final ChangeRecord changeRecord = currentRecord != null
109                     ? currentRecord : getOrCreateChangeRecordLocked();
110             if (!changeRecord.isPending) {
111                 changeRecord.isPending = true;
112                 mPendingUidChanges.add(changeRecord);
113             } else {
114                 change = mergeWithPendingChange(change, changeRecord.change);
115             }
116 
117             changeRecord.uid = uid;
118             changeRecord.change = change;
119             changeRecord.procState = procState;
120             changeRecord.procStateSeq = procStateSeq;
121             changeRecord.capability = capability;
122             changeRecord.ephemeral = ephemeral;
123 
124             return changeRecord.change;
125         }
126     }
127 
getPendingUidChangesForTest()128     ArrayList<ChangeRecord> getPendingUidChangesForTest() {
129         return mPendingUidChanges;
130     }
131 
getValidateUidsForTest()132     ActiveUids getValidateUidsForTest() {
133         return mValidateUids;
134     }
135 
getDispatchRunnableForTest()136     Runnable getDispatchRunnableForTest() {
137         return mDispatchRunnable;
138     }
139 
140     @VisibleForTesting
mergeWithPendingChange(int currentChange, int pendingChange)141     static int mergeWithPendingChange(int currentChange, int pendingChange) {
142         // If there is no change in idle or active state, then keep whatever was pending.
143         if ((currentChange & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
144             currentChange |= (pendingChange & (UidRecord.CHANGE_IDLE
145                     | UidRecord.CHANGE_ACTIVE));
146         }
147         // If there is no change in cached or uncached state, then keep whatever was pending.
148         if ((currentChange & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
149             currentChange |= (pendingChange & (UidRecord.CHANGE_CACHED
150                     | UidRecord.CHANGE_UNCACHED));
151         }
152         // If this is a report of the UID being gone, then we shouldn't keep any previous
153         // report of it being active or cached.  (That is, a gone uid is never active,
154         // and never cached.)
155         if ((currentChange & UidRecord.CHANGE_GONE) != 0) {
156             currentChange &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
157         }
158         if ((pendingChange & UidRecord.CHANGE_CAPABILITY) != 0) {
159             currentChange |= UidRecord.CHANGE_CAPABILITY;
160         }
161         if ((pendingChange & UidRecord.CHANGE_PROCSTATE) != 0) {
162             currentChange |= UidRecord.CHANGE_PROCSTATE;
163         }
164         if ((pendingChange & UidRecord.CHANGE_PROCADJ) != 0) {
165             currentChange |= UidRecord.CHANGE_PROCADJ;
166         }
167         return currentChange;
168     }
169 
170     @GuardedBy("mLock")
getOrCreateChangeRecordLocked()171     private ChangeRecord getOrCreateChangeRecordLocked() {
172         final ChangeRecord changeRecord;
173         final int size = mAvailUidChanges.size();
174         if (size > 0) {
175             changeRecord = mAvailUidChanges.remove(size - 1);
176             if (DEBUG_UID_OBSERVERS) {
177                 Slog.i(TAG_UID_OBSERVERS, "Retrieving available item: " + changeRecord);
178             }
179         } else {
180             changeRecord = new ChangeRecord();
181             if (DEBUG_UID_OBSERVERS) {
182                 Slog.i(TAG_UID_OBSERVERS, "Allocating new item: " + changeRecord);
183             }
184         }
185         return changeRecord;
186     }
187 
188     @VisibleForTesting
dispatchUidsChanged()189     void dispatchUidsChanged() {
190         final int numUidChanges;
191         synchronized (mLock) {
192             numUidChanges = mPendingUidChanges.size();
193             if (mActiveUidChanges.length < numUidChanges) {
194                 mActiveUidChanges = new ChangeRecord[numUidChanges];
195             }
196             for (int i = 0; i < numUidChanges; i++) {
197                 final ChangeRecord changeRecord = mPendingUidChanges.get(i);
198                 mActiveUidChanges[i] = getOrCreateChangeRecordLocked();
199                 changeRecord.copyTo(mActiveUidChanges[i]);
200                 changeRecord.isPending = false;
201             }
202             mPendingUidChanges.clear();
203             if (DEBUG_UID_OBSERVERS) {
204                 Slog.i(TAG_UID_OBSERVERS, "*** Delivering " + numUidChanges + " uid changes");
205             }
206             mUidChangeDispatchCount += numUidChanges;
207         }
208 
209         int i = mUidObservers.beginBroadcast();
210         while (i-- > 0) {
211             dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
212                     (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), numUidChanges);
213         }
214         mUidObservers.finishBroadcast();
215 
216         if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
217             for (int j = 0; j < numUidChanges; ++j) {
218                 final ChangeRecord item = mActiveUidChanges[j];
219                 if ((item.change & UidRecord.CHANGE_GONE) != 0) {
220                     mValidateUids.remove(item.uid);
221                 } else {
222                     UidRecord validateUid = mValidateUids.get(item.uid);
223                     if (validateUid == null) {
224                         validateUid = new UidRecord(item.uid, null);
225                         mValidateUids.put(item.uid, validateUid);
226                     }
227                     if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
228                         validateUid.setIdle(true);
229                     } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
230                         validateUid.setIdle(false);
231                     }
232                     validateUid.setSetProcState(item.procState);
233                     validateUid.setCurProcState(item.procState);
234                     validateUid.setSetCapability(item.capability);
235                     validateUid.setCurCapability(item.capability);
236                 }
237             }
238         }
239 
240         synchronized (mLock) {
241             for (int j = 0; j < numUidChanges; j++) {
242                 final ChangeRecord changeRecord = mActiveUidChanges[j];
243                 changeRecord.isPending = false;
244                 mAvailUidChanges.add(changeRecord);
245             }
246         }
247     }
248 
dispatchUidsChangedForObserver(@onNull IUidObserver observer, @NonNull UidObserverRegistration reg, int changesSize)249     private void dispatchUidsChangedForObserver(@NonNull IUidObserver observer,
250             @NonNull UidObserverRegistration reg, int changesSize) {
251         if (observer == null) {
252             return;
253         }
254         try {
255             for (int j = 0; j < changesSize; j++) {
256                 final ChangeRecord item = mActiveUidChanges[j];
257                 final long start = SystemClock.uptimeMillis();
258                 final int change = item.change;
259                 // Does the user have permission? Don't send a non user UID change otherwise
260                 if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
261                         && !reg.mCanInteractAcrossUsers) {
262                     continue;
263                 }
264                 if (change == UidRecord.CHANGE_PROCSTATE
265                         && (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
266                     // No-op common case: no significant change, the observer is not
267                     // interested in all proc state changes.
268                     continue;
269                 }
270                 if (change == UidRecord.CHANGE_PROCADJ
271                         && (reg.mWhich & ActivityManager.UID_OBSERVER_PROC_OOM_ADJ) == 0) {
272                     // No-op common case: no significant change, the observer is not
273                     // interested in proc adj changes.
274                     continue;
275                 }
276                 if ((change & UidRecord.CHANGE_IDLE) != 0) {
277                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
278                         if (DEBUG_UID_OBSERVERS) {
279                             Slog.i(TAG_UID_OBSERVERS, "UID idle uid=" + item.uid);
280                         }
281                         observer.onUidIdle(item.uid, item.ephemeral);
282                     }
283                 } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
284                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
285                         if (DEBUG_UID_OBSERVERS) {
286                             Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid);
287                         }
288                         observer.onUidActive(item.uid);
289                     }
290                 }
291                 if ((reg.mWhich & ActivityManager.UID_OBSERVER_CACHED) != 0) {
292                     if ((change & UidRecord.CHANGE_CACHED) != 0) {
293                         if (DEBUG_UID_OBSERVERS) {
294                             Slog.i(TAG_UID_OBSERVERS, "UID cached uid=" + item.uid);
295                         }
296                         observer.onUidCachedChanged(item.uid, true);
297                     } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
298                         if (DEBUG_UID_OBSERVERS) {
299                             Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid);
300                         }
301                         observer.onUidCachedChanged(item.uid, false);
302                     }
303                 }
304                 if ((change & UidRecord.CHANGE_GONE) != 0) {
305                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
306                         if (DEBUG_UID_OBSERVERS) {
307                             Slog.i(TAG_UID_OBSERVERS, "UID gone uid=" + item.uid);
308                         }
309                         observer.onUidGone(item.uid, item.ephemeral);
310                     }
311                     if (reg.mLastProcStates != null) {
312                         reg.mLastProcStates.delete(item.uid);
313                     }
314                 } else {
315                     boolean doReport = false;
316                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
317                         doReport = true;
318                         if (reg.mCutpoint >= ActivityManager.MIN_PROCESS_STATE) {
319                             final int lastState = reg.mLastProcStates.get(item.uid,
320                                     ActivityManager.PROCESS_STATE_UNKNOWN);
321                             if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
322                                 final boolean lastAboveCut = lastState <= reg.mCutpoint;
323                                 final boolean newAboveCut = item.procState <= reg.mCutpoint;
324                                 doReport = lastAboveCut != newAboveCut;
325                             } else {
326                                 doReport = item.procState != PROCESS_STATE_NONEXISTENT;
327                             }
328                         }
329                     }
330                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_CAPABILITY) != 0) {
331                         doReport |= (change & UidRecord.CHANGE_CAPABILITY) != 0;
332                     }
333                     if (doReport) {
334                         if (DEBUG_UID_OBSERVERS) {
335                             Slog.i(TAG_UID_OBSERVERS, "UID CHANGED uid=" + item.uid
336                                     + ": " + item.procState + ": " + item.capability);
337                         }
338                         if (reg.mLastProcStates != null) {
339                             reg.mLastProcStates.put(item.uid, item.procState);
340                         }
341                         observer.onUidStateChanged(item.uid, item.procState,
342                                 item.procStateSeq,
343                                 item.capability);
344                     }
345                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROC_OOM_ADJ) != 0
346                             && (change & UidRecord.CHANGE_PROCADJ) != 0) {
347                         observer.onUidProcAdjChanged(item.uid);
348                     }
349                 }
350                 final int duration = (int) (SystemClock.uptimeMillis() - start);
351                 if (reg.mMaxDispatchTime < duration) {
352                     reg.mMaxDispatchTime = duration;
353                 }
354                 if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
355                     reg.mSlowDispatchCount++;
356                 }
357             }
358         } catch (RemoteException e) {
359         }
360     }
361 
getValidateUidRecord(int uid)362     UidRecord getValidateUidRecord(int uid) {
363         return mValidateUids.get(uid);
364     }
365 
dump(@onNull PrintWriter pw, @Nullable String dumpPackage)366     void dump(@NonNull PrintWriter pw, @Nullable String dumpPackage) {
367         synchronized (mLock) {
368             final int count = mUidObservers.getRegisteredCallbackCount();
369             boolean printed = false;
370             for (int i = 0; i < count; i++) {
371                 final UidObserverRegistration reg = (UidObserverRegistration)
372                         mUidObservers.getRegisteredCallbackCookie(i);
373                 if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
374                     if (!printed) {
375                         pw.println("  mUidObservers:");
376                         printed = true;
377                     }
378                     reg.dump(pw, mUidObservers.getRegisteredCallbackItem(i));
379                 }
380             }
381 
382             pw.println();
383             pw.print("  mUidChangeDispatchCount=");
384             pw.print(mUidChangeDispatchCount);
385             pw.println();
386             pw.println("  Slow UID dispatches:");
387             for (int i = 0; i < count; i++) {
388                 final UidObserverRegistration reg = (UidObserverRegistration)
389                         mUidObservers.getRegisteredCallbackCookie(i);
390                 pw.print("    ");
391                 pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName());
392                 pw.print(": ");
393                 pw.print(reg.mSlowDispatchCount);
394                 pw.print(" / Max ");
395                 pw.print(reg.mMaxDispatchTime);
396                 pw.println("ms");
397             }
398         }
399     }
400 
dumpDebug(@onNull ProtoOutputStream proto, @Nullable String dumpPackage)401     void dumpDebug(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage) {
402         synchronized (mLock) {
403             final int count = mUidObservers.getRegisteredCallbackCount();
404             for (int i = 0; i < count; i++) {
405                 final UidObserverRegistration reg = (UidObserverRegistration)
406                         mUidObservers.getRegisteredCallbackCookie(i);
407                 if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
408                     reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
409                 }
410             }
411         }
412     }
413 
dumpValidateUids(@onNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId, @NonNull String header, boolean needSep)414     boolean dumpValidateUids(@NonNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId,
415             @NonNull String header, boolean needSep) {
416         return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
417     }
418 
dumpValidateUidsProto(@onNull ProtoOutputStream proto, @Nullable String dumpPackage, int dumpAppId, long fieldId)419     void dumpValidateUidsProto(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage,
420             int dumpAppId, long fieldId) {
421         mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
422     }
423 
424     static final class ChangeRecord {
425         public boolean isPending;
426         public int uid;
427         public int change;
428         public int procState;
429         public int capability;
430         public boolean ephemeral;
431         public long procStateSeq;
432 
copyTo(@onNull ChangeRecord changeRecord)433         void copyTo(@NonNull ChangeRecord changeRecord) {
434             changeRecord.isPending = isPending;
435             changeRecord.uid = uid;
436             changeRecord.change = change;
437             changeRecord.procState = procState;
438             changeRecord.capability = capability;
439             changeRecord.ephemeral = ephemeral;
440             changeRecord.procStateSeq = procStateSeq;
441         }
442     }
443 
444     private static final class UidObserverRegistration {
445         private final int mUid;
446         private final String mPkg;
447         private final int mWhich;
448         private final int mCutpoint;
449         private final boolean mCanInteractAcrossUsers;
450 
451         /**
452          * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
453          * We show it in dumpsys.
454          */
455         int mSlowDispatchCount;
456 
457         /** Max time it took for each dispatch. */
458         int mMaxDispatchTime;
459 
460         final SparseIntArray mLastProcStates;
461 
462         // Please keep the enum lists in sync
463         private static final int[] ORIG_ENUMS = new int[]{
464                 ActivityManager.UID_OBSERVER_IDLE,
465                 ActivityManager.UID_OBSERVER_ACTIVE,
466                 ActivityManager.UID_OBSERVER_GONE,
467                 ActivityManager.UID_OBSERVER_PROCSTATE,
468                 ActivityManager.UID_OBSERVER_CAPABILITY,
469                 ActivityManager.UID_OBSERVER_PROC_OOM_ADJ,
470         };
471         private static final int[] PROTO_ENUMS = new int[]{
472                 ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
473                 ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
474                 ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
475                 ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
476                 ActivityManagerProto.UID_OBSERVER_FLAG_CAPABILITY,
477                 ActivityManagerProto.UID_OBSERVER_FLAG_PROC_OOM_ADJ,
478         };
479 
UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint, boolean canInteractAcrossUsers)480         UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
481                 boolean canInteractAcrossUsers) {
482             this.mUid = uid;
483             this.mPkg = pkg;
484             this.mWhich = which;
485             this.mCutpoint = cutpoint;
486             this.mCanInteractAcrossUsers = canInteractAcrossUsers;
487             mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
488                     ? new SparseIntArray() : null;
489         }
490 
dump(@onNull PrintWriter pw, @NonNull IUidObserver observer)491         void dump(@NonNull PrintWriter pw, @NonNull IUidObserver observer) {
492             pw.print("    ");
493             UserHandle.formatUid(pw, mUid);
494             pw.print(" ");
495             pw.print(mPkg);
496             pw.print(" ");
497             pw.print(observer.getClass().getTypeName());
498             pw.print(":");
499             if ((mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
500                 pw.print(" IDLE");
501             }
502             if ((mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
503                 pw.print(" ACT");
504             }
505             if ((mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
506                 pw.print(" GONE");
507             }
508             if ((mWhich & ActivityManager.UID_OBSERVER_CAPABILITY) != 0) {
509                 pw.print(" CAP");
510             }
511             if ((mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
512                 pw.print(" STATE");
513                 pw.print(" (cut=");
514                 pw.print(mCutpoint);
515                 pw.print(")");
516             }
517             pw.println();
518             if (mLastProcStates != null) {
519                 final int size = mLastProcStates.size();
520                 for (int j = 0; j < size; j++) {
521                     pw.print("      Last ");
522                     UserHandle.formatUid(pw, mLastProcStates.keyAt(j));
523                     pw.print(": ");
524                     pw.println(mLastProcStates.valueAt(j));
525                 }
526             }
527         }
528 
dumpDebug(@onNull ProtoOutputStream proto, long fieldId)529         void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
530             final long token = proto.start(fieldId);
531             proto.write(UidObserverRegistrationProto.UID, mUid);
532             proto.write(UidObserverRegistrationProto.PACKAGE, mPkg);
533             ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
534                     mWhich, ORIG_ENUMS, PROTO_ENUMS);
535             proto.write(UidObserverRegistrationProto.CUT_POINT, mCutpoint);
536             if (mLastProcStates != null) {
537                 final int size = mLastProcStates.size();
538                 for (int i = 0; i < size; i++) {
539                     final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
540                     proto.write(UidObserverRegistrationProto.ProcState.UID,
541                             mLastProcStates.keyAt(i));
542                     proto.write(UidObserverRegistrationProto.ProcState.STATE,
543                             mLastProcStates.valueAt(i));
544                     proto.end(pToken);
545                 }
546             }
547             proto.end(token);
548         }
549     }
550 }
551