diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8385a299ab20f..db1e4e0942573 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -12120,6 +12120,7 @@ package android.service.voice { method public static int getMaxScore(); method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent(); method public int getPersonalizedScore(); + method public int getProximity(); method public int getScore(); method public boolean isHotwordDetectionPersonalized(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -12133,6 +12134,9 @@ package android.service.voice { field public static final int CONFIDENCE_LEVEL_VERY_HIGH = 6; // 0x6 field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff + field public static final int PROXIMITY_FAR = 2; // 0x2 + field public static final int PROXIMITY_NEAR = 1; // 0x1 + field public static final int PROXIMITY_UNKNOWN = -1; // 0xffffffff } public static final class HotwordDetectedResult.Builder { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d8419a87edc2a..535f6cee041c9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -758,6 +758,7 @@ package android.content { method public int getUserId(); method public void setAutofillOptions(@Nullable android.content.AutofillOptions); method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions); + field public static final String ATTENTION_SERVICE = "attention"; field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; field public static final String DREAM_SERVICE = "dream"; @@ -2499,6 +2500,10 @@ package android.service.voice { method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List); } + public abstract class HotwordDetectionService extends android.app.Service { + field public static final boolean ENABLE_PROXIMITY_RESULT = true; + } + public final class VisibleActivityInfo implements android.os.Parcelable { ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 1df0fa8084a5f..ae1f68958d0f1 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4917,6 +4917,7 @@ public abstract class Context { * @see android.server.attention.AttentionManagerService * @hide */ + @TestApi public static final String ATTENTION_SERVICE = "attention"; /** diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index b4f20c910a4a1..dee560b322bb5 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -31,6 +31,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; @@ -99,14 +101,30 @@ 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. - * - * @hide */ - public static final String EXTRA_PROXIMITY_METERS = - "android.service.voice.extra.PROXIMITY_METERS"; + private static final String EXTRA_PROXIMITY = + "android.service.voice.extra.PROXIMITY"; + + /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */ + public static final int PROXIMITY_UNKNOWN = -1; + + /** Proximity value that represents that the object is near. */ + public static final int PROXIMITY_NEAR = 1; + + /** Proximity value that represents that the object is far. */ + 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 @@ -220,12 +238,14 @@ public final class HotwordDetectedResult implements Parcelable { * versions of Android. * *

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. * *

This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. @@ -348,16 +368,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"); @@ -372,6 +392,52 @@ public final class HotwordDetectedResult implements Parcelable { return List.copyOf(mAudioStreams); } + /** + * 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); + } + } + + /** + * Returns proximity level, which can be either of {@link HotwordDetectedResult#PROXIMITY_NEAR} + * or {@link HotwordDetectedResult#PROXIMITY_FAR}. If the proximity is unknown, it will + * return {@link HotwordDetectedResult#PROXIMITY_UNKNOWN}. + */ + @ProximityValue + public int getProximity() { + return mExtras.getInt(EXTRA_PROXIMITY, PROXIMITY_UNKNOWN); + } + + /** + * 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; + } + } + @DataClass.Suppress("addAudioStreams") abstract static class BaseBuilder { /** @@ -432,7 +498,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 {} @@ -463,7 +529,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 {} @@ -479,6 +545,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, @@ -605,12 +695,14 @@ public final class HotwordDetectedResult implements Parcelable { * versions of Android. * *

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. * *

This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. @@ -923,12 +1015,14 @@ public final class HotwordDetectedResult implements Parcelable { * versions of Android. * *

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. * *

This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. @@ -1003,10 +1097,10 @@ public final class HotwordDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1666342044844L, + time = 1668385264834L, 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 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 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.annotation.NonNull java.util.List getAudioStreams()\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List)\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.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List)\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 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 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.annotation.NonNull java.util.List getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List)\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.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index df69cc00709c6..552a793b6350c 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Service; import android.content.ContentCaptureOptions; import android.content.Context; @@ -90,10 +91,10 @@ public abstract class HotwordDetectionService extends Service { /** * Feature flag for Attention Service. * - * TODO(b/247920386): Add TestApi annotation * @hide */ - public static final boolean ENABLE_PROXIMITY_RESULT = false; + @TestApi + public static final boolean ENABLE_PROXIMITY_RESULT = true; /** * Indicates that the updated status is successful. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 1fc54d6bbcaea..0721c281f15c6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -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; @@ -206,7 +205,7 @@ final class HotwordDetectionConnection { @Nullable AttentionManagerInternal mAttentionManagerInternal = null; final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal = - this::setProximityMeters; + this::setProximityValue; volatile HotwordDetectionServiceIdentity mIdentity; @@ -504,7 +503,7 @@ final class HotwordDetectionConnection { mSoftwareCallback.onError(); return; } - saveProximityMetersToBundle(result); + saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); @@ -639,7 +638,7 @@ final class HotwordDetectionConnection { externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION); return; } - saveProximityMetersToBundle(result); + saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); @@ -1242,15 +1241,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; }