• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.audio;
18 
19 import static android.Manifest.permission.ACCESS_ULTRASOUND;
20 import static android.Manifest.permission.BLUETOOTH_CONNECT;
21 import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION;
22 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
23 import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
24 import static android.Manifest.permission.CAPTURE_MEDIA_OUTPUT;
25 import static android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT;
26 import static android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT;
27 import static android.Manifest.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
28 import static android.Manifest.permission.MODIFY_AUDIO_ROUTING;
29 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS;
30 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
31 import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS;
32 import static android.Manifest.permission.MODIFY_PHONE_STATE;
33 import static android.Manifest.permission.RECORD_AUDIO;
34 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
35 
36 import android.annotation.Nullable;
37 import android.os.RemoteException;
38 import android.os.Trace;
39 import android.os.UserHandle;
40 import android.util.ArraySet;
41 import android.util.IntArray;
42 import android.util.Log;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.media.permission.INativePermissionController;
46 import com.android.media.permission.PermissionEnum;
47 import com.android.media.permission.UidPackageState;
48 import com.android.server.pm.pkg.PackageState;
49 
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Objects;
57 import java.util.Set;
58 import java.util.function.BiPredicate;
59 import java.util.function.Supplier;
60 import java.util.stream.Collector;
61 import java.util.stream.Collectors;
62 
63 /** Responsible for synchronizing system server permission state to the native audioserver. */
64 public class AudioServerPermissionProvider {
65 
66     static final String TAG = "AudioServerPermissionProvider";
67 
68     static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE];
69 
70     static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD,
71             PermissionEnum.CAPTURE_AUDIO_OUTPUT, PermissionEnum.RECORD_AUDIO};
72 
73     static {
74         MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO;
75         MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING;
76         MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS] = MODIFY_AUDIO_SETTINGS;
77         MONITORED_PERMS[PermissionEnum.MODIFY_PHONE_STATE] = MODIFY_PHONE_STATE;
78         MONITORED_PERMS[PermissionEnum.MODIFY_DEFAULT_AUDIO_EFFECTS] = MODIFY_DEFAULT_AUDIO_EFFECTS;
79         MONITORED_PERMS[PermissionEnum.WRITE_SECURE_SETTINGS] = WRITE_SECURE_SETTINGS;
80         MONITORED_PERMS[PermissionEnum.CALL_AUDIO_INTERCEPTION] = CALL_AUDIO_INTERCEPTION;
81         MONITORED_PERMS[PermissionEnum.ACCESS_ULTRASOUND] = ACCESS_ULTRASOUND;
82         MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT] = CAPTURE_AUDIO_OUTPUT;
83         MONITORED_PERMS[PermissionEnum.CAPTURE_MEDIA_OUTPUT] = CAPTURE_MEDIA_OUTPUT;
84         MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD] = CAPTURE_AUDIO_HOTWORD;
85         MONITORED_PERMS[PermissionEnum.CAPTURE_TUNER_AUDIO_INPUT] = CAPTURE_TUNER_AUDIO_INPUT;
86         MONITORED_PERMS[PermissionEnum.CAPTURE_VOICE_COMMUNICATION_OUTPUT] =
87                 CAPTURE_VOICE_COMMUNICATION_OUTPUT;
88         MONITORED_PERMS[PermissionEnum.BLUETOOTH_CONNECT] = BLUETOOTH_CONNECT;
89         MONITORED_PERMS[PermissionEnum.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION] =
90                 BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
91         MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS_PRIVILEGED] =
92                 MODIFY_AUDIO_SETTINGS_PRIVILEGED;
93     }
94 
95     private final Object mLock = new Object();
96     private final Supplier<int[]> mUserIdSupplier;
97     private final BiPredicate<Integer, String> mPermissionPredicate;
98 
99     @GuardedBy("mLock")
100     private INativePermissionController mDest;
101 
102     @GuardedBy("mLock")
103     private final Map<Integer, Set<String>> mPackageMap;
104 
105     // Values are sorted
106     @GuardedBy("mLock")
107     private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][];
108 
109     @GuardedBy("mLock")
110     private boolean mIsUpdateDeferred = true;
111 
112     @GuardedBy("mLock")
113     private int mHdsUid = -1;
114 
115     /**
116      * @param appInfos - PackageState for all apps on the device, used to populate init state
117      */
AudioServerPermissionProvider( Collection<PackageState> appInfos, BiPredicate<Integer, String> permissionPredicate, Supplier<int[]> userIdSupplier)118     public AudioServerPermissionProvider(
119             Collection<PackageState> appInfos,
120             BiPredicate<Integer, String> permissionPredicate,
121             Supplier<int[]> userIdSupplier) {
122         for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
123             Objects.requireNonNull(MONITORED_PERMS[i]);
124         }
125         mUserIdSupplier = userIdSupplier;
126         mPermissionPredicate = permissionPredicate;
127         // Initialize the package state
128         mPackageMap = generatePackageMappings(appInfos);
129     }
130 
131     /**
132      * Called whenever audioserver starts (or started before us)
133      *
134      * @param pc - The permission controller interface from audioserver, which we push updates to
135      */
onServiceStart(@ullable INativePermissionController pc)136     public void onServiceStart(@Nullable INativePermissionController pc) {
137         if (pc == null) return;
138         synchronized (mLock) {
139             mDest = pc;
140             resetNativePackageState();
141             try {
142                 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
143                     if (mIsUpdateDeferred) {
144                         mPermMap[i] = getUidsHoldingPerm(i);
145                     }
146                     mDest.populatePermissionState(i, mPermMap[i]);
147                 }
148                 mIsUpdateDeferred = false;
149             } catch (RemoteException e) {
150                 // We will re-init the state when the service comes back up
151                 mDest = null;
152             }
153         }
154     }
155 
156     /**
157      * Called when a package is added or removed
158      *
159      * @param uid - uid of modified package (only app-id matters)
160      * @param packageName - the (new) packageName
161      * @param isRemove - true if the package is being removed, false if it is being added
162      */
onModifyPackageState(int uid, String packageName, boolean isRemove)163     public void onModifyPackageState(int uid, String packageName, boolean isRemove) {
164         // No point in maintaining package mappings for uids of different users
165         uid = UserHandle.getAppId(uid);
166         synchronized (mLock) {
167             // Update state
168             Set<String> packages;
169             if (!isRemove) {
170                 packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1));
171                 packages.add(packageName);
172             } else {
173                 packages = mPackageMap.get(uid);
174                 if (packages != null) {
175                     packages.remove(packageName);
176                     if (packages.isEmpty()) mPackageMap.remove(uid);
177                 }
178             }
179             // Push state to destination
180             if (mDest == null) {
181                 return;
182             }
183             var state = new UidPackageState();
184             state.uid = uid;
185             state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList();
186             try {
187                 mDest.updatePackagesForUid(state);
188             } catch (RemoteException e) {
189                 // We will re-init the state when the service comes back up
190                 mDest = null;
191             }
192         }
193     }
194 
195     /** Called whenever any package/permission changes occur which invalidate uids holding perms */
onPermissionStateChanged()196     public void onPermissionStateChanged() {
197         synchronized (mLock) {
198             if (mDest == null) {
199                 mIsUpdateDeferred = true;
200                 return;
201             }
202             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "audioserver_permission_update");
203             try {
204                 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
205                     var newPerms = getUidsHoldingPerm(i);
206                     if (!Arrays.equals(newPerms, mPermMap[i])) {
207                         mPermMap[i] = newPerms;
208                         mDest.populatePermissionState(i, newPerms);
209                     }
210                 }
211             } catch (RemoteException e) {
212                 // We will re-init the state when the service comes back up
213                 mDest = null;
214                 // We didn't necessarily finish
215                 mIsUpdateDeferred = true;
216             } finally {
217                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
218             }
219         }
220     }
221 
setIsolatedServiceUid(int uid, int owningUid)222     public void setIsolatedServiceUid(int uid, int owningUid) {
223         synchronized (mLock) {
224             if (mHdsUid == uid) return;
225             var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid));
226             if (packageNameSet != null) {
227                 var packageName = packageNameSet.iterator().next();
228                 onModifyPackageState(uid, packageName, /* isRemove= */ false);
229             } else {
230                 Log.wtf(TAG, "setIsolatedService owning uid not found");
231             }
232             // permissions
233             mHdsUid = uid;
234             if (mDest == null) {
235                 mIsUpdateDeferred = true;
236                 return;
237             }
238             try {
239                 for (byte perm : HDS_PERMS) {
240                     int[] newPerms = new int[mPermMap[perm].length + 1];
241                     System.arraycopy(mPermMap[perm], 0, newPerms, 0, mPermMap[perm].length);
242                     newPerms[newPerms.length - 1] = mHdsUid;
243                     Arrays.sort(newPerms);
244                     mPermMap[perm] = newPerms;
245                     mDest.populatePermissionState(perm, newPerms);
246                 }
247             } catch (RemoteException e) {
248                 // We will re-init the state when the service comes back up
249                 mDest = null;
250                 // We didn't necessarily finish
251                 mIsUpdateDeferred = true;
252             }
253         }
254     }
255 
clearIsolatedServiceUid(int uid)256     public void clearIsolatedServiceUid(int uid) {
257         synchronized (mLock) {
258             var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid));
259             if (mHdsUid != uid) {
260                 Log.wtf(TAG,
261                         "Unexpected isolated service uid cleared: " + uid + packageNameSet
262                                 + ", expected " + mHdsUid);
263                 return;
264             }
265             if (packageNameSet != null) {
266                 var packageName = packageNameSet.iterator().next();
267                 onModifyPackageState(uid, packageName, /* isRemove= */ true);
268             } else {
269                 Log.wtf(TAG, "clearIsolatedService uid not found");
270             }
271             // permissions
272             if (mDest == null) {
273                 mIsUpdateDeferred = true;
274                 return;
275             }
276             try {
277                 for (byte perm : HDS_PERMS) {
278                     int[] newPerms = new int[mPermMap[perm].length - 1];
279                     int ind = Arrays.binarySearch(mPermMap[perm], uid);
280                     if (ind < 0) continue;
281                     System.arraycopy(mPermMap[perm], 0, newPerms, 0, ind);
282                     System.arraycopy(mPermMap[perm], ind + 1, newPerms, ind,
283                             mPermMap[perm].length - ind - 1);
284                     mPermMap[perm] = newPerms;
285                     mDest.populatePermissionState(perm, newPerms);
286                 }
287             } catch (RemoteException e) {
288                 // We will re-init the state when the service comes back up
289                 mDest = null;
290                 // We didn't necessarily finish
291                 mIsUpdateDeferred = true;
292             }
293             mHdsUid = -1;
294         }
295     }
296 
isSpecialHdsPermission(int perm)297     private boolean isSpecialHdsPermission(int perm) {
298         for (var hdsPerm : HDS_PERMS) {
299             if (perm == hdsPerm) return true;
300         }
301         return false;
302     }
303 
304     /** Called when full syncing package state to audioserver. */
305     @GuardedBy("mLock")
resetNativePackageState()306     private void resetNativePackageState() {
307         if (mDest == null) return;
308         List<UidPackageState> states =
309                 mPackageMap.entrySet().stream()
310                         .map(
311                                 entry -> {
312                                     UidPackageState state = new UidPackageState();
313                                     state.uid = entry.getKey();
314                                     state.packageNames = List.copyOf(entry.getValue());
315                                     return state;
316                                 })
317                         .toList();
318         try {
319             mDest.populatePackagesForUids(states);
320         } catch (RemoteException e) {
321             // We will re-init the state when the service comes back up
322             mDest = null;
323         }
324     }
325 
326     @GuardedBy("mLock")
327     /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */
getUidsHoldingPerm(int perm)328     private int[] getUidsHoldingPerm(int perm) {
329         IntArray acc = new IntArray();
330         for (int userId : mUserIdSupplier.get()) {
331             for (int appId : mPackageMap.keySet()) {
332                 int uid = UserHandle.getUid(userId, appId);
333                 if (mPermissionPredicate.test(uid, MONITORED_PERMS[perm])) {
334                     acc.add(uid);
335                 }
336             }
337         }
338         if (isSpecialHdsPermission(perm) && mHdsUid != -1) {
339             acc.add(mHdsUid);
340         }
341         var unwrapped = acc.toArray();
342         Arrays.sort(unwrapped);
343         return unwrapped;
344     }
345 
346     /**
347      * Aggregation operation on all package states list: groups by states by app-id and merges the
348      * packageName for each state into an ArraySet.
349      */
generatePackageMappings( Collection<PackageState> appInfos)350     private static Map<Integer, Set<String>> generatePackageMappings(
351             Collection<PackageState> appInfos) {
352         Collector<PackageState, Object, Set<String>> reducer =
353                 Collectors.mapping(
354                         (PackageState p) -> p.getPackageName(),
355                         Collectors.toCollection(() -> new ArraySet(1)));
356 
357         return appInfos.stream()
358                 .collect(
359                         Collectors.groupingBy(
360                                 /* predicate */ (PackageState p) -> p.getAppId(),
361                                 /* factory */ HashMap::new,
362                                 /* downstream collector */ reducer));
363     }
364 }
365