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

Commit 6182af7a authored by Rhed Jao's avatar Rhed Jao
Browse files

Spoken feedback when a11y shortcut dialog is shown

Bug: 73901137
Test: atest AccessibilityServiceInfoTest
Test: atest AccessibilityShortcutControllerTest
Change-Id: I002458f3365883214578642cdf4aecdcb392ad46
parent 44b6289c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2905,6 +2905,7 @@ package android.accessibilityservice {
    field public static final deprecated int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
    field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
    field public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 512; // 0x200
    field public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1024; // 0x400
    field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
    field public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 64; // 0x40
    field public int eventTypes;
+9 −0
Original line number Diff line number Diff line
@@ -313,6 +313,12 @@ public class AccessibilityServiceInfo implements Parcelable {
     */
    public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 0x00000200;

    /**
     * This flag requests that accessibility shortcut warning dialog has spoken feedback when
     * dialog is shown.
     */
    public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 0x00000400;

    /** {@hide} */
    public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;

@@ -413,6 +419,7 @@ public class AccessibilityServiceInfo implements Parcelable {
     * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
     * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
     * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
     * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
     */
    public int flags;

@@ -1011,6 +1018,8 @@ public class AccessibilityServiceInfo implements Parcelable {
                return "FLAG_REQUEST_ACCESSIBILITY_BUTTON";
            case FLAG_REQUEST_FINGERPRINT_GESTURES:
                return "FLAG_REQUEST_FINGERPRINT_GESTURES";
            case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK:
                return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK";
            default:
                return null;
        }
+129 −21
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.internal.accessibility;

import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;

import static com.android.internal.util.ArrayUtils.convertToLongArray;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -34,23 +38,23 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;

import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.util.function.pooled.PooledLambda;

import java.util.Collections;
import java.util.Locale;
import java.util.Map;

import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;

import static com.android.internal.util.ArrayUtils.convertToLongArray;

/**
 * Class to help manage the accessibility shortcut
 */
@@ -70,6 +74,7 @@ public class AccessibilityShortcutController {
    private static Map<ComponentName, ToggleableFrameworkFeatureInfo> sFrameworkShortcutFeaturesMap;

    private final Context mContext;
    private final Handler mHandler;
    private AlertDialog mAlertDialog;
    private boolean mIsShortcutEnabled;
    private boolean mEnabledOnLockScreen;
@@ -123,6 +128,7 @@ public class AccessibilityShortcutController {

    public AccessibilityShortcutController(Context context, Handler handler, int initialUserId) {
        mContext = context;
        mHandler = handler;
        mUserId = initialUserId;

        // Keep track of state of shortcut settings
@@ -189,22 +195,6 @@ public class AccessibilityShortcutController {
        final int userId = ActivityManager.getCurrentUser();
        final int dialogAlreadyShown = Settings.Secure.getIntForUser(
                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
        // Use USAGE_ASSISTANCE_ACCESSIBILITY for TVs to ensure that TVs play the ringtone as they
        // have less ways of providing feedback like vibration.
        final int audioAttributesUsage = hasFeatureLeanback()
                ? AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
                : AudioAttributes.USAGE_NOTIFICATION_EVENT;

        // Play a notification tone
        final Ringtone tone =
                RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_NOTIFICATION_URI);
        if (tone != null) {
            tone.setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(audioAttributesUsage)
                .build());
            tone.play();
        }

        // Play a notification vibration
        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
        if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -223,6 +213,9 @@ public class AccessibilityShortcutController {
            if (mAlertDialog == null) {
                return;
            }
            if (!performTtsPrompt(mAlertDialog)) {
                playNotificationTone();
            }
            Window w = mAlertDialog.getWindow();
            WindowManager.LayoutParams attr = w.getAttributes();
            attr.type = TYPE_KEYGUARD_DIALOG;
@@ -231,6 +224,7 @@ public class AccessibilityShortcutController {
            Settings.Secure.putIntForUser(
                    cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId);
        } else {
            playNotificationTone();
            if (mAlertDialog != null) {
                mAlertDialog.dismiss();
                mAlertDialog = null;
@@ -344,6 +338,102 @@ public class AccessibilityShortcutController {
        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
    }

    private void playNotificationTone() {
        // Use USAGE_ASSISTANCE_ACCESSIBILITY for TVs to ensure that TVs play the ringtone as they
        // have less ways of providing feedback like vibration.
        final int audioAttributesUsage = hasFeatureLeanback()
                ? AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
                : AudioAttributes.USAGE_NOTIFICATION_EVENT;

        // Play a notification tone
        final Ringtone tone = mFrameworkObjectProvider.getRingtone(mContext,
                Settings.System.DEFAULT_NOTIFICATION_URI);
        if (tone != null) {
            tone.setAudioAttributes(new AudioAttributes.Builder()
                    .setUsage(audioAttributesUsage)
                    .build());
            tone.play();
        }
    }

    private boolean performTtsPrompt(AlertDialog alertDialog) {
        final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
        if (serviceInfo == null) {
            return false;
        }
        if ((serviceInfo.flags & AccessibilityServiceInfo
                .FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK) == 0) {
            return false;
        }
        final TtsPrompt tts = new TtsPrompt();
        alertDialog.setOnDismissListener(dialog -> tts.dismiss());
        return true;
    }

    /**
     * Class to wrap TextToSpeech for shortcut dialog spoken feedback.
     */
    private class TtsPrompt implements TextToSpeech.OnInitListener {
        private final CharSequence mText;
        private boolean mDismiss;
        private TextToSpeech mTts;

        TtsPrompt() {
            mText = mContext.getString(R.string.accessibility_shortcut_spoken_feedback);
            mTts = mFrameworkObjectProvider.getTextToSpeech(mContext, this);
        }

        /**
         * Releases the resources used by the TextToSpeech, when dialog dismiss.
         */
        public void dismiss() {
            mDismiss = true;
            mHandler.sendMessage(PooledLambda.obtainMessage(TextToSpeech::shutdown, mTts));
        }

        @Override
        public void onInit(int status) {
            if (status != TextToSpeech.SUCCESS) {
                Slog.d(TAG, "Tts init fail, status=" + Integer.toString(status));
                playNotificationTone();
                return;
            }
            mHandler.sendMessage(PooledLambda.obtainMessage(TtsPrompt::play, this));
        }

        private void play() {
            if (mDismiss) {
                return;
            }
            int status = TextToSpeech.ERROR;
            if (setLanguage(Locale.getDefault())) {
                status = mTts.speak(mText, TextToSpeech.QUEUE_FLUSH, null, null);
            }
            if (status != TextToSpeech.SUCCESS) {
                Slog.d(TAG, "Tts play fail");
                playNotificationTone();
            }
        }

        /**
         * @return false if tts language is not available
         */
        private boolean setLanguage(final Locale locale) {
            int status = mTts.isLanguageAvailable(locale);
            if (status == TextToSpeech.LANG_MISSING_DATA
                    || status == TextToSpeech.LANG_NOT_SUPPORTED) {
                return false;
            }
            mTts.setLanguage(locale);
            Voice voice = mTts.getVoice();
            if (voice == null || (voice.getFeatures() != null && voice.getFeatures()
                    .contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED))) {
                return false;
            }
            return true;
        }
    }

    /**
     * Immutable class to hold info about framework features that can be controlled by shortcut
     */
@@ -406,5 +496,23 @@ public class AccessibilityShortcutController {
        public Context getSystemUiContext() {
            return ActivityThread.currentActivityThread().getSystemUiContext();
        }

        /**
         * @param ctx A context for TextToSpeech
         * @param listener TextToSpeech initialization callback
         * @return TextToSpeech instance
         */
        public TextToSpeech getTextToSpeech(Context ctx, TextToSpeech.OnInitListener listener) {
            return new TextToSpeech(ctx, listener);
        }

        /**
         * @param ctx context for ringtone
         * @param uri ringtone uri
         * @return Ringtone instance
         */
        public Ringtone getRingtone(Context ctx, Uri uri) {
            return RingtoneManager.getRingtone(ctx, uri);
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -3599,6 +3599,8 @@
            <flag name="flagRequestAccessibilityButton" value="0x00000100" />
            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_FINGERPRINT_GESTURES}. -->
            <flag name="flagRequestFingerprintGestures" value="0x00000200" />
            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK}. -->
            <flag name="flagRequestShortcutWarningDialogSpokenFeedback" value="0x00000400" />
        </attr>
        <!-- Component name of an activity that allows the user to modify
             the settings for this service. This setting cannot be changed at runtime. -->
+3 −0
Original line number Diff line number Diff line
@@ -4331,6 +4331,9 @@
    <string name="accessibility_shortcut_disabling_service">Accessibility Shortcut turned
        <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> off</string>

    <!-- Text spoken when accessibility shortcut warning dialog is shown. [CHAR LIMIT=none] -->
    <string name="accessibility_shortcut_spoken_feedback">Use Accessibility Shortcut again to start the current accessibility feature</string>

    <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
    <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the Accessibility button:</string>

Loading