diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index cd29dace2263f..3a651042ea715 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -47,7 +47,6 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK; import static com.android.server.voiceinteraction.HotwordDetectionConnection.ENFORCE_HOTWORD_PHRASE_ID; -import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight; import android.annotation.NonNull; import android.annotation.Nullable; @@ -743,7 +742,14 @@ abstract class DetectorSession { void enforcePermissionsForDataDelivery() { Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { - enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO); + int result = PermissionChecker.checkPermissionForPreflight( + mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid, + mVoiceInteractorIdentity.packageName); + if (result != PermissionChecker.PERMISSION_GRANTED) { + throw new SecurityException( + "Failed to obtain permission RECORD_AUDIO for identity " + + mVoiceInteractorIdentity); + } int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD); mAppOpsManager.noteOpNoThrow(hotwordOp, mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, @@ -770,7 +776,7 @@ abstract class DetectorSession { throw new SecurityException( TextUtils.formatSimple("Failed to obtain permission %s for identity %s", permission, - SoundTriggerSessionPermissionsDecorator.toString(identity))); + identity)); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java deleted file mode 100644 index 0ef2f06b66845..0000000000000 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.voiceinteraction; - -import android.hardware.soundtrigger.SoundTrigger; -import android.os.RemoteException; - -import com.android.internal.app.IHotwordRecognitionStatusCallback; -import com.android.internal.app.IVoiceInteractionSoundTriggerSession; - -/** - * A remote object that simply proxies calls to a real {@link IVoiceInteractionSoundTriggerSession} - * implementation. This design pattern allows us to add decorators to the core implementation - * (simply wrapping a binder object does not work). - */ -final class SoundTriggerSessionBinderProxy extends IVoiceInteractionSoundTriggerSession.Stub { - - private final IVoiceInteractionSoundTriggerSession mDelegate; - - SoundTriggerSessionBinderProxy(IVoiceInteractionSoundTriggerSession delegate) { - mDelegate = delegate; - } - - @Override - public SoundTrigger.ModuleProperties getDspModuleProperties() throws RemoteException { - return mDelegate.getDspModuleProperties(); - } - - @Override - public int startRecognition(int i, String s, - IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback, - SoundTrigger.RecognitionConfig recognitionConfig, boolean b) throws RemoteException { - return mDelegate.startRecognition(i, s, iHotwordRecognitionStatusCallback, - recognitionConfig, b); - } - - @Override - public int stopRecognition(int i, - IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback) - throws RemoteException { - return mDelegate.stopRecognition(i, iHotwordRecognitionStatusCallback); - } - - @Override - public int setParameter(int i, int i1, int i2) throws RemoteException { - return mDelegate.setParameter(i, i1, i2); - } - - @Override - public int getParameter(int i, int i1) throws RemoteException { - return mDelegate.getParameter(i, i1); - } - - @Override - public SoundTrigger.ModelParamRange queryParameter(int i, int i1) throws RemoteException { - return mDelegate.queryParameter(i, i1); - } - - @Override - public void detach() throws RemoteException { - mDelegate.detach(); - } -} diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java deleted file mode 100644 index 0f8a945ec4612..0000000000000 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.voiceinteraction; - -import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; -import static android.Manifest.permission.RECORD_AUDIO; - -import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.PermissionChecker; -import android.hardware.soundtrigger.SoundTrigger; -import android.media.permission.Identity; -import android.media.permission.PermissionUtil; -import android.os.IBinder; -import android.os.RemoteException; -import android.text.TextUtils; -import android.util.Slog; - -import com.android.internal.app.IHotwordRecognitionStatusCallback; -import com.android.internal.app.IVoiceInteractionSoundTriggerSession; - -/** - * Decorates {@link IVoiceInteractionSoundTriggerSession} with permission checks for {@link - * android.Manifest.permission#RECORD_AUDIO} and - * {@link android.Manifest.permission#CAPTURE_AUDIO_HOTWORD}. - *
- * Does not implement {@link #asBinder()} as it's intended to be wrapped by an - * {@link IVoiceInteractionSoundTriggerSession.Stub} object. - */ -final class SoundTriggerSessionPermissionsDecorator implements - IVoiceInteractionSoundTriggerSession { - static final String TAG = "SoundTriggerSessionPermissionsDecorator"; - - private final IVoiceInteractionSoundTriggerSession mDelegate; - private final Context mContext; - private final Identity mOriginatorIdentity; - - SoundTriggerSessionPermissionsDecorator(IVoiceInteractionSoundTriggerSession delegate, - Context context, Identity originatorIdentity) { - mDelegate = delegate; - mContext = context; - mOriginatorIdentity = originatorIdentity; - } - - @Override - public SoundTrigger.ModuleProperties getDspModuleProperties() throws RemoteException { - // No permission needed here (the app must have the Assistant Role to retrieve the session). - return mDelegate.getDspModuleProperties(); - } - - @Override - public int startRecognition(int i, String s, - IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback, - SoundTrigger.RecognitionConfig recognitionConfig, boolean b) throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "startRecognition"); - } - if (!isHoldingPermissions()) { - return SoundTrigger.STATUS_PERMISSION_DENIED; - } - return mDelegate.startRecognition(i, s, iHotwordRecognitionStatusCallback, - recognitionConfig, b); - } - - @Override - public int stopRecognition(int i, - IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback) - throws RemoteException { - // Stopping a model does not require special permissions. Having a handle to the session is - // sufficient. - return mDelegate.stopRecognition(i, iHotwordRecognitionStatusCallback); - } - - @Override - public int setParameter(int i, int i1, int i2) throws RemoteException { - if (!isHoldingPermissions()) { - return SoundTrigger.STATUS_PERMISSION_DENIED; - } - return mDelegate.setParameter(i, i1, i2); - } - - @Override - public int getParameter(int i, int i1) throws RemoteException { - // No permission needed here (the app must have the Assistant Role to retrieve the session). - return mDelegate.getParameter(i, i1); - } - - @Override - public SoundTrigger.ModelParamRange queryParameter(int i, int i1) throws RemoteException { - // No permission needed here (the app must have the Assistant Role to retrieve the session). - return mDelegate.queryParameter(i, i1); - } - - @Override - public IBinder asBinder() { - throw new UnsupportedOperationException( - "This object isn't intended to be used as a Binder."); - } - - @Override - public void detach() { - try { - mDelegate.detach(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - // TODO: Share this code with SoundTriggerMiddlewarePermission. - private boolean isHoldingPermissions() { - try { - enforcePermissionForPreflight(mContext, mOriginatorIdentity, RECORD_AUDIO); - enforcePermissionForPreflight(mContext, mOriginatorIdentity, CAPTURE_AUDIO_HOTWORD); - return true; - } catch (SecurityException e) { - Slog.e(TAG, e.toString()); - return false; - } - } - - /** - * Throws a {@link SecurityException} if originator permanently doesn't have the given - * permission. - * Soft (temporary) denials are considered OK for preflight purposes. - * - * @param context A {@link Context}, used for permission checks. - * @param identity The identity to check. - * @param permission The identifier of the permission we want to check. - */ - static void enforcePermissionForPreflight(@NonNull Context context, - @NonNull Identity identity, @NonNull String permission) { - final int status = PermissionUtil.checkPermissionForPreflight(context, identity, - permission); - switch (status) { - case PermissionChecker.PERMISSION_GRANTED: - case PermissionChecker.PERMISSION_SOFT_DENIED: - return; - case PermissionChecker.PERMISSION_HARD_DENIED: - throw new SecurityException( - TextUtils.formatSimple("Failed to obtain permission %s for identity %s", - permission, toString(identity))); - default: - throw new RuntimeException("Unexpected permission check result."); - } - } - - static String toString(Identity identity) { - return "{uid=" + identity.uid - + " pid=" + identity.pid - + " packageName=" + identity.packageName - + " attributionTag=" + identity.attributionTag - + "}"; - } - - // Temporary hack for using the same status code as SoundTrigger, so we don't change behavior. - // TODO: Reuse SoundTrigger code so we don't need to do this. - private static final int TEMPORARY_PERMISSION_DENIED = 3; -}