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

Commit b8611fde authored by Dieter Hsu's avatar Dieter Hsu Committed by Android (Google) Code Review
Browse files

Merge "Retry TTS speaking for a11y shortcut warning dialog"

parents 799b2763 7fb7b739
Loading
Loading
Loading
Loading
+41 −17
Original line number Diff line number Diff line
@@ -412,8 +412,13 @@ public class AccessibilityShortcutController {
     * Class to wrap TextToSpeech for shortcut dialog spoken feedback.
     */
    private class TtsPrompt implements TextToSpeech.OnInitListener {
        private static final int RETRY_MILLIS = 1000;

        private final CharSequence mText;

        private int mRetryCount = 3;
        private boolean mDismiss;
        private boolean mLanguageReady = false;
        private TextToSpeech mTts;

        TtsPrompt(String serviceName) {
@@ -437,17 +442,15 @@ public class AccessibilityShortcutController {
                playNotificationTone();
                return;
            }
            mHandler.sendMessage(PooledLambda.obtainMessage(TtsPrompt::play, this));
            mHandler.sendMessage(PooledLambda.obtainMessage(
                    TtsPrompt::waitForTtsReady, this));
        }

        private void play() {
            if (mDismiss) {
                return;
            }
            int status = TextToSpeech.ERROR;
            if (setLanguage(Locale.getDefault())) {
                status = mTts.speak(mText, TextToSpeech.QUEUE_FLUSH, null, null);
            }
            final int status = mTts.speak(mText, TextToSpeech.QUEUE_FLUSH, null, null);
            if (status != TextToSpeech.SUCCESS) {
                Slog.d(TAG, "Tts play fail");
                playNotificationTone();
@@ -455,21 +458,42 @@ public class AccessibilityShortcutController {
        }

        /**
         * @return false if tts language is not available
         * Waiting for tts is ready to speak. Trying again if tts language pack is not available
         * or tts voice data is not installed yet.
         */
        private boolean setLanguage(final Locale locale) {
            int status = mTts.isLanguageAvailable(locale);
            if (status == TextToSpeech.LANG_MISSING_DATA
                    || status == TextToSpeech.LANG_NOT_SUPPORTED) {
                return false;
        private void waitForTtsReady() {
            if (mDismiss) {
                return;
            }
            mTts.setLanguage(locale);
            Voice voice = mTts.getVoice();
            if (voice == null || (voice.getFeatures() != null && voice.getFeatures()
                    .contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED))) {
                return false;
            if (!mLanguageReady) {
                final int status = mTts.setLanguage(Locale.getDefault());
                // True if language is available and TTS#loadVoice has called once
                // that trigger TTS service to start initialization.
                mLanguageReady = status != TextToSpeech.LANG_MISSING_DATA
                    && status != TextToSpeech.LANG_NOT_SUPPORTED;
            }
            if (mLanguageReady) {
                final Voice voice = mTts.getVoice();
                final boolean voiceDataInstalled = voice != null
                        && voice.getFeatures() != null
                        && !voice.getFeatures().contains(
                                TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED);
                if (voiceDataInstalled) {
                    mHandler.sendMessage(PooledLambda.obtainMessage(
                            TtsPrompt::play, this));
                    return;
                }
            return true;
            }

            if (mRetryCount == 0) {
                Slog.d(TAG, "Tts not ready to speak.");
                playNotificationTone();
                return;
            }
            // Retry if TTS service not ready yet.
            mRetryCount -= 1;
            mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
                    TtsPrompt::waitForTtsReady, this), RETRY_MILLIS);
        }
    }

+33 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -85,7 +86,9 @@ import org.mockito.invocation.InvocationOnMock;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


@RunWith(AndroidJUnit4.class)
@@ -534,6 +537,36 @@ public class AccessibilityShortcutControllerTest {
        verify(mRingtone).play();
    }

    @Test
    public void testOnAccessibilityShortcut_showsWarningDialog_ttsLongTimeInit_retrySpoken()
            throws Exception {
        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
        configureValidShortcutService();
        configureTtsSpokenPromptEnabled();
        configureHandlerCallbackInvocation();
        AccessibilityShortcutController accessibilityShortcutController = getController();
        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
        Set<String> features = new HashSet<>();
        features.add(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED);
        doReturn(features, Collections.emptySet()).when(mVoice).getFeatures();
        doReturn(TextToSpeech.LANG_NOT_SUPPORTED, TextToSpeech.LANG_AVAILABLE)
                .when(mTextToSpeech).setLanguage(any());
        accessibilityShortcutController.performAccessibilityShortcut();

        verify(mAlertDialog).show();
        ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
                TextToSpeech.OnInitListener.class);
        verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
        onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
        verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
        ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
                DialogInterface.OnDismissListener.class);
        verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
        onDismissCap.getValue().onDismiss(mAlertDialog);
        verify(mTextToSpeech).shutdown();
        verify(mRingtone, times(0)).play();
    }

    private void configureNoShortcutService() throws Exception {
        when(mAccessibilityManagerService
                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))