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

Commit 307d533c authored by Charles Chen's avatar Charles Chen Committed by charleschen
Browse files

[API] Introduce API for accessibility data stream

We provide an API to egress necessary data in the from of bytes only
when the accessibility setting is turned on for VisualQueryDetector. The
use of this API should be strictly reviewed and should only be used to
egress visual data related to accessibility features. Design:
go/vqds-vic-design

Bug: 318617199
Test: atest CtsVoiceInteractionTestCases
Flag: ACONFIG android.service.voice.flags.allow_complex_results_egress_from_vqds NEXTFOOD
Change-Id: I295bd8818058af8c89ce73c82d9c12ca22d367df
parent b7d18815
Loading
Loading
Loading
Loading
+5 −0
Original line number Original line Diff line number Diff line
@@ -13450,6 +13450,7 @@ package android.service.voice {
  @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final class VisualQueryDetectedResult implements android.os.Parcelable {
  @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final class VisualQueryDetectedResult implements android.os.Parcelable {
    method public int describeContents();
    method public int describeContents();
    method @Nullable public byte[] getAccessibilityDetectionData();
    method public static int getMaxSpeakerId();
    method public static int getMaxSpeakerId();
    method @NonNull public String getPartialQuery();
    method @NonNull public String getPartialQuery();
    method public int getSpeakerId();
    method public int getSpeakerId();
@@ -13460,6 +13461,7 @@ package android.service.voice {
  public static final class VisualQueryDetectedResult.Builder {
  public static final class VisualQueryDetectedResult.Builder {
    ctor public VisualQueryDetectedResult.Builder();
    ctor public VisualQueryDetectedResult.Builder();
    method @NonNull public android.service.voice.VisualQueryDetectedResult build();
    method @NonNull public android.service.voice.VisualQueryDetectedResult build();
    method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setAccessibilityDetectionData(@NonNull byte...);
    method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setPartialQuery(@NonNull String);
    method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setPartialQuery(@NonNull String);
    method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setSpeakerId(int);
    method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setSpeakerId(int);
  }
  }
@@ -13497,7 +13499,10 @@ package android.service.voice {
  }
  }
  public class VisualQueryDetector {
  public class VisualQueryDetector {
    method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void clearAccessibilityDetectionEnabledListener();
    method public void destroy();
    method public void destroy();
    method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public boolean isAccessibilityDetectionEnabled();
    method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void setAccessibilityDetectionEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
+64 −9
Original line number Original line Diff line number Diff line
@@ -68,6 +68,22 @@ public final class VisualQueryDetectedResult implements Parcelable {
        return 15;
        return 15;
    }
    }


    /**
     * Detected signal representing the arbitrary data that will make accessibility detections work.
     *
     * This field should only be set if the device setting for allowing accessibility data is on
     * based on the result of {@link VisualQueryDetector#isAccessibilityDetectionEnabled()}. If the
     * enable bit return by the method is {@code false}, it would suggest a failure to egress the
     * {@link VisualQueryDetectedResult} object with this field set. The system server will prevent
     * egress and invoke
     * {@link VisualQueryDetector.Callback#onFailure(VisualQueryDetectionServiceFailure)}.
     */
    @Nullable
    private final byte[] mAccessibilityDetectionData;
    private static byte[] defaultAccessibilityDetectionData() {
        return null;
    }

    private void onConstructed() {
    private void onConstructed() {
        Preconditions.checkArgumentInRange(mSpeakerId, 0, getMaxSpeakerId(), "speakerId");
        Preconditions.checkArgumentInRange(mSpeakerId, 0, getMaxSpeakerId(), "speakerId");
    }
    }
@@ -78,7 +94,10 @@ public final class VisualQueryDetectedResult implements Parcelable {
     * @hide
     * @hide
     */
     */
    public Builder buildUpon() {
    public Builder buildUpon() {
        return new Builder().setPartialQuery(mPartialQuery).setSpeakerId(mSpeakerId);
        return new Builder()
                .setPartialQuery(mPartialQuery)
                .setSpeakerId(mSpeakerId)
                .setAccessibilityDetectionData(mAccessibilityDetectionData);
    }
    }




@@ -98,11 +117,13 @@ public final class VisualQueryDetectedResult implements Parcelable {
    @DataClass.Generated.Member
    @DataClass.Generated.Member
    /* package-private */ VisualQueryDetectedResult(
    /* package-private */ VisualQueryDetectedResult(
            @NonNull String partialQuery,
            @NonNull String partialQuery,
            int speakerId) {
            int speakerId,
            @Nullable byte[] accessibilityDetectionData) {
        this.mPartialQuery = partialQuery;
        this.mPartialQuery = partialQuery;
        com.android.internal.util.AnnotationValidations.validate(
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPartialQuery);
                NonNull.class, null, mPartialQuery);
        this.mSpeakerId = speakerId;
        this.mSpeakerId = speakerId;
        this.mAccessibilityDetectionData = accessibilityDetectionData;


        onConstructed();
        onConstructed();
    }
    }
@@ -125,6 +146,16 @@ public final class VisualQueryDetectedResult implements Parcelable {
        return mSpeakerId;
        return mSpeakerId;
    }
    }


    /**
     * Detected signal representing the data for allowing accessibility feature. This field can
     * only be set when the secure device settings is set to true by either settings page UI or
     * {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)}
     */
    @DataClass.Generated.Member
    public @Nullable byte[] getAccessibilityDetectionData() {
        return mAccessibilityDetectionData;
    }

    @Override
    @Override
    @DataClass.Generated.Member
    @DataClass.Generated.Member
    public String toString() {
    public String toString() {
@@ -133,7 +164,8 @@ public final class VisualQueryDetectedResult implements Parcelable {


        return "VisualQueryDetectedResult { " +
        return "VisualQueryDetectedResult { " +
                "partialQuery = " + mPartialQuery + ", " +
                "partialQuery = " + mPartialQuery + ", " +
                "speakerId = " + mSpeakerId +
                "speakerId = " + mSpeakerId + ", " +
                "accessibilityDetectionData = " + java.util.Arrays.toString(mAccessibilityDetectionData) +
        " }";
        " }";
    }
    }


@@ -151,7 +183,8 @@ public final class VisualQueryDetectedResult implements Parcelable {
        //noinspection PointlessBooleanExpression
        //noinspection PointlessBooleanExpression
        return true
        return true
                && Objects.equals(mPartialQuery, that.mPartialQuery)
                && Objects.equals(mPartialQuery, that.mPartialQuery)
                && mSpeakerId == that.mSpeakerId;
                && mSpeakerId == that.mSpeakerId
                && java.util.Arrays.equals(mAccessibilityDetectionData, that.mAccessibilityDetectionData);
    }
    }


    @Override
    @Override
@@ -163,6 +196,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
        int _hash = 1;
        int _hash = 1;
        _hash = 31 * _hash + Objects.hashCode(mPartialQuery);
        _hash = 31 * _hash + Objects.hashCode(mPartialQuery);
        _hash = 31 * _hash + mSpeakerId;
        _hash = 31 * _hash + mSpeakerId;
        _hash = 31 * _hash + java.util.Arrays.hashCode(mAccessibilityDetectionData);
        return _hash;
        return _hash;
    }
    }


@@ -174,6 +208,7 @@ public final class VisualQueryDetectedResult implements Parcelable {


        dest.writeString(mPartialQuery);
        dest.writeString(mPartialQuery);
        dest.writeInt(mSpeakerId);
        dest.writeInt(mSpeakerId);
        dest.writeByteArray(mAccessibilityDetectionData);
    }
    }


    @Override
    @Override
@@ -189,11 +224,13 @@ public final class VisualQueryDetectedResult implements Parcelable {


        String partialQuery = in.readString();
        String partialQuery = in.readString();
        int speakerId = in.readInt();
        int speakerId = in.readInt();
        byte[] accessibilityDetectionData = in.createByteArray();


        this.mPartialQuery = partialQuery;
        this.mPartialQuery = partialQuery;
        com.android.internal.util.AnnotationValidations.validate(
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPartialQuery);
                NonNull.class, null, mPartialQuery);
        this.mSpeakerId = speakerId;
        this.mSpeakerId = speakerId;
        this.mAccessibilityDetectionData = accessibilityDetectionData;


        onConstructed();
        onConstructed();
    }
    }
