Merge "Retry TTS speaking for a11y shortcut warning dialog" into rvc-dev am: 67c02e6cb0 am: 28d79c4c4c
Change-Id: I1e9fbb0ec0351e94f93c2384878856e2777a0c90
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