Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 189d6050 authored by Ytai Ben-tsvi's avatar Ytai Ben-tsvi Committed by Android (Google) Code Review
Browse files

Merge changes from topic "new-perm"

* changes:
  Demote AlwaysOnHotwordDetector to SystemApi
  Sessionize VoiceInteractionManagerService
  Sessionize the SoundTriggerService layer.
  Use standard API in SoundTriggerTestApp
  Fix bug in dumpsys
  Require identity information in SoundTrigger.java
  Associate an originator identity to sessions
  Extract permission checking as a separate aspect
  Correctly handle HAL death
parents 89008da4 c4d23a48
Loading
Loading
Loading
Loading
+0 −46
Original line number Diff line number Diff line
@@ -44287,54 +44287,8 @@ package android.service.textservice {
package android.service.voice {
  public class AlwaysOnHotwordDetector {
    method public android.content.Intent createEnrollIntent();
    method public android.content.Intent createReEnrollIntent();
    method public android.content.Intent createUnEnrollIntent();
    method public int getParameter(int);
    method public int getSupportedAudioCapabilities();
    method public int getSupportedRecognitionModes();
    method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
    method public int setParameter(int, int);
    method public boolean startRecognition(int);
    method public boolean stopRecognition();
    field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
    field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
    field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
    field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
    field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
    field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
    field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
    field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
    field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
    field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
  }
  public abstract static class AlwaysOnHotwordDetector.Callback {
    ctor public AlwaysOnHotwordDetector.Callback();
    method public abstract void onAvailabilityChanged(int);
    method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
    method public abstract void onError();
    method public abstract void onRecognitionPaused();
    method public abstract void onRecognitionResumed();
  }
  public static class AlwaysOnHotwordDetector.EventPayload {
    method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
    method @Nullable public byte[] getTriggerAudio();
  }
  public static final class AlwaysOnHotwordDetector.ModelParamRange {
    method public int getEnd();
    method public int getStart();
  }
  public class VoiceInteractionService extends android.app.Service {
    ctor public VoiceInteractionService();
    method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
    method public int getDisabledShowContext();
    method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
    method public android.os.IBinder onBind(android.content.Intent);
+46 −0
Original line number Diff line number Diff line
@@ -10272,7 +10272,53 @@ package android.service.trust {
package android.service.voice {
  public class AlwaysOnHotwordDetector {
    method @Nullable public android.content.Intent createEnrollIntent();
    method @Nullable public android.content.Intent createReEnrollIntent();
    method @Nullable public android.content.Intent createUnEnrollIntent();
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int);
    method public int getSupportedAudioCapabilities();
    method public int getSupportedRecognitionModes();
    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
    field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
    field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
    field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
    field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
    field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
    field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
    field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
    field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
    field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
    field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
  }
  public abstract static class AlwaysOnHotwordDetector.Callback {
    ctor public AlwaysOnHotwordDetector.Callback();
    method public abstract void onAvailabilityChanged(int);
    method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
    method public abstract void onError();
    method public abstract void onRecognitionPaused();
    method public abstract void onRecognitionResumed();
  }
  public static class AlwaysOnHotwordDetector.EventPayload {
    method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
    method @Nullable public byte[] getTriggerAudio();
  }
  public static final class AlwaysOnHotwordDetector.ModelParamRange {
    method public int getEnd();
    method public int getStart();
  }
  public class VoiceInteractionService extends android.app.Service {
    method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
    method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
  }
+166 −18
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package android.hardware.soundtrigger;

import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ENOSYS;
@@ -27,6 +30,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -34,9 +38,13 @@ import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioFormat;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.Identity;
import android.media.permission.SafeCloseable;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.media.soundtrigger_middleware.Status;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -1943,16 +1951,6 @@ public class SoundTrigger {
    public static final int SERVICE_STATE_DISABLED = 1;
    private static Object mServiceLock = new Object();
    private static ISoundTriggerMiddlewareService mService;
   /**
     * @return returns current package name.
     */
    static String getCurrentOpPackageName() {
        String packageName = ActivityThread.currentOpPackageName();
        if (packageName == null) {
            return "";
        }
        return packageName;
    }

    /**
     * Translate an exception thrown from interaction with the underlying service to an error code.
@@ -2005,23 +2003,103 @@ public class SoundTrigger {
     *         - {@link #STATUS_BAD_VALUE} if modules is null
     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
     *
     * @deprecated Please use {@link #listModulesAsOriginator(ArrayList, Identity)} or
     * {@link #listModulesAsMiddleman(ArrayList, Identity, Identity)}, based on whether the
     * client is acting on behalf of its own identity or a separate identity.
     * @hide
     */
    @UnsupportedAppUsage
    public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
        // TODO(ytai): This is a temporary hack to retain prior behavior, which makes
        //  assumptions about process affinity and Binder context, namely that the binder calling ID
        //  reliably reflects the originator identity.
        Identity middlemanIdentity = new Identity();
        middlemanIdentity.packageName = ActivityThread.currentOpPackageName();

        Identity originatorIdentity = new Identity();
        originatorIdentity.pid = Binder.getCallingPid();
        originatorIdentity.uid = Binder.getCallingUid();

        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
            return listModulesAsMiddleman(modules, middlemanIdentity, originatorIdentity);
        }
    }

    /**
     * Returns a list of descriptors for all hardware modules loaded.
     * This variant is intended for use when the caller itself is the originator of the operation.
     * @param modules A ModuleProperties array where the list will be returned.
     * @param originatorIdentity The identity of the originator, which will be used for permission
     *                           purposes.
     * @return - {@link #STATUS_OK} in case of success
     *         - {@link #STATUS_ERROR} in case of unspecified error
     *         - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
     *         - {@link #STATUS_NO_INIT} if the native service cannot be reached
     *         - {@link #STATUS_BAD_VALUE} if modules is null
     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
     *
     * @hide
     */
    @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
    public static int listModulesAsOriginator(@NonNull ArrayList<ModuleProperties> modules,
            @NonNull Identity originatorIdentity) {
        try {
            SoundTriggerModuleDescriptor[] descs = getService().listModules();
            modules.clear();
            modules.ensureCapacity(descs.length);
            for (SoundTriggerModuleDescriptor desc : descs) {
                modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
            SoundTriggerModuleDescriptor[] descs = getService().listModulesAsOriginator(
                    originatorIdentity);
            convertDescriptorsToModuleProperties(descs, modules);
            return STATUS_OK;
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Returns a list of descriptors for all hardware modules loaded.
     * This variant is intended for use when the caller is acting on behalf of a different identity
     * for permission purposes.
     * @param modules A ModuleProperties array where the list will be returned.
     * @param middlemanIdentity The identity of the caller, acting as middleman.
     * @param originatorIdentity The identity of the originator, which will be used for permission
     *                           purposes.
     * @return - {@link #STATUS_OK} in case of success
     *         - {@link #STATUS_ERROR} in case of unspecified error
     *         - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
     *         - {@link #STATUS_NO_INIT} if the native service cannot be reached
     *         - {@link #STATUS_BAD_VALUE} if modules is null
     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
     *
     * @hide
     */
    @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY)
    public static int listModulesAsMiddleman(@NonNull ArrayList<ModuleProperties> modules,
            @NonNull Identity middlemanIdentity,
            @NonNull Identity originatorIdentity) {
        try {
            SoundTriggerModuleDescriptor[] descs = getService().listModulesAsMiddleman(
                    middlemanIdentity, originatorIdentity);
            convertDescriptorsToModuleProperties(descs, modules);
            return STATUS_OK;
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Converts an array of SoundTriggerModuleDescriptor into an (existing) ArrayList of
     * ModuleProperties.
     * @param descsIn The input descriptors.
     * @param modulesOut The output list.
     */
    private static void convertDescriptorsToModuleProperties(
            @NonNull SoundTriggerModuleDescriptor[] descsIn,
            @NonNull ArrayList<ModuleProperties> modulesOut) {
        modulesOut.clear();
        modulesOut.ensureCapacity(descsIn.length);
        for (SoundTriggerModuleDescriptor desc : descsIn) {
            modulesOut.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
        }
    }

    /**
     * Get an interface on a hardware module to control sound models and recognition on
     * this module.
@@ -2031,15 +2109,85 @@ public class SoundTrigger {
     *                is OK.
     * @return a valid sound module in case of success or null in case of error.
     *
     * @deprecated Please use
     * {@link #attachModuleAsOriginator(int, StatusListener, Handler, Identity)} or
     * {@link #attachModuleAsMiddleman(int, StatusListener, Handler, Identity, Identity)}, based
     * on whether the client is acting on behalf of its own identity or a separate identity.
     * @hide
     */
    @UnsupportedAppUsage
    public static @NonNull SoundTriggerModule attachModule(int moduleId,
    private static SoundTriggerModule attachModule(int moduleId,
            @NonNull StatusListener listener,
            @Nullable Handler handler) {
        // TODO(ytai): This is a temporary hack to retain prior behavior, which makes
        //  assumptions about process affinity and Binder context, namely that the binder calling ID
        //  reliably reflects the originator identity.
        Identity middlemanIdentity = new Identity();
        middlemanIdentity.packageName = ActivityThread.currentOpPackageName();

        Identity originatorIdentity = new Identity();
        originatorIdentity.pid = Binder.getCallingPid();
        originatorIdentity.uid = Binder.getCallingUid();

        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
            return attachModuleAsMiddleman(moduleId, listener, handler, middlemanIdentity,
                    originatorIdentity);
        }
    }

    /**
     * Get an interface on a hardware module to control sound models and recognition on
     * this module.
     * This variant is intended for use when the caller is acting on behalf of a different identity
     * for permission purposes.
     * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory.
     * @param listener {@link StatusListener} interface. Mandatory.
     * @param handler the Handler that will receive the callabcks. Can be null if default handler
     *                is OK.
     * @param middlemanIdentity The identity of the caller, acting as middleman.
     * @param originatorIdentity The identity of the originator, which will be used for permission
     *                           purposes.
     * @return a valid sound module in case of success or null in case of error.
     *
     * @hide
     */
    @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY)
    public static SoundTriggerModule attachModuleAsMiddleman(int moduleId,
            @NonNull SoundTrigger.StatusListener listener,
            @Nullable Handler handler, Identity middlemanIdentity,
            Identity originatorIdentity) {
        Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
        try {
            return new SoundTriggerModule(getService(), moduleId, listener, looper,
                    middlemanIdentity, originatorIdentity);
        } catch (Exception e) {
            Log.e(TAG, "", e);
            return null;
        }
    }

    /**
     * Get an interface on a hardware module to control sound models and recognition on
     * this module.
     * This variant is intended for use when the caller itself is the originator of the operation.
     * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory.
     * @param listener {@link StatusListener} interface. Mandatory.
     * @param handler the Handler that will receive the callabcks. Can be null if default handler
     *                is OK.
     * @param originatorIdentity The identity of the originator, which will be used for permission
     *                           purposes.
     * @return a valid sound module in case of success or null in case of error.
     *
     * @hide
     */
    @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
    public static SoundTriggerModule attachModuleAsOriginator(int moduleId,
            @NonNull SoundTrigger.StatusListener listener,
            @Nullable Handler handler, @NonNull Identity originatorIdentity) {
        Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
        try {
            return new SoundTriggerModule(getService(), moduleId, listener, looper);
            return new SoundTriggerModule(getService(), moduleId, listener, looper,
                    originatorIdentity);
        } catch (Exception e) {
            Log.e(TAG, "", e);
            return null;
+32 −2
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package android.hardware.soundtrigger;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.Identity;
import android.media.permission.SafeCloseable;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -50,12 +53,39 @@ public class SoundTriggerModule {
    private EventHandlerDelegate mEventHandlerDelegate;
    private ISoundTriggerModule mService;

    /**
     * This variant is intended for use when the caller is acting an originator, rather than on
     * behalf of a different entity, as far as authorization goes.
     */
    SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
            int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
            int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
            @NonNull Identity originatorIdentity)
            throws RemoteException {
        mId = moduleId;
        mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
        mService = service.attach(moduleId, mEventHandlerDelegate);

        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
            mService = service.attachAsOriginator(moduleId, originatorIdentity,
                    mEventHandlerDelegate);
        }
        mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
    }

    /**
     * This variant is intended for use when the caller is acting as a middleman, i.e. on behalf of
     * a different entity, as far as authorization goes.
     */
    SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
            int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
            @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity)
            throws RemoteException {
        mId = moduleId;
        mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);

        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
            mService = service.attachAsMiddleman(moduleId, middlemanIdentity, originatorIdentity,
                    mEventHandlerDelegate);
        }
        mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
    }

+37 −7

File changed.

Preview size limit exceeded, changes collapsed.

Loading