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