@@ -221,6 +258,7 @@ public final class VisualQueryDetectedResult implements Parcelable {


        private @NonNull String mPartialQuery;
        private @NonNull String mPartialQuery;
        private int mSpeakerId;
        private int mSpeakerId;
        private @Nullable byte[] mAccessibilityDetectionData;


        private long mBuilderFieldsSet = 0L;
        private long mBuilderFieldsSet = 0L;


@@ -251,10 +289,23 @@ public final class VisualQueryDetectedResult implements Parcelable {
            return this;
            return this;
        }
        }


        /**
         * Detected signal representing the data for allowing accessibility feature. This field can
         * only be set when the secure device settings is set to true by either settings page UI or
         * {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)}
         */
        @DataClass.Generated.Member
        public @NonNull Builder setAccessibilityDetectionData(@NonNull byte... value) {
            checkNotUsed();
            mBuilderFieldsSet |= 0x4;
            mAccessibilityDetectionData = value;
            return this;
        }

        /** Builds the instance. This builder should not be touched after calling this! */
        /** Builds the instance. This builder should not be touched after calling this! */
        public @NonNull VisualQueryDetectedResult build() {
        public @NonNull VisualQueryDetectedResult build() {
            checkNotUsed();
            checkNotUsed();
            mBuilderFieldsSet |= 0x4; // Mark builder used
            mBuilderFieldsSet |= 0x8; // Mark builder used


            if ((mBuilderFieldsSet & 0x1) == 0) {
            if ((mBuilderFieldsSet & 0x1) == 0) {
                mPartialQuery = defaultPartialQuery();
                mPartialQuery = defaultPartialQuery();
@@ -262,14 +313,18 @@ public final class VisualQueryDetectedResult implements Parcelable {
            if ((mBuilderFieldsSet & 0x2) == 0) {
            if ((mBuilderFieldsSet & 0x2) == 0) {
                mSpeakerId = defaultSpeakerId();
                mSpeakerId = defaultSpeakerId();
            }
            }
            if ((mBuilderFieldsSet & 0x4) == 0) {
                mAccessibilityDetectionData = defaultAccessibilityDetectionData();
            }
            VisualQueryDetectedResult o = new VisualQueryDetectedResult(
            VisualQueryDetectedResult o = new VisualQueryDetectedResult(
                    mPartialQuery,
                    mPartialQuery,
                    mSpeakerId);
                    mSpeakerId,
                    mAccessibilityDetectionData);
            return o;
            return o;
        }
        }


        private void checkNotUsed() {
        private void checkNotUsed() {
            if ((mBuilderFieldsSet & 0x4) != 0) {
            if ((mBuilderFieldsSet & 0x8) != 0) {
                throw new IllegalStateException(
                throw new IllegalStateException(
                        "This Builder should not be reused. Use a new Builder instance instead");
                        "This Builder should not be reused. Use a new Builder instance instead");
            }
            }
@@ -277,10 +332,10 @@ public final class VisualQueryDetectedResult implements Parcelable {
    }
    }


    @DataClass.Generated(
    @DataClass.Generated(
            time = 1704949386772L,
            time = 1707429290528L,
            codegenVersion = "1.0.23",
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryDetectedResult.java",
            sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryDetectedResult.java",
            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final  int mSpeakerId\nprivate static  java.lang.String defaultPartialQuery()\nprivate static  int defaultSpeakerId()\npublic static  int getMaxSpeakerId()\nprivate  void onConstructed()\npublic  android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final  int mSpeakerId\nprivate final @android.annotation.Nullable byte[] mAccessibilityDetectionData\nprivate static  java.lang.String defaultPartialQuery()\nprivate static  int defaultSpeakerId()\npublic static  int getMaxSpeakerId()\nprivate static  byte[] defaultAccessibilityDetectionData()\nprivate  void onConstructed()\npublic  android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
    @Deprecated
    @Deprecated
    private void __metadata() {}
    private void __metadata() {}


+107 −0
Original line number Original line Diff line number Diff line
@@ -39,6 +39,7 @@ import android.text.TextUtils;
import android.util.Slog;
import android.util.Slog;


import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.AndroidFuture;


@@ -61,6 +62,8 @@ import java.util.function.Consumer;
public class VisualQueryDetector {
public class VisualQueryDetector {
    private static final String TAG = VisualQueryDetector.class.getSimpleName();
    private static final String TAG = VisualQueryDetector.class.getSimpleName();
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = false;
    private static final int SETTINGS_DISABLE_BIT = 0;
    private static final int SETTINGS_ENABLE_BIT = 1;


    private final Callback mCallback;
    private final Callback mCallback;
    private final Executor mExecutor;
    private final Executor mExecutor;
@@ -68,6 +71,8 @@ public class VisualQueryDetector {
    private final IVoiceInteractionManagerService mManagerService;
    private final IVoiceInteractionManagerService mManagerService;
    private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
    private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
    private final String mAttributionTag;
    private final String mAttributionTag;
    // Used to manage the internal mapping of exposed listener API and internal aidl impl
    private AccessibilityDetectionEnabledListenerWrapper mActiveAccessibilityListenerWrapper = null;


    VisualQueryDetector(
    VisualQueryDetector(
            IVoiceInteractionManagerService managerService,
            IVoiceInteractionManagerService managerService,
@@ -174,6 +179,108 @@ public class VisualQueryDetector {
        }
        }
    }
    }


    /**
     * Gets the binary value that controls the egress of accessibility data from
     * {@link VisualQueryDetectedResult#setAccessibilityDetectionData(byte[])} is enabled.
     *
     * @return boolean value denoting if the setting is on. Default is {@code false}.
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
    public boolean isAccessibilityDetectionEnabled() {
        Slog.d(TAG, "Fetching accessibility setting");
        synchronized (mInitializationDelegate.getLock()) {
            try {
                return mManagerService.getAccessibilityDetectionEnabled();
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
            return false;
        }
    }

    /**
     * Sets a listener subscribing to the value of the system setting that controls the egress of
     * accessibility data from
     * {@link VisualQueryDetectedResult#setAccessibilityDetectionData(byte[])} is enabled.
     *
     * Only one listener can be set at a time. The listener set must be unset with
     * {@link clearAccessibilityDetectionEnabledListener(Consumer<Boolean>)}
     * in order to set a new listener. Otherwise, this method will throw a
     * {@link IllegalStateException}.
     *
     * @param listener Listener of type {@code Consumer<Boolean>} to subscribe to the value update.
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
    public void setAccessibilityDetectionEnabledListener(@NonNull Consumer<Boolean> listener) {
        Slog.d(TAG, "Registering Accessibility settings listener.");
        synchronized (mInitializationDelegate.getLock()) {
            try {
                if (mActiveAccessibilityListenerWrapper != null) {
                    Slog.e(TAG, "Fail to register accessibility setting listener: "
                            + "already registered and not unregistered.");
                    throw new IllegalStateException(
                            "Cannot register listener with listeners already set.");
                }
                mActiveAccessibilityListenerWrapper =
                        new AccessibilityDetectionEnabledListenerWrapper(listener);
                mManagerService.registerAccessibilityDetectionSettingsListener(
                        mActiveAccessibilityListenerWrapper);
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Clear the listener that has been set with
     * {@link setAccessibilityDetectionEnabledListener(Consumer<Boolean>)} such that when the value
     * of the setting that controls the egress of accessibility data is changed the listener gets
     * notified.
     *
     * If there is not listener that has been registered, the call to this method will lead to a
     * {@link IllegalStateException}.
     *
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
    public void clearAccessibilityDetectionEnabledListener() {
        Slog.d(TAG, "Unregistering Accessibility settings listener.");
        synchronized (mInitializationDelegate.getLock()) {
            try {
                if (mActiveAccessibilityListenerWrapper == null) {
                    Slog.e(TAG, "Not able to remove the listener: listener does not exist.");
                    throw new IllegalStateException("Cannot clear listener since it is not set.");
                }
                mManagerService.unregisterAccessibilityDetectionSettingsListener(
                        mActiveAccessibilityListenerWrapper);
                mActiveAccessibilityListenerWrapper = null;
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        }
    }


    private final class AccessibilityDetectionEnabledListenerWrapper
            extends IVoiceInteractionAccessibilitySettingsListener.Stub {

        private Consumer<Boolean> mListener;

        AccessibilityDetectionEnabledListenerWrapper(Consumer<Boolean> listener) {
            mListener = listener;
        }

        @Override
        public void onAccessibilityDetectionChanged(boolean enabled) {
            mListener.accept(enabled);
        }
    }

    /** @hide */
    /** @hide */
    public void dump(String prefix, PrintWriter pw) {
    public void dump(String prefix, PrintWriter pw) {
        synchronized (mInitializationDelegate.getLock()) {
        synchronized (mInitializationDelegate.getLock()) {
+24 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.app;

oneway interface IVoiceInteractionAccessibilitySettingsListener {
   /**
    * Called when the value of secure setting has changed.
    */
   void onAccessibilityDetectionChanged(boolean enable);
}
+18 −0
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import android.service.voice.VisibleActivityInfo;


import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener;
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
@@ -382,4 +383,21 @@ interface IVoiceInteractionManagerService {
    oneway void notifyActivityEventChanged(
    oneway void notifyActivityEventChanged(
            in IBinder activityToken,
            in IBinder activityToken,
            int type);
            int type);

    /**
     * rely on the system server to get the secure settings
     */
    boolean getAccessibilityDetectionEnabled();

    /**
     * register the listener
     */
    oneway void registerAccessibilityDetectionSettingsListener(
            in IVoiceInteractionAccessibilitySettingsListener listener);

    /**
     * unregister the listener
     */
     oneway void unregisterAccessibilityDetectionSettingsListener(
            in IVoiceInteractionAccessibilitySettingsListener listener);
}
}
Loading