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

Commit a8479861 authored by Daniel's avatar Daniel
Browse files

Update the proximity feature to return proximity levels

The original proximity feature which returned the distance in meters was
determined to be too privacy-invasive. AttentionManagerService continue
to receive proximity in distance from Attention Service, but
HotwordDetectedResult will map it
to different proximity levels, near and far. These proximity levels will be used in HotwordDetectionConnection. Use a RequiredServiceRule to make sure we only run the test if the device has the attention service

Bug: 253326426
Test: atest CtsVoiceInteractionTestCases
Change-Id: I4d55564c39d48dd1869d747beb37f81170c102f6
Merged-In: I4d55564c39d48dd1869d747beb37f81170c102f6
parent c9de1e52
Loading
Loading
Loading
Loading
+125 −29
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ import com.android.internal.R;
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -100,14 +102,42 @@ public final class HotwordDetectedResult implements Parcelable {
    private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;

    /**
     * The bundle key for proximity value
     * The bundle key for proximity
     *
     * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
     */
    private static final String EXTRA_PROXIMITY =
            "android.service.voice.extra.PROXIMITY";

    /**
     * Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported).
     *
     * @hide
     */
    public static final String EXTRA_PROXIMITY_METERS =
            "android.service.voice.extra.PROXIMITY_METERS";
    public static final int PROXIMITY_UNKNOWN = -1;

    /**
     * Proximity value that represents that the object is near.
     *
     * @hide
     */
    public static final int PROXIMITY_NEAR = 1;

    /**
     * Proximity value that represents that the object is far.
     *
     * @hide
     */
    public static final int PROXIMITY_FAR = 2;

    /** @hide */
    @IntDef(prefix = {"PROXIMITY"}, value = {
            PROXIMITY_UNKNOWN,
            PROXIMITY_NEAR,
            PROXIMITY_FAR
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ProximityValue {}

    /** Confidence level in the trigger outcome. */
    @HotwordConfidenceLevelValue
@@ -223,12 +253,14 @@ public final class HotwordDetectedResult implements Parcelable {
     * versions of Android.
     *
     * <p>After the trigger happens, a special case of proximity-related extra, with the key of
     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
     * will be stored to enable proximity logic. The proximity meters is provided by the system,
     * on devices that support detecting proximity of nearby users, to help disambiguate which
     * nearby device should respond. When the proximity is unknown, the proximity value will not
     * be stored. This mapping will be excluded from the max bundle size calculation because this
     * mapping is included after the result is returned from the hotword detector service.
     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
     * proximity. The proximity value is provided by the system, on devices that support detecting
     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
     * from the max bundle size calculation because this mapping is included after the result is
     * returned from the hotword detector service.
     *
     * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
     * that can be used to communicate with other processes.
@@ -351,16 +383,16 @@ public final class HotwordDetectedResult implements Parcelable {
            // Remove the proximity key from the bundle before checking the bundle size. The
            // proximity value is added after the privileged module and can avoid the
            // maxBundleSize limitation.
            if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
                double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
                mExtras.remove(EXTRA_PROXIMITY_METERS);
            if (mExtras.containsKey(EXTRA_PROXIMITY)) {
                int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
                mExtras.remove(EXTRA_PROXIMITY);
                // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
                // has parcelable size of 4, but the default bundle has parcelable size of 0.
                if (mExtras.size() > 0) {
                    Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                            getMaxBundleSize(), "extras");
                }
                mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
                mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
            } else {
                Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                        getMaxBundleSize(), "extras");
@@ -416,6 +448,42 @@ public final class HotwordDetectedResult implements Parcelable {
            .setExtras(mExtras);
    }

    /**
     * Adds proximity level, either near or far, that is mapped for the given distance into
     * the bundle. The proximity value is provided by the system, on devices that support detecting
     * proximity of nearby users, to help disambiguate which nearby device should respond.
     * This mapping will be excluded from the max bundle size calculation because this mapping is
     * included after the result is returned from the hotword detector service. The value will not
     * be included if the proximity was unknown.
     *
     * @hide
     */
    public void setProximity(double distance) {
        int proximityLevel = convertToProximityLevel(distance);
        if (proximityLevel != PROXIMITY_UNKNOWN) {
            mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
        }
    }

    /**
     * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
     * Currently, this mapping is handled by HotwordDetectedResult because it handles just
     * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
     * move to a more centralized place once there are more clients.
     *
     * TODO(b/258531144): Move the proximity mapping to a central location
     */
    @ProximityValue
    private int convertToProximityLevel(double distance) {
        if (distance < 0) {
            return PROXIMITY_UNKNOWN;
        } else if (distance <= 3) {
            return PROXIMITY_NEAR;
        } else {
            return PROXIMITY_FAR;
        }
    }



    // Code below generated by codegen v1.0.23.
@@ -441,7 +509,7 @@ public final class HotwordDetectedResult implements Parcelable {
        CONFIDENCE_LEVEL_HIGH,
        CONFIDENCE_LEVEL_VERY_HIGH
    })
    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    @DataClass.Generated.Member
    public @interface ConfidenceLevel {}

@@ -472,7 +540,7 @@ public final class HotwordDetectedResult implements Parcelable {
        LIMIT_HOTWORD_OFFSET_MAX_VALUE,
        LIMIT_AUDIO_CHANNEL_MAX_VALUE
    })
    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    @DataClass.Generated.Member
    /* package-private */ @interface Limit {}

@@ -488,6 +556,30 @@ public final class HotwordDetectedResult implements Parcelable {
        }
    }

    /** @hide */
    @IntDef(prefix = "PROXIMITY_", value = {
        PROXIMITY_UNKNOWN,
        PROXIMITY_NEAR,
        PROXIMITY_FAR
    })
    @Retention(RetentionPolicy.SOURCE)
    @DataClass.Generated.Member
    public @interface Proximity {}

    /** @hide */
    @DataClass.Generated.Member
    public static String proximityToString(@Proximity int value) {
        switch (value) {
            case PROXIMITY_UNKNOWN:
                    return "PROXIMITY_UNKNOWN";
            case PROXIMITY_NEAR:
                    return "PROXIMITY_NEAR";
            case PROXIMITY_FAR:
                    return "PROXIMITY_FAR";
            default: return Integer.toHexString(value);
        }
    }

    @DataClass.Generated.Member
    /* package-private */ HotwordDetectedResult(
            @HotwordConfidenceLevelValue int confidenceLevel,
@@ -614,12 +706,14 @@ public final class HotwordDetectedResult implements Parcelable {
     * versions of Android.
     *
     * <p>After the trigger happens, a special case of proximity-related extra, with the key of
     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
     * will be stored to enable proximity logic. The proximity meters is provided by the system,
     * on devices that support detecting proximity of nearby users, to help disambiguate which
     * nearby device should respond. When the proximity is unknown, the proximity value will not
     * be stored. This mapping will be excluded from the max bundle size calculation because this
     * mapping is included after the result is returned from the hotword detector service.
     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
     * proximity. The proximity value is provided by the system, on devices that support detecting
     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
     * from the max bundle size calculation because this mapping is included after the result is
     * returned from the hotword detector service.
     *
     * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
     * that can be used to communicate with other processes.
@@ -932,12 +1026,14 @@ public final class HotwordDetectedResult implements Parcelable {
         * versions of Android.
         *
         * <p>After the trigger happens, a special case of proximity-related extra, with the key of
         * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
         * will be stored to enable proximity logic. The proximity meters is provided by the system,
         * on devices that support detecting proximity of nearby users, to help disambiguate which
         * nearby device should respond. When the proximity is unknown, the proximity value will not
         * be stored. This mapping will be excluded from the max bundle size calculation because this
         * mapping is included after the result is returned from the hotword detector service.
         * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
         * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
         * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
         * proximity. The proximity value is provided by the system, on devices that support detecting
         * proximity of nearby users, to help disambiguate which nearby device should respond. When the
         * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
         * from the max bundle size calculation because this mapping is included after the result is
         * returned from the hotword detector service.
         *
         * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
         * that can be used to communicate with other processes.
@@ -1012,10 +1108,10 @@ public final class HotwordDetectedResult implements Parcelable {
    }

    @DataClass.Generated(
            time = 1668466781144L,
            time = 1668528946960L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\npublic  void setProximity(double)\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
    @Deprecated
    private void __metadata() {}

+1 −1
Original line number Diff line number Diff line
@@ -92,7 +92,7 @@ public abstract class HotwordDetectionService extends Service {
     * TODO(b/247920386): Add TestApi annotation
     * @hide
     */
    public static final boolean ENABLE_PROXIMITY_RESULT = false;
    public static final boolean ENABLE_PROXIMITY_RESULT = true;

    /**
     * Indicates that the updated status is successful.
+7 −8
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.server.voiceinteraction;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -193,7 +192,7 @@ final class HotwordDetectionConnection {
    @Nullable AttentionManagerInternal mAttentionManagerInternal = null;

    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
            this::setProximityMeters;
            this::setProximityValue;


    volatile HotwordDetectionServiceIdentity mIdentity;
@@ -488,7 +487,7 @@ final class HotwordDetectionConnection {
                        mSoftwareCallback.onError();
                        return;
                    }
                    saveProximityMetersToBundle(result);
                    saveProximityValueToBundle(result);
                    HotwordDetectedResult newResult;
                    try {
                        newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -599,7 +598,7 @@ final class HotwordDetectionConnection {
                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                        return;
                    }
                    saveProximityMetersToBundle(result);
                    saveProximityValueToBundle(result);
                    externalCallback.onKeyphraseDetected(recognitionEvent, result);
                    if (result != null) {
                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -680,7 +679,7 @@ final class HotwordDetectionConnection {
                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                        return;
                    }
                    saveProximityMetersToBundle(result);
                    saveProximityValueToBundle(result);
                    HotwordDetectedResult newResult;
                    try {
                        newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -1244,15 +1243,15 @@ final class HotwordDetectionConnection {
        });
    }

    private void saveProximityMetersToBundle(HotwordDetectedResult result) {
    private void saveProximityValueToBundle(HotwordDetectedResult result) {
        synchronized (mLock) {
            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
                result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
                result.setProximity(mProximityMeters);
            }
        }
    }

    private void setProximityMeters(double proximityMeters) {
    private void setProximityValue(double proximityMeters) {
        synchronized (mLock) {
            mProximityMeters = proximityMeters;
        }