Retry TTS speaking for a11y shortcut warning dialog
TTS engine might fetch data over the network or prepare data when
binding tts service at first time after boot that may take a few
seconds. Because the dialog is shown only once and on-demand for
enableing accessibility shortcut, it's unable to speak successfully
unless others have bound tts ahead of time.
Bug: 139887992
Test: 1. adb shell settings delete secure accessibility_shortcut_dialog_shown
2. manual reboot, long press both vol keys and tts speaks
Test: Add 2nd user and long press both vol keys and tts speaks
Test: atest AccessibilityShortcutControllerTest
Change-Id: I6290f0a64d1a51aa46f13e7d88a15a096ddb0fdc
This commit is contained in:
@@ -412,8 +412,13 @@ public class AccessibilityShortcutController {
|
|||||||
* Class to wrap TextToSpeech for shortcut dialog spoken feedback.
|
* Class to wrap TextToSpeech for shortcut dialog spoken feedback.
|
||||||
*/
|
*/
|
||||||
private class TtsPrompt implements TextToSpeech.OnInitListener {
|
private class TtsPrompt implements TextToSpeech.OnInitListener {
|
||||||
|
private static final int RETRY_MILLIS = 1000;
|
||||||
|
|
||||||
private final CharSequence mText;
|
private final CharSequence mText;
|
||||||
|
|
||||||
|
private int mRetryCount = 3;
|
||||||
private boolean mDismiss;
|
private boolean mDismiss;
|
||||||
|
private boolean mLanguageReady = false;
|
||||||
private TextToSpeech mTts;
|
private TextToSpeech mTts;
|
||||||
|
|
||||||
TtsPrompt(String serviceName) {
|
TtsPrompt(String serviceName) {
|
||||||
@@ -437,17 +442,15 @@ public class AccessibilityShortcutController {
|
|||||||
playNotificationTone();
|
playNotificationTone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mHandler.sendMessage(PooledLambda.obtainMessage(TtsPrompt::play, this));
|
mHandler.sendMessage(PooledLambda.obtainMessage(
|
||||||
|
TtsPrompt::waitForTtsReady, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void play() {
|
private void play() {
|
||||||
if (mDismiss) {
|
if (mDismiss) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int status = TextToSpeech.ERROR;
|
final int status = mTts.speak(mText, TextToSpeech.QUEUE_FLUSH, null, null);
|
||||||
if (setLanguage(Locale.getDefault())) {
|
|
||||||
status = mTts.speak(mText, TextToSpeech.QUEUE_FLUSH, null, null);
|
|
||||||
}
|
|
||||||
if (status != TextToSpeech.SUCCESS) {
|
if (status != TextToSpeech.SUCCESS) {
|
||||||
Slog.d(TAG, "Tts play fail");
|
Slog.d(TAG, "Tts play fail");
|
||||||
playNotificationTone();
|
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) {
|
private void waitForTtsReady() {
|
||||||
int status = mTts.isLanguageAvailable(locale);
|
if (mDismiss) {
|
||||||
if (status == TextToSpeech.LANG_MISSING_DATA
|
return;
|
||||||
|| status == TextToSpeech.LANG_NOT_SUPPORTED) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
mTts.setLanguage(locale);
|
if (!mLanguageReady) {
|
||||||
Voice voice = mTts.getVoice();
|
final int status = mTts.setLanguage(Locale.getDefault());
|
||||||
if (voice == null || (voice.getFeatures() != null && voice.getFeatures()
|
// True if language is available and TTS#loadVoice has called once
|
||||||
.contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED))) {
|
// that trigger TTS service to start initialization.
|
||||||
return false;
|
mLanguageReady = status != TextToSpeech.LANG_MISSING_DATA
|
||||||
|
&& status != TextToSpeech.LANG_NOT_SUPPORTED;
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
// Retry if TTS service not ready yet.
|
||||||
|
mRetryCount -= 1;
|
||||||
|
mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
|
||||||
|
TtsPrompt::waitForTtsReady, this), RETRY_MILLIS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import static org.mockito.Matchers.anyObject;
|
|||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -85,7 +86,9 @@ import org.mockito.invocation.InvocationOnMock;
|
|||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@@ -534,6 +537,36 @@ public class AccessibilityShortcutControllerTest {
|
|||||||
verify(mRingtone).play();
|
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 {
|
private void configureNoShortcutService() throws Exception {
|
||||||
when(mAccessibilityManagerService
|
when(mAccessibilityManagerService
|
||||||
.getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
|
.getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
|
||||||
|
|||||||
Reference in New Issue
Block a user