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.CALL_AUDIO_INTERCEPTION;
20 import static android.Manifest.permission.MODIFY_AUDIO_ROUTING;
21 import static android.Manifest.permission.MODIFY_PHONE_STATE;
22 import static android.Manifest.permission.RECORD_AUDIO;
23 
24 import android.annotation.Nullable;
25 import android.os.RemoteException;
26 import android.os.UserHandle;
27 import android.util.ArraySet;
28 import android.util.IntArray;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.media.permission.INativePermissionController;
32 import com.android.media.permission.PermissionEnum;
33 import com.android.media.permission.UidPackageState;
34 import com.android.server.pm.pkg.PackageState;
35 
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 import java.util.function.BiPredicate;
45 import java.util.function.Supplier;
46 import java.util.stream.Collector;
47 import java.util.stream.Collectors;
48 
49 /** Responsible for synchronizing system server permission state to the native audioserver. */
50 public class AudioServerPermissionProvider {
51 
52     static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE];
53 
54     static {
55         MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING;
56         MONITORED_PERMS[PermissionEnum.MODIFY_PHONE_STATE] = MODIFY_PHONE_STATE;
57         MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO;
58         MONITORED_PERMS[PermissionEnum.CALL_AUDIO_INTERCEPTION] = CALL_AUDIO_INTERCEPTION;
59     }
60 
61     private final Object mLock = new Object();
62     private final Supplier<int[]> mUserIdSupplier;
63     private final BiPredicate<Integer, String> mPermissionPredicate;
64 
65     @GuardedBy("mLock")
66     private INativePermissionController mDest;
67 
68     @GuardedBy("mLock")
69     private final Map<Integer, Set<String>> mPackageMap;
70     // Values are sorted
71     @GuardedBy("mLock")
72     private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][];
73 
74     @GuardedBy("mLock")
75     private boolean mIsUpdateDeferred = true;
76 
77     /**
78      * @param appInfos - PackageState for all apps on the device, used to populate init state
79      */
AudioServerPermissionProvider( Collection<PackageState> appInfos, BiPredicate<Integer, String> permissionPredicate, Supplier<int[]> userIdSupplier)80     public AudioServerPermissionProvider(
81             Collection<PackageState> appInfos,
82             BiPredicate<Integer, String> permissionPredicate,
83             Supplier<int[]> userIdSupplier) {
84         for (int i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
85             Objects.requireNonNull(MONITORED_PERMS[i]);
86         }
87         mUserIdSupplier = userIdSupplier;
88         mPermissionPredicate = permissionPredicate;
89         // Initialize the package state
90         mPackageMap = generatePackageMappings(appInfos);
91     }
92 
93     /**
94      * Called whenever audioserver starts (or started before us)
95      *
96      * @param pc - The permission controller interface from audioserver, which we push updates to
97      */
onServiceStart(@ullable INativePermissionController pc)98     public void onServiceStart(@Nullable INativePermissionController pc) {
99         if (pc == null) return;
100         synchronized (mLock) {
101             mDest = pc;
102             resetNativePackageState();
103             try {
104                 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
105                     if (mIsUpdateDeferred) {
106                         mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]);
107                     }
108                     mDest.populatePermissionState(i, mPermMap[i]);
109                 }
110                 mIsUpdateDeferred = false;
111             } catch (RemoteException e) {
112                 // We will re-init the state when the service comes back up
113                 mDest = null;
114             }
115         }
116     }
117 
118     /**
119      * Called when a package is added or removed
120      *
121      * @param uid - uid of modified package (only app-id matters)
122      * @param packageName - the (new) packageName
123      * @param isRemove - true if the package is being removed, false if it is being added
124      */
onModifyPackageState(int uid, String packageName, boolean isRemove)125     public void onModifyPackageState(int uid, String packageName, boolean isRemove) {
126         // No point in maintaining package mappings for uids of different users
127         uid = UserHandle.getAppId(uid);
128         synchronized (mLock) {
129             // Update state
130             Set<String> packages;
131             if (!isRemove) {
132                 packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1));
133                 packages.add(packageName);
134             } else {
135                 packages = mPackageMap.get(uid);
136                 if (packages != null) {
137                     packages.remove(packageName);
138                     if (packages.isEmpty()) mPackageMap.remove(uid);
139                 }
140             }
141             // Push state to destination
142             if (mDest == null) {
143                 return;
144             }
145             var state = new UidPackageState();
146             state.uid = uid;
147             state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList();
148             try {
149                 mDest.updatePackagesForUid(state);
150             } catch (RemoteException e) {
151                 // We will re-init the state when the service comes back up
152                 mDest = null;
153             }
154         }
155     }
156 
157     /** Called whenever any package/permission changes occur which invalidate uids holding perms */
onPermissionStateChanged()158     public void onPermissionStateChanged() {
159         synchronized (mLock) {
160             if (mDest == null) {
161                 mIsUpdateDeferred = true;
162                 return;
163             }
164             try {
165                 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
166                     var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]);
167                     if (!Arrays.equals(newPerms, mPermMap[i])) {
168                         mPermMap[i] = newPerms;
169                         mDest.populatePermissionState(i, newPerms);
170                     }
171                 }
172             } catch (RemoteException e) {
173                 // We will re-init the state when the service comes back up
174                 mDest = null;
175                 // We didn't necessarily finish
176                 mIsUpdateDeferred = true;
177             }
178         }
179     }
180 
181     /** Called when full syncing package state to audioserver. */
182     @GuardedBy("mLock")
resetNativePackageState()183     private void resetNativePackageState() {
184         if (mDest == null) return;
185         List<UidPackageState> states =
186                 mPackageMap.entrySet().stream()
187                         .map(
188                                 entry -> {
189                                     UidPackageState state = new UidPackageState();
190                                     state.uid = entry.getKey();
191                                     state.packageNames = List.copyOf(entry.getValue());
192                                     return state;
193                                 })
194                         .toList();
195         try {
196             mDest.populatePackagesForUids(states);
197         } catch (RemoteException e) {
198             // We will re-init the state when the service comes back up
199             mDest = null;
200         }
201     }
202 
203     @GuardedBy("mLock")
204     /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */
getUidsHoldingPerm(String perm)205     private int[] getUidsHoldingPerm(String perm) {
206         IntArray acc = new IntArray();
207         for (int userId : mUserIdSupplier.get()) {
208             for (int appId : mPackageMap.keySet()) {
209                 int uid = UserHandle.getUid(userId, appId);
210                 if (mPermissionPredicate.test(uid, perm)) {
211                     acc.add(uid);
212                 }
213             }
214         }
215         var unwrapped = acc.toArray();
216         Arrays.sort(unwrapped);
217         return unwrapped;
218     }
219 
220     /**
221      * Aggregation operation on all package states list: groups by states by app-id and merges the
222      * packageName for each state into an ArraySet.
223      */
generatePackageMappings( Collection<PackageState> appInfos)224     private static Map<Integer, Set<String>> generatePackageMappings(
225             Collection<PackageState> appInfos) {
226         Collector<PackageState, Object, Set<String>> reducer =
227                 Collectors.mapping(
228                         (PackageState p) -> p.getPackageName(),
229                         Collectors.toCollection(() -> new ArraySet(1)));
230 
231         return appInfos.stream()
232                 .collect(
233                         Collectors.groupingBy(
234                                 /* predicate */ (PackageState p) -> p.getAppId(),
235                                 /* factory */ HashMap::new,
236                                 /* downstream collector */ reducer));
237     }
238 }
239