Loading core/java/android/service/voice/VoiceInteractionManagerInternal.java +37 −0 Original line number Diff line number Diff line Loading @@ -16,9 +16,12 @@ package android.service.voice; import android.annotation.Nullable; import android.os.Bundle; import android.os.IBinder; import com.android.internal.annotations.Immutable; /** * @hide Loading Loading @@ -46,4 +49,38 @@ public abstract class VoiceInteractionManagerInternal { * Returns whether the given package is currently in an active session */ public abstract boolean hasActiveSession(String packageName); /** * Gets the identity of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceIdentity */ @Nullable public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity(); /** * Provides the uids of the currently active * {@link android.service.voice.HotwordDetectionService} and its owning package. The * HotwordDetectionService is an isolated service, so it has a separate uid. */ @Immutable public static class HotwordDetectionServiceIdentity { private final int mIsolatedUid; private final int mOwnerUid; public HotwordDetectionServiceIdentity(int isolatedUid, int ownerUid) { mIsolatedUid = isolatedUid; mOwnerUid = ownerUid; } /** Gets the uid of the currently active isolated process hosting the service. */ public int getIsolatedUid() { return mIsolatedUid; } /** Gets the uid of the package that provides the HotwordDetectionService. */ public int getOwnerUid() { return mOwnerUid; } } } No newline at end of file services/core/java/com/android/server/pm/permission/PermissionManagerService.java +67 −25 Original line number Diff line number Diff line Loading @@ -17,7 +17,9 @@ package com.android.server.pm.permission; import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.RECORD_AUDIO; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; Loading Loading @@ -148,6 +150,7 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; Loading Loading @@ -308,6 +311,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { @NonNull private final OnPermissionChangeListeners mOnPermissionChangeListeners; @Nullable private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider; // TODO: Take a look at the methods defined in the callback. // The callback was initially created to support the split between permission // manager and the package manager. However, it's started to be used for other Loading Loading @@ -5200,6 +5206,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { public int[] getGidsForUid(int uid) { return PermissionManagerService.this.getGidsForUid(uid); } @Override public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) { mHotwordDetectionServiceProvider = provider; } @Override public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() { return mHotwordDetectionServiceProvider; } } /** Loading Loading @@ -5476,10 +5492,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { private final @NonNull Context mContext; private final @NonNull AppOpsManager mAppOpsManager; private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal; PermissionCheckerService(@NonNull Context context) { mContext = context; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mPermissionManagerServiceInternal = LocalServices.getService(PermissionManagerServiceInternal.class); } @Override Loading @@ -5492,8 +5511,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { Objects.requireNonNull(attributionSourceState); final AttributionSource attributionSource = new AttributionSource( attributionSourceState); final int result = checkPermission(mContext, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); final int result = checkPermission(mContext, mPermissionManagerServiceInternal, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED && result != PermissionChecker.PERMISSION_SOFT_DENIED) { Loading Loading @@ -5582,10 +5602,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @PermissionCheckerManager.PermissionResult private static int checkPermission(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { private static int checkPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { PermissionInfo permissionInfo = sPlatformPermissions.get(permission); if (permissionInfo == null) { Loading @@ -5602,22 +5623,25 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (permissionInfo.isAppOp()) { return checkAppOpPermission(context, permission, attributionSource, message, forDataDelivery, fromDatasource); return checkAppOpPermission(context, permissionManagerServiceInt, permission, attributionSource, message, forDataDelivery, fromDatasource); } if (permissionInfo.isRuntime()) { return checkRuntimePermission(context, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); return checkRuntimePermission(context, permissionManagerServiceInt, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); } if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt, permission, attributionSource.getUid(), attributionSource.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (attributionSource.getNext() != null) { return checkPermission(context, permission, attributionSource.getNext(), message, forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp); return checkPermission(context, permissionManagerServiceInt, permission, attributionSource.getNext(), message, forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp); } return PermissionChecker.PERMISSION_GRANTED; Loading @@ -5625,6 +5649,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @PermissionCheckerManager.PermissionResult private static int checkAppOpPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean fromDatasource) { final int op = AppOpsManager.permissionToOpCode(permission); Loading Loading @@ -5668,13 +5693,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_HARD_DENIED; } case AppOpsManager.MODE_DEFAULT: { if (!skipCurrentChecks && !checkPermission(context, permission, attributionSource.getUid(), attributionSource .getRenouncedPermissions())) { if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt, permission, attributionSource.getUid(), attributionSource.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (next != null && !checkPermission(context, permission, next.getUid(), next.getRenouncedPermissions())) { if (next != null && !checkPermission(context, permissionManagerServiceInt, permission, next.getUid(), next.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } } Loading @@ -5689,6 +5714,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private static int checkRuntimePermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { Loading @@ -5713,13 +5739,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { } // If we already checked the permission for this one, skip the work if (!skipCurrentChecks && !checkPermission(context, permission, current.getUid(), current.getRenouncedPermissions())) { if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt, permission, current.getUid(), current.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (next != null && !checkPermission(context, permission, next.getUid(), next.getRenouncedPermissions())) { if (next != null && !checkPermission(context, permissionManagerServiceInt, permission, next.getUid(), next.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } Loading Loading @@ -5774,10 +5800,26 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } private static boolean checkPermission(@NonNull Context context, @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) { final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, private static boolean checkPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) { boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED; // Override certain permissions checks for the HotwordDetectionService. This service is // an isolated service, which ordinarily cannot hold permissions. // There's probably a cleaner, more generalizable way to do this. For now, this is // the only use case for this, so simply override here. if (!permissionGranted && Process.isIsolated(uid) // simple check which fails-fast for the common case && (permission.equals(RECORD_AUDIO) || permission.equals(CAPTURE_AUDIO_HOTWORD))) { HotwordDetectionServiceProvider hotwordServiceProvider = permissionManagerServiceInt.getHotwordDetectionServiceProvider(); permissionGranted = hotwordServiceProvider != null && uid == hotwordServiceProvider.getUid(); } if (permissionGranted && renouncedPermissions.contains(permission) && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) { Loading services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +24 −0 Original line number Diff line number Diff line Loading @@ -428,4 +428,28 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter } } } /** * Sets the provider of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceProvider */ void setHotwordDetectionServiceProvider(@Nullable HotwordDetectionServiceProvider provider); /** * Gets the provider of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceProvider */ @Nullable HotwordDetectionServiceProvider getHotwordDetectionServiceProvider(); /** * Provides the uid of the currently active * {@link android.service.voice.HotwordDetectionService}, which should be granted RECORD_AUDIO * and CAPTURE_AUDIO_HOTWORD permissions. */ interface HotwordDetectionServiceProvider { int getUid(); } } services/core/java/com/android/server/policy/AppOpsPolicy.java +33 −5 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ import android.content.pm.ResolveInfo; import android.location.LocationManagerInternal; import android.net.Uri; import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; Loading Loading @@ -78,6 +81,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @NonNull private final RoleManager mRoleManager; @NonNull private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; /** * The locking policy around the location tags is a bit special. Since we want to * avoid grabbing the lock on every op note we are taking the approach where the Loading @@ -101,6 +107,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public AppOpsPolicy(@NonNull Context context) { mContext = context; mRoleManager = mContext.getSystemService(RoleManager.class); mVoiceInteractionManagerInternal = LocalServices.getService( VoiceInteractionManagerInternal.class); final LocationManagerInternal locationManagerInternal = LocalServices.getService( LocationManagerInternal.class); Loading Loading @@ -150,7 +158,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { return superImpl.apply(code, uid, packageName, attributionTag, raw); return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw); } @Override Loading @@ -164,8 +172,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), uid, packageName, attributionTag, shouldCollectAsyncNotedOp, return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } Loading @@ -190,8 +198,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat String, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) { return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId); resolveUid(code, uid), packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId); } @Override Loading Loading @@ -404,4 +413,23 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } return code; } private int resolveUid(int code, int uid) { // The HotwordDetectionService is an isolated service, which ordinarily cannot hold // permissions. So we allow it to assume the owning package identity for certain // operations. // Note: The package name coming from the audio server is already the one for the owning // package, so we don't need to modify it. if (Process.isIsolated(uid) // simple check which fails-fast for the common case && (code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD)) { final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); if (hotwordDetectionServiceIdentity != null && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { uid = hotwordDetectionServiceIdentity.getOwnerUid(); } } return uid; } } services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +22 −3 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; Loading @@ -48,6 +49,7 @@ import android.service.voice.HotwordRejectedResult; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IHotwordDetectionService; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.util.Pair; import android.util.Slog; import android.view.contentcapture.IContentCaptureManager; Loading @@ -56,6 +58,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.server.LocalServices; import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.Closeable; import java.io.IOException; Loading Loading @@ -94,20 +98,24 @@ final class HotwordDetectionConnection { private final AtomicBoolean mUpdateStateFinish = new AtomicBoolean(false); final Object mLock; final int mVoiceInteractionServiceUid; final ComponentName mDetectionComponentName; final int mUser; final Context mContext; final @NonNull ServiceConnector<IHotwordDetectionService> mRemoteHotwordDetectionService; boolean mBound; volatile HotwordDetectionServiceIdentity mIdentity; @GuardedBy("mLock") private ParcelFileDescriptor mCurrentAudioSink; HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) { HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) { mLock = lock; mContext = context; mVoiceInteractionServiceUid = voiceInteractionServiceUid; mDetectionComponentName = serviceName; mUser = userId; final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE); Loading Loading @@ -164,7 +172,15 @@ final class HotwordDetectionConnection { public void sendResult(Bundle bundle) throws RemoteException { if (DEBUG) { Slog.d(TAG, "updateState finish"); } Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid()); } // TODO: Do this earlier than this callback and have the provider point to the // current state stored in VoiceInteractionManagerServiceImpl. final int uid = Binder.getCallingUid(); LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); future.complete(null); try { if (mUpdateStateFinish.getAndSet(true)) { Loading Loading @@ -235,6 +251,9 @@ final class HotwordDetectionConnection { if (mBound) { mRemoteHotwordDetectionService.unbind(); mBound = false; LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(null); mIdentity = null; } } Loading Loading
core/java/android/service/voice/VoiceInteractionManagerInternal.java +37 −0 Original line number Diff line number Diff line Loading @@ -16,9 +16,12 @@ package android.service.voice; import android.annotation.Nullable; import android.os.Bundle; import android.os.IBinder; import com.android.internal.annotations.Immutable; /** * @hide Loading Loading @@ -46,4 +49,38 @@ public abstract class VoiceInteractionManagerInternal { * Returns whether the given package is currently in an active session */ public abstract boolean hasActiveSession(String packageName); /** * Gets the identity of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceIdentity */ @Nullable public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity(); /** * Provides the uids of the currently active * {@link android.service.voice.HotwordDetectionService} and its owning package. The * HotwordDetectionService is an isolated service, so it has a separate uid. */ @Immutable public static class HotwordDetectionServiceIdentity { private final int mIsolatedUid; private final int mOwnerUid; public HotwordDetectionServiceIdentity(int isolatedUid, int ownerUid) { mIsolatedUid = isolatedUid; mOwnerUid = ownerUid; } /** Gets the uid of the currently active isolated process hosting the service. */ public int getIsolatedUid() { return mIsolatedUid; } /** Gets the uid of the package that provides the HotwordDetectionService. */ public int getOwnerUid() { return mOwnerUid; } } } No newline at end of file
services/core/java/com/android/server/pm/permission/PermissionManagerService.java +67 −25 Original line number Diff line number Diff line Loading @@ -17,7 +17,9 @@ package com.android.server.pm.permission; import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.RECORD_AUDIO; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; Loading Loading @@ -148,6 +150,7 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; Loading Loading @@ -308,6 +311,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { @NonNull private final OnPermissionChangeListeners mOnPermissionChangeListeners; @Nullable private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider; // TODO: Take a look at the methods defined in the callback. // The callback was initially created to support the split between permission // manager and the package manager. However, it's started to be used for other Loading Loading @@ -5200,6 +5206,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { public int[] getGidsForUid(int uid) { return PermissionManagerService.this.getGidsForUid(uid); } @Override public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) { mHotwordDetectionServiceProvider = provider; } @Override public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() { return mHotwordDetectionServiceProvider; } } /** Loading Loading @@ -5476,10 +5492,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { private final @NonNull Context mContext; private final @NonNull AppOpsManager mAppOpsManager; private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal; PermissionCheckerService(@NonNull Context context) { mContext = context; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mPermissionManagerServiceInternal = LocalServices.getService(PermissionManagerServiceInternal.class); } @Override Loading @@ -5492,8 +5511,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { Objects.requireNonNull(attributionSourceState); final AttributionSource attributionSource = new AttributionSource( attributionSourceState); final int result = checkPermission(mContext, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); final int result = checkPermission(mContext, mPermissionManagerServiceInternal, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED && result != PermissionChecker.PERMISSION_SOFT_DENIED) { Loading Loading @@ -5582,10 +5602,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @PermissionCheckerManager.PermissionResult private static int checkPermission(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { private static int checkPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { PermissionInfo permissionInfo = sPlatformPermissions.get(permission); if (permissionInfo == null) { Loading @@ -5602,22 +5623,25 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (permissionInfo.isAppOp()) { return checkAppOpPermission(context, permission, attributionSource, message, forDataDelivery, fromDatasource); return checkAppOpPermission(context, permissionManagerServiceInt, permission, attributionSource, message, forDataDelivery, fromDatasource); } if (permissionInfo.isRuntime()) { return checkRuntimePermission(context, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); return checkRuntimePermission(context, permissionManagerServiceInt, permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); } if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt, permission, attributionSource.getUid(), attributionSource.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (attributionSource.getNext() != null) { return checkPermission(context, permission, attributionSource.getNext(), message, forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp); return checkPermission(context, permissionManagerServiceInt, permission, attributionSource.getNext(), message, forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp); } return PermissionChecker.PERMISSION_GRANTED; Loading @@ -5625,6 +5649,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @PermissionCheckerManager.PermissionResult private static int checkAppOpPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean fromDatasource) { final int op = AppOpsManager.permissionToOpCode(permission); Loading Loading @@ -5668,13 +5693,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_HARD_DENIED; } case AppOpsManager.MODE_DEFAULT: { if (!skipCurrentChecks && !checkPermission(context, permission, attributionSource.getUid(), attributionSource .getRenouncedPermissions())) { if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt, permission, attributionSource.getUid(), attributionSource.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (next != null && !checkPermission(context, permission, next.getUid(), next.getRenouncedPermissions())) { if (next != null && !checkPermission(context, permissionManagerServiceInt, permission, next.getUid(), next.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } } Loading @@ -5689,6 +5714,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private static int checkRuntimePermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { Loading @@ -5713,13 +5739,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { } // If we already checked the permission for this one, skip the work if (!skipCurrentChecks && !checkPermission(context, permission, current.getUid(), current.getRenouncedPermissions())) { if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt, permission, current.getUid(), current.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (next != null && !checkPermission(context, permission, next.getUid(), next.getRenouncedPermissions())) { if (next != null && !checkPermission(context, permissionManagerServiceInt, permission, next.getUid(), next.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } Loading Loading @@ -5774,10 +5800,26 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } private static boolean checkPermission(@NonNull Context context, @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) { final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, private static boolean checkPermission(@NonNull Context context, @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) { boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED; // Override certain permissions checks for the HotwordDetectionService. This service is // an isolated service, which ordinarily cannot hold permissions. // There's probably a cleaner, more generalizable way to do this. For now, this is // the only use case for this, so simply override here. if (!permissionGranted && Process.isIsolated(uid) // simple check which fails-fast for the common case && (permission.equals(RECORD_AUDIO) || permission.equals(CAPTURE_AUDIO_HOTWORD))) { HotwordDetectionServiceProvider hotwordServiceProvider = permissionManagerServiceInt.getHotwordDetectionServiceProvider(); permissionGranted = hotwordServiceProvider != null && uid == hotwordServiceProvider.getUid(); } if (permissionGranted && renouncedPermissions.contains(permission) && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) { Loading
services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +24 −0 Original line number Diff line number Diff line Loading @@ -428,4 +428,28 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter } } } /** * Sets the provider of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceProvider */ void setHotwordDetectionServiceProvider(@Nullable HotwordDetectionServiceProvider provider); /** * Gets the provider of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceProvider */ @Nullable HotwordDetectionServiceProvider getHotwordDetectionServiceProvider(); /** * Provides the uid of the currently active * {@link android.service.voice.HotwordDetectionService}, which should be granted RECORD_AUDIO * and CAPTURE_AUDIO_HOTWORD permissions. */ interface HotwordDetectionServiceProvider { int getUid(); } }
services/core/java/com/android/server/policy/AppOpsPolicy.java +33 −5 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ import android.content.pm.ResolveInfo; import android.location.LocationManagerInternal; import android.net.Uri; import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; Loading Loading @@ -78,6 +81,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @NonNull private final RoleManager mRoleManager; @NonNull private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; /** * The locking policy around the location tags is a bit special. Since we want to * avoid grabbing the lock on every op note we are taking the approach where the Loading @@ -101,6 +107,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public AppOpsPolicy(@NonNull Context context) { mContext = context; mRoleManager = mContext.getSystemService(RoleManager.class); mVoiceInteractionManagerInternal = LocalServices.getService( VoiceInteractionManagerInternal.class); final LocationManagerInternal locationManagerInternal = LocalServices.getService( LocationManagerInternal.class); Loading Loading @@ -150,7 +158,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { return superImpl.apply(code, uid, packageName, attributionTag, raw); return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw); } @Override Loading @@ -164,8 +172,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), uid, packageName, attributionTag, shouldCollectAsyncNotedOp, return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } Loading @@ -190,8 +198,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat String, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) { return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId); resolveUid(code, uid), packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId); } @Override Loading Loading @@ -404,4 +413,23 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } return code; } private int resolveUid(int code, int uid) { // The HotwordDetectionService is an isolated service, which ordinarily cannot hold // permissions. So we allow it to assume the owning package identity for certain // operations. // Note: The package name coming from the audio server is already the one for the owning // package, so we don't need to modify it. if (Process.isIsolated(uid) // simple check which fails-fast for the common case && (code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD)) { final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); if (hotwordDetectionServiceIdentity != null && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { uid = hotwordDetectionServiceIdentity.getOwnerUid(); } } return uid; } }
services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +22 −3 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; Loading @@ -48,6 +49,7 @@ import android.service.voice.HotwordRejectedResult; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IHotwordDetectionService; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.util.Pair; import android.util.Slog; import android.view.contentcapture.IContentCaptureManager; Loading @@ -56,6 +58,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.server.LocalServices; import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.Closeable; import java.io.IOException; Loading Loading @@ -94,20 +98,24 @@ final class HotwordDetectionConnection { private final AtomicBoolean mUpdateStateFinish = new AtomicBoolean(false); final Object mLock; final int mVoiceInteractionServiceUid; final ComponentName mDetectionComponentName; final int mUser; final Context mContext; final @NonNull ServiceConnector<IHotwordDetectionService> mRemoteHotwordDetectionService; boolean mBound; volatile HotwordDetectionServiceIdentity mIdentity; @GuardedBy("mLock") private ParcelFileDescriptor mCurrentAudioSink; HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) { HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) { mLock = lock; mContext = context; mVoiceInteractionServiceUid = voiceInteractionServiceUid; mDetectionComponentName = serviceName; mUser = userId; final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE); Loading Loading @@ -164,7 +172,15 @@ final class HotwordDetectionConnection { public void sendResult(Bundle bundle) throws RemoteException { if (DEBUG) { Slog.d(TAG, "updateState finish"); } Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid()); } // TODO: Do this earlier than this callback and have the provider point to the // current state stored in VoiceInteractionManagerServiceImpl. final int uid = Binder.getCallingUid(); LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); future.complete(null); try { if (mUpdateStateFinish.getAndSet(true)) { Loading Loading @@ -235,6 +251,9 @@ final class HotwordDetectionConnection { if (mBound) { mRemoteHotwordDetectionService.unbind(); mBound = false; LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(null); mIdentity = null; } } Loading