1 /*
2  * Copyright (C) 2021 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.policy;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.AppOpsManager;
22 import android.app.AppOpsManager.AttributionFlags;
23 import android.app.AppOpsManagerInternal;
24 import android.app.SyncNotedAppOp;
25 import android.app.role.RoleManager;
26 import android.content.AttributionSource;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.location.LocationManagerInternal;
34 import android.net.Uri;
35 import android.os.Binder;
36 import android.os.Bundle;
37 import android.os.IBinder;
38 import android.os.PackageTagsList;
39 import android.os.Process;
40 import android.os.SystemProperties;
41 import android.os.UserHandle;
42 import android.service.voice.VoiceInteractionManagerInternal;
43 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.util.SparseArray;
47 
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.internal.util.function.HeptFunction;
50 import com.android.internal.util.function.HexFunction;
51 import com.android.internal.util.function.QuadFunction;
52 import com.android.internal.util.function.QuintConsumer;
53 import com.android.internal.util.function.QuintFunction;
54 import com.android.internal.util.function.UndecFunction;
55 import com.android.server.LocalServices;
56 
57 import java.io.PrintWriter;
58 import java.util.Arrays;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.concurrent.ConcurrentHashMap;
62 
63 /**
64  * This class defines policy for special behaviors around app ops.
65  */
66 public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate {
67     private static final String LOG_TAG = AppOpsPolicy.class.getName();
68 
69     private static final String ACTIVITY_RECOGNITION_TAGS =
70             "android:activity_recognition_allow_listed_tags";
71     private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
72     private static final boolean SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED =
73             SystemProperties.getBoolean("ro.hotword.detection_service_required", false);
74 
75     @NonNull
76     private final Object mLock = new Object();
77 
78     @NonNull
79     private final IBinder mToken = new Binder();
80 
81     @NonNull
82     private final Context mContext;
83 
84     @NonNull
85     private final RoleManager mRoleManager;
86 
87     @NonNull
88     private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal;
89 
90     /**
91      * Whether this device allows only the HotwordDetectionService to use
92      * OP_RECORD_AUDIO_HOTWORD which doesn't incur the privacy indicator.
93      */
94     private final boolean mIsHotwordDetectionServiceRequired;
95 
96     /**
97      * The locking policy around the location tags is a bit special. Since we want to
98      * avoid grabbing the lock on every op note we are taking the approach where the
99      * read and write are being done via a thread-safe data structure such that the
100      * lookup/insert are single thread-safe calls. When we update the cached state we
101      * use a lock to ensure the update's lookup and store calls are done atomically,
102      * so multiple writers would not interleave. The tradeoff is we make is that the
103      * concurrent data structure would use boxing/unboxing of integers but this is
104      * preferred to locking.
105      */
106     @GuardedBy("mLock - writes only - see above")
107     @NonNull
108     private final ConcurrentHashMap<Integer, PackageTagsList> mLocationTags =
109             new ConcurrentHashMap<>();
110 
111     // location tags can vary per uid - but we merge all tags under an app id into the final data
112     // structure above
113     @GuardedBy("mLock")
114     private final SparseArray<PackageTagsList> mPerUidLocationTags = new SparseArray<>();
115 
116     // activity recognition currently only grabs tags from the APK manifest. we know that the
117     // manifest is the same for all users, so there's no need to track variations in tags across
118     // different users. if that logic ever changes, this might need to behave more like location
119     // tags above.
120     @GuardedBy("mLock - writes only - see above")
121     @NonNull
122     private final ConcurrentHashMap<Integer, PackageTagsList> mActivityRecognitionTags =
123             new ConcurrentHashMap<>();
124 
AppOpsPolicy(@onNull Context context)125     public AppOpsPolicy(@NonNull Context context) {
126         mContext = context;
127         mRoleManager = mContext.getSystemService(RoleManager.class);
128         mVoiceInteractionManagerInternal = LocalServices.getService(
129                 VoiceInteractionManagerInternal.class);
130         mIsHotwordDetectionServiceRequired = isHotwordDetectionServiceRequired(
131                 mContext.getPackageManager());
132 
133         final LocationManagerInternal locationManagerInternal = LocalServices.getService(
134                 LocationManagerInternal.class);
135         locationManagerInternal.setLocationPackageTagsListener(
136                 (uid, packageTagsList) -> {
137                     synchronized (mLock) {
138                         if (packageTagsList.isEmpty()) {
139                             mPerUidLocationTags.remove(uid);
140                         } else {
141                             mPerUidLocationTags.set(uid, packageTagsList);
142                         }
143 
144                         int appId = UserHandle.getAppId(uid);
145                         PackageTagsList.Builder appIdTags = new PackageTagsList.Builder(1);
146                         int size = mPerUidLocationTags.size();
147                         for (int i = 0; i < size; i++) {
148                             if (UserHandle.getAppId(mPerUidLocationTags.keyAt(i)) == appId) {
149                                 appIdTags.add(mPerUidLocationTags.valueAt(i));
150                             }
151                         }
152 
153                         updateAllowListedTagsForPackageLocked(appId, appIdTags.build(),
154                                 mLocationTags);
155                     }
156                 });
157 
158         final IntentFilter intentFilter = new IntentFilter();
159         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
160         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
161         intentFilter.addDataScheme("package");
162 
163         context.registerReceiverAsUser(new BroadcastReceiver() {
164             @Override
165             public void onReceive(Context context, Intent intent) {
166                 final Uri uri = intent.getData();
167                 if (uri == null) {
168                     return;
169                 }
170                 final String packageName = uri.getSchemeSpecificPart();
171                 if (TextUtils.isEmpty(packageName)) {
172                     return;
173                 }
174                 final List<String> activityRecognizers = mRoleManager.getRoleHolders(
175                         RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER);
176                 if (activityRecognizers.contains(packageName)) {
177                     updateActivityRecognizerTags(packageName);
178                 }
179             }
180         }, UserHandle.SYSTEM, intentFilter, null, null);
181 
182         mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
183                 (String roleName, UserHandle user) -> {
184             if (RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER.equals(roleName)) {
185                 initializeActivityRecognizersTags();
186             }
187         }, UserHandle.SYSTEM);
188 
189         initializeActivityRecognizersTags();
190 
191         // Restrict phone call ops if the TelecomService will not start (conditioned on having
192         // FEATURE_MICROPHONE, FEATURE_TELECOM, or FEATURE_TELEPHONY).
193         PackageManager pm = mContext.getPackageManager();
194         if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
195                 && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
196                 && !pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
197             AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
198             appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken,
199                     null, UserHandle.USER_ALL);
200             appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true, mToken,
201                     null, UserHandle.USER_ALL);
202         }
203     }
204 
205     /**
206      * @hide
207      */
isHotwordDetectionServiceRequired(PackageManager pm)208     public static boolean isHotwordDetectionServiceRequired(PackageManager pm) {
209         // The HotwordDetectionService APIs aren't ready yet for Auto or TV.
210         if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
211                 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
212             return false;
213         }
214         return SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED;
215     }
216 
217     @Override
checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl)218     public int checkOperation(int code, int uid, String packageName,
219             @Nullable String attributionTag, boolean raw,
220             QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) {
221         return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw);
222     }
223 
224     @Override
checkAudioOperation(int code, int usage, int uid, String packageName, QuadFunction<Integer, Integer, Integer, String, Integer> superImpl)225     public int checkAudioOperation(int code, int usage, int uid, String packageName,
226             QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) {
227         return superImpl.apply(code, usage, uid, packageName);
228     }
229 
230     @Override
noteOperation(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl)231     public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
232             @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
233             String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer,
234                     String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
235         return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
236                 resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp,
237                 message, shouldCollectMessage);
238     }
239 
240     @Override
noteProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl)241     public SyncNotedAppOp noteProxyOperation(int code, @NonNull AttributionSource attributionSource,
242             boolean shouldCollectAsyncNotedOp, @Nullable String message,
243             boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer,
244                     AttributionSource, Boolean, String, Boolean, Boolean,
245                     SyncNotedAppOp> superImpl) {
246         return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
247                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
248                 attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
249                 skipProxyOperation);
250     }
251 
252     @Override
startOperation(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl)253     public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
254             @Nullable String packageName, @Nullable String attributionTag,
255             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
256             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
257             int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String,
258                     String, Boolean, Boolean, String, Boolean, Integer, Integer,
259             SyncNotedAppOp> superImpl) {
260         return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag),
261                 resolveUid(code, uid), packageName, attributionTag, startIfModeDefault,
262                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
263                 attributionChainId);
264     }
265 
266     @Override
startProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId, @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl)267     public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
268             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
269             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
270             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
271             @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
272             @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String,
273                     Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
274         return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
275                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
276                 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
277                 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
278                 proxiedAttributionFlags, attributionChainId);
279     }
280 
281     @Override
finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl)282     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
283             String attributionTag,
284             @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) {
285         superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag),
286                 resolveUid(code, uid), packageName, attributionTag);
287     }
288 
289     @Override
finishProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation, @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl)290     public void finishProxyOperation(@NonNull IBinder clientId, int code,
291             @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
292             @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) {
293         superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
294                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
295                 attributionSource, skipProxyOperation);
296     }
297 
298     /**
299      * Write location and activity recognition tags to console.
300      * See also {@code adb shell dumpsys appops}.
301      */
dumpTags(PrintWriter writer)302     public void dumpTags(PrintWriter writer) {
303         if (!mLocationTags.isEmpty()) {
304             writer.println("  AppOps policy location tags:");
305             writeTags(mLocationTags, writer);
306             writer.println();
307         }
308         if (!mActivityRecognitionTags.isEmpty()) {
309             writer.println("  AppOps policy activity recognition tags:");
310             writeTags(mActivityRecognitionTags, writer);
311             writer.println();
312         }
313     }
314 
writeTags(Map<Integer, PackageTagsList> tags, PrintWriter writer)315     private void writeTags(Map<Integer, PackageTagsList> tags, PrintWriter writer) {
316         int counter = 0;
317         for (Map.Entry<Integer, PackageTagsList> tagEntry : tags.entrySet()) {
318             writer.print("    #"); writer.print(counter++); writer.print(": ");
319             writer.print(tagEntry.getKey().toString()); writer.print("=");
320             tagEntry.getValue().dump(writer);
321         }
322     }
323 
324 
resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag)325     private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
326             @Nullable String attributionTag) {
327         code = resolveRecordAudioOp(code, uid);
328         code = resolveSandboxedServiceOp(code, uid);
329         if (attributionTag == null) {
330             return code;
331         }
332         int resolvedCode = resolveLocationOp(code);
333         if (resolvedCode != code) {
334             if (isDatasourceAttributionTag(uid, packageName, attributionTag,
335                     mLocationTags)) {
336                 return resolvedCode;
337             }
338         } else {
339             resolvedCode = resolveArOp(code);
340             if (resolvedCode != code) {
341                 if (isDatasourceAttributionTag(uid, packageName, attributionTag,
342                         mActivityRecognitionTags)) {
343                     return resolvedCode;
344                 }
345             }
346         }
347         return code;
348     }
349 
initializeActivityRecognizersTags()350     private void initializeActivityRecognizersTags() {
351         final List<String> activityRecognizers = mRoleManager.getRoleHolders(
352                 RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER);
353         final int recognizerCount = activityRecognizers.size();
354         if (recognizerCount > 0) {
355             for (int i = 0; i < recognizerCount; i++) {
356                 final String activityRecognizer = activityRecognizers.get(i);
357                 updateActivityRecognizerTags(activityRecognizer);
358             }
359         } else {
360             clearActivityRecognitionTags();
361         }
362     }
363 
clearActivityRecognitionTags()364     private void clearActivityRecognitionTags() {
365         synchronized (mLock) {
366             mActivityRecognitionTags.clear();
367         }
368     }
369 
updateActivityRecognizerTags(@onNull String activityRecognizer)370     private void updateActivityRecognizerTags(@NonNull String activityRecognizer) {
371         final int flags = PackageManager.GET_SERVICES
372                 | PackageManager.GET_META_DATA
373                 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
374                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
375                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
376 
377         final Intent intent = new Intent(Intent.ACTION_ACTIVITY_RECOGNIZER);
378         intent.setPackage(activityRecognizer);
379         final ResolveInfo resolvedService = mContext.getPackageManager()
380                 .resolveServiceAsUser(intent, flags, UserHandle.USER_SYSTEM);
381         if (resolvedService == null || resolvedService.serviceInfo == null) {
382             Log.w(LOG_TAG, "Service recognizer doesn't handle "
383                     + Intent.ACTION_ACTIVITY_RECOGNIZER +  ", ignoring!");
384             return;
385         }
386         final Bundle metaData = resolvedService.serviceInfo.metaData;
387         if (metaData == null) {
388             return;
389         }
390         final String tagsList = metaData.getString(ACTIVITY_RECOGNITION_TAGS);
391         if (!TextUtils.isEmpty(tagsList)) {
392             PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add(
393                     resolvedService.serviceInfo.packageName,
394                     Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build();
395             synchronized (mLock) {
396                 updateAllowListedTagsForPackageLocked(
397                         UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid),
398                         packageTagsList,
399                         mActivityRecognitionTags);
400             }
401         }
402     }
403 
updateAllowListedTagsForPackageLocked(int appId, PackageTagsList packageTagsList, ConcurrentHashMap<Integer, PackageTagsList> datastore)404     private static void updateAllowListedTagsForPackageLocked(int appId,
405             PackageTagsList packageTagsList,
406             ConcurrentHashMap<Integer, PackageTagsList> datastore) {
407         datastore.put(appId, packageTagsList);
408     }
409 
isDatasourceAttributionTag(int uid, @NonNull String packageName, @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps)410     private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName,
411             @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) {
412         // Only a single lookup from the underlying concurrent data structure
413         final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid));
414         return appIdTags != null && appIdTags.contains(packageName, attributionTag);
415     }
416 
resolveLocationOp(int code)417     private static int resolveLocationOp(int code) {
418         switch (code) {
419             case AppOpsManager.OP_FINE_LOCATION:
420                 return AppOpsManager.OP_FINE_LOCATION_SOURCE;
421             case AppOpsManager.OP_COARSE_LOCATION:
422                 return AppOpsManager.OP_COARSE_LOCATION_SOURCE;
423         }
424         return code;
425     }
426 
resolveArOp(int code)427     private static int resolveArOp(int code) {
428         if (code == AppOpsManager.OP_ACTIVITY_RECOGNITION) {
429             return AppOpsManager.OP_ACTIVITY_RECOGNITION_SOURCE;
430         }
431         return code;
432     }
433 
resolveRecordAudioOp(int code, int uid)434     private int resolveRecordAudioOp(int code, int uid) {
435         if (code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD) {
436             if (!mIsHotwordDetectionServiceRequired) {
437                 return code;
438             }
439             // Only the HotwordDetectionService can use the RECORD_AUDIO_HOTWORD op which doesn't
440             // incur the privacy indicator. Downgrade to standard RECORD_AUDIO for other processes.
441             final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
442                     mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
443             if (hotwordDetectionServiceIdentity != null
444                     && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
445                 return code;
446             }
447             return AppOpsManager.OP_RECORD_AUDIO;
448         }
449         return code;
450     }
451 
resolveSandboxedServiceOp(int code, int uid)452     private int resolveSandboxedServiceOp(int code, int uid) {
453         if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
454                  || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
455             return code;
456         }
457         final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
458                 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
459         if (hotwordDetectionServiceIdentity != null
460                 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
461             // Upgrade the op such that no indicators is shown for camera or audio service. This
462             // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
463             switch (code) {
464                 case AppOpsManager.OP_RECORD_AUDIO:
465                     return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
466                 case AppOpsManager.OP_CAMERA:
467                     return AppOpsManager.OP_CAMERA_SANDBOXED;
468             }
469         }
470         return code;
471     }
472 
473 
resolveUid(int code, int uid)474     private int resolveUid(int code, int uid) {
475         // The HotwordDetectionService is an isolated service, which ordinarily cannot hold
476         // permissions. So we allow it to assume the owning package identity for certain
477         // operations.
478         // Note: The package name coming from the audio server is already the one for the owning
479         // package, so we don't need to modify it.
480         if (Process.isIsolated(uid) // simple check which fails-fast for the common case
481                 && (code == AppOpsManager.OP_RECORD_AUDIO
482                 || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD
483                 || code == AppOpsManager.OP_CAMERA)) {
484             final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
485                     mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
486             if (hotwordDetectionServiceIdentity != null
487                     && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
488                 uid = hotwordDetectionServiceIdentity.getOwnerUid();
489             }
490         }
491         return uid;
492     }
493 }
494