1 /*
2  * Copyright (C) 2023 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.power.stats;
18 
19 import android.util.IntArray;
20 import android.util.Slog;
21 import android.util.SparseIntArray;
22 
23 import java.io.PrintWriter;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 
28 /**
29  * Maintains a map of isolated UIDs to their respective owner UIDs, to support combining
30  * power stats for isolated UIDs, which are typically short-lived, into the corresponding app UID.
31  */
32 public class PowerStatsUidResolver {
33     private static final String TAG = "PowerStatsUidResolver";
34 
35     /**
36      * Listener notified when isolated UIDs are created and removed.
37      */
38     public interface Listener {
39 
40         /**
41          * Callback invoked when a new isolated UID is registered.
42          */
onIsolatedUidAdded(int isolatedUid, int parentUid)43         void onIsolatedUidAdded(int isolatedUid, int parentUid);
44 
45         /**
46          * Callback invoked before an isolated UID is evicted from the resolver.
47          * If the listener calls {@link PowerStatsUidResolver#retainIsolatedUid}, the mapping
48          * will be retained until {@link PowerStatsUidResolver#releaseIsolatedUid} is called.
49          */
onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid)50         void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid);
51 
52         /**
53          * Callback invoked when an isolated UID to owner UID mapping is removed.
54          */
onAfterIsolatedUidRemoved(int isolatedUid, int parentUid)55         void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid);
56     }
57 
58     /**
59      * Mapping isolated uids to the actual owning app uid.
60      */
61     private final SparseIntArray mIsolatedUids = new SparseIntArray();
62 
63     /**
64      * Internal reference count of isolated uids.
65      */
66     private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
67 
68     // Keep the list read-only in order to avoid locking during the delivery of listener calls.
69     private volatile List<Listener> mListeners = Collections.emptyList();
70 
71     /**
72      * Adds a listener.
73      */
addListener(Listener listener)74     public void addListener(Listener listener) {
75         synchronized (this) {
76             List<Listener> newList = new ArrayList<>(mListeners);
77             newList.add(listener);
78             mListeners = Collections.unmodifiableList(newList);
79         }
80     }
81 
82     /**
83      * Removes a listener.
84      */
removeListener(Listener listener)85     public void removeListener(Listener listener) {
86         synchronized (this) {
87             List<Listener> newList = new ArrayList<>(mListeners);
88             newList.remove(listener);
89             mListeners = Collections.unmodifiableList(newList);
90         }
91     }
92 
93     /**
94      * Remembers the connection between a newly created isolated UID and its owner app UID.
95      * Calls {@link Listener#onIsolatedUidAdded} on each registered listener.
96      */
noteIsolatedUidAdded(int isolatedUid, int parentUid)97     public void noteIsolatedUidAdded(int isolatedUid, int parentUid) {
98         synchronized (this) {
99             mIsolatedUids.put(isolatedUid, parentUid);
100             mIsolatedUidRefCounts.put(isolatedUid, 1);
101         }
102 
103         List<Listener> listeners = mListeners;
104         for (int i = listeners.size() - 1; i >= 0; i--) {
105             listeners.get(i).onIsolatedUidAdded(isolatedUid, parentUid);
106         }
107     }
108 
109     /**
110      * Handles the removal of an isolated UID by invoking
111      * {@link Listener#onBeforeIsolatedUidRemoved} on each registered listener and the releases
112      * the UID, see {@link #releaseIsolatedUid}.
113      */
noteIsolatedUidRemoved(int isolatedUid, int parentUid)114     public void noteIsolatedUidRemoved(int isolatedUid, int parentUid) {
115         synchronized (this) {
116             int curUid = mIsolatedUids.get(isolatedUid, -1);
117             if (curUid != parentUid) {
118                 Slog.wtf(TAG, "Attempt to remove an isolated UID " + isolatedUid
119                               + " with the parent UID " + parentUid
120                               + ". The registered parent UID is " + curUid);
121                 return;
122             }
123         }
124 
125         List<Listener> listeners = mListeners;
126         for (int i = listeners.size() - 1; i >= 0; i--) {
127             listeners.get(i).onBeforeIsolatedUidRemoved(isolatedUid, parentUid);
128         }
129 
130         releaseIsolatedUid(isolatedUid);
131     }
132 
133     /**
134      * Increments the ref count for an isolated uid.
135      * Call #releaseIsolatedUid to decrement.
136      */
retainIsolatedUid(int uid)137     public void retainIsolatedUid(int uid) {
138         synchronized (this) {
139             final int refCount = mIsolatedUidRefCounts.get(uid, 0);
140             if (refCount <= 0) {
141                 // Uid is not mapped or referenced
142                 Slog.w(TAG,
143                         "Attempted to increment ref counted of untracked isolated uid (" + uid
144                         + ")");
145                 return;
146             }
147             mIsolatedUidRefCounts.put(uid, refCount + 1);
148         }
149     }
150 
151     /**
152      * Decrements the ref count for the given isolated UID.  If the ref count drops to zero,
153      * removes the mapping and calls {@link Listener#onAfterIsolatedUidRemoved} on each registered
154      * listener.
155      */
releaseIsolatedUid(int isolatedUid)156     public void releaseIsolatedUid(int isolatedUid) {
157         int parentUid;
158         synchronized (this) {
159             final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
160             if (refCount > 0) {
161                 // Isolated uid is still being tracked
162                 mIsolatedUidRefCounts.put(isolatedUid, refCount);
163                 return;
164             }
165 
166             final int idx = mIsolatedUids.indexOfKey(isolatedUid);
167             if (idx >= 0) {
168                 parentUid = mIsolatedUids.valueAt(idx);
169                 mIsolatedUids.removeAt(idx);
170                 mIsolatedUidRefCounts.delete(isolatedUid);
171             } else {
172                 Slog.w(TAG, "Attempted to remove untracked child uid (" + isolatedUid + ")");
173                 return;
174             }
175         }
176 
177         List<Listener> listeners = mListeners;
178         for (int i = listeners.size() - 1; i >= 0; i--) {
179             listeners.get(i).onAfterIsolatedUidRemoved(isolatedUid, parentUid);
180         }
181     }
182 
183     /**
184      * Releases all isolated UIDs in the specified range, both ends inclusive.
185      */
releaseUidsInRange(int startUid, int endUid)186     public void releaseUidsInRange(int startUid, int endUid) {
187         IntArray toRelease;
188         synchronized (this) {
189             int startIndex = mIsolatedUids.indexOfKey(startUid);
190             int endIndex = mIsolatedUids.indexOfKey(endUid);
191 
192             if (startIndex < 0) {
193                 startIndex = ~startIndex;
194             }
195 
196             if (endIndex < 0) {
197                 // In this ~endIndex is pointing just past where endUid would be, so we must -1.
198                 endIndex = ~endIndex - 1;
199             }
200 
201             if (startIndex > endIndex) {
202                 return;
203             }
204 
205             toRelease = new IntArray(endIndex - startIndex);
206             for (int i = startIndex; i <= endIndex; i++) {
207                 toRelease.add(mIsolatedUids.keyAt(i));
208             }
209         }
210 
211         for (int i = toRelease.size() - 1; i >= 0; i--) {
212             releaseIsolatedUid(toRelease.get(i));
213         }
214     }
215 
216     /**
217      * Given an isolated UID, returns the corresponding owner UID.  For a non-isolated
218      * UID, returns the UID itself.
219      */
mapUid(int uid)220     public int mapUid(int uid) {
221         synchronized (this) {
222             return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid);
223         }
224     }
225 
226     /**
227      * Dumps the current contents of the resolver for the sake of dumpsys.
228      */
dump(PrintWriter pw)229     public void dump(PrintWriter pw) {
230         pw.println("Currently mapped isolated uids:");
231         synchronized (this) {
232             final int numIsolatedUids = mIsolatedUids.size();
233             for (int i = 0; i < numIsolatedUids; i++) {
234                 final int isolatedUid = mIsolatedUids.keyAt(i);
235                 final int ownerUid = mIsolatedUids.valueAt(i);
236                 final int refs = mIsolatedUidRefCounts.get(isolatedUid);
237                 pw.println("  " + isolatedUid + "->" + ownerUid + " (ref count = " + refs + ")");
238             }
239         }
240     }
241 }
242