diff options
2 files changed, 74 insertions, 17 deletions
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index bb404654b741..5cdcab029877 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -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; + } + 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; } - 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 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; + } + } + + if (mRetryCount == 0) { + Slog.d(TAG, "Tts not ready to speak."); + playNotificationTone(); + return; } - return true; + // Retry if TTS service not ready yet. + mRetryCount -= 1; + mHandler.sendMessageDelayed(PooledLambda.obtainMessage( + TtsPrompt::waitForTtsReady, this), RETRY_MILLIS); } } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index bbf3b12d9b7d..9af0ed0ab826 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -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)) |