1 /**
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.voicecontrol;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.StringRes;
21 
22 import java.util.List;
23 import java.util.function.Consumer;
24 
25 /**
26  * Minimal text-to-speech module interface.
27  */
28 public interface TextToSpeech {
29     /**
30      * Listener used to receive a notification once utterance is finished.
31      */
32     interface Listener {
33         /**
34          * Called when TTS is ready.
35          */
onReady(TextToSpeech tts)36         default void onReady(TextToSpeech tts) {};
37 
38         /**
39          * Called when the last utterance requested with {@link TextToSpeech#speak(int, Object...)}
40          * has finished.
41          * @param successful whether utterance was successful.
42          */
onUtteranceDone(boolean successful)43         default void onUtteranceDone(boolean successful) {};
44 
45         /**
46          * Called when the last utterance requires the user to answer a question. Implementors must
47          * call {@link TextToSpeech#provideAnswer(List)} once that answer has been
48          * captured.
49          */
onWaitingForAnswer()50         default void onWaitingForAnswer() {}
51     }
52 
53     /**
54      * A callback to be provided when asking a question to the user
55      */
56     interface QuestionCallback {
57         /**
58          * Method invoked when the answer to a question is received
59          *
60          * @param results Recognized answers in confidence order, or empty list if no answer was
61          *               recognized.
62          */
onResult(@onNull List<String> results)63         void onResult(@NonNull List<String> results);
64     }
65 
66     /**
67      * Known answer types
68      */
69     enum AnswerType {
70         AFFIRMATIVE,
71         NEGATIVE,
72     }
73 
74     /**
75      * Convenient factory method that creates a 'yes/no' question callback.
76      *
77      * @param consumer A lambda that will receive the yes or no answer.
78      * @param retries Number of times we will try to get a yes/no answer from the user. After this
79      *                many tries, the application will use the default answer.
80      */
createBooleanQuestionCallback(Consumer<Boolean> consumer, int retries, boolean defaultAnswer)81     default QuestionCallback createBooleanQuestionCallback(Consumer<Boolean> consumer,
82             int retries, boolean defaultAnswer) {
83         return new QuestionCallback() {
84             private int mRetries = retries;
85 
86             @Override
87             public void onResult(List<String> results) {
88                 AnswerType type = getAnswerType(results);
89                 if (type != AnswerType.AFFIRMATIVE && type != AnswerType.NEGATIVE) {
90                     if (mRetries > 0) {
91                         mRetries--;
92                         ask(this, R.string.speech_reply_yes_no_question_not_understood);
93                     } else {
94                         // If no answer is understood after a few retries, let's assume a
95                         // negative answer
96                         consumer.accept(defaultAnswer);
97                     }
98                 } else {
99                     consumer.accept(type == AnswerType.AFFIRMATIVE);
100                 }
101             }
102         };
103     }
104 
105     /**
106      * Releases internal resources
107      */
destroy()108     void destroy();
109 
110     /**
111      * Requests the given text to be added to the queue of pending utterances to read out.
112      * If this method is called before {@link Listener#onReady(TextToSpeech)}, the speech will be
113      * saved and its utterance will be delayed until TTS is ready.
114      */
speak(String fmt, Object... args)115     void speak(String fmt, Object... args);
116 
117     /**
118      * Similar to {@link #speak(String, Object[])} but it takes a string resource rather than an
119      * actual string.
120      */
speak(@tringRes int stringId, Object... args)121     void speak(@StringRes int stringId, Object... args);
122 
123     /**
124      * Requests the given text to be added to the queue of pending utterances to read out (similar
125      * semantics as {@link #speak(int, Object...)}.
126      * Once this is done, instead of calling {@link Listener#onUtteranceDone(boolean)}, it will
127      * call {@link Listener#onWaitingForAnswer()}, to wait for the user to provide an answer. Once
128      * the answer is collected (using {@link #provideAnswer(List)}), the answer will be
129      * given through the {@link QuestionCallback}.
130      */
ask(QuestionCallback callback, String fmt, Object... args)131     void ask(QuestionCallback callback, String fmt, Object... args);
132 
133     /**
134      * Similar to {@link #ask(QuestionCallback, String, Object...)} but it takes a string
135      * resource rather than an actual string.
136      */
ask(QuestionCallback callback, @StringRes int resId, Object... args)137     void ask(QuestionCallback callback, @StringRes int resId, Object... args);
138 
139     /**
140      * @return true if the last utterance was a question that is waiting to be answered
141      */
isWaitingForAnswer()142     boolean isWaitingForAnswer();
143 
144     /**
145      * Provides an answer to last uttered questions. Calling this method could cause the
146      * {@link QuestionCallback#onResult(List)} method of the last
147      * {@link #ask(QuestionCallback, int, Object...)} invocation to be called.
148      */
provideAnswer(@onNull List<String> results)149     void provideAnswer(@NonNull List<String> results);
150 
151     /**
152      * @return the type of answer received, or null if the answer is not recognized
153      */
getAnswerType(List<String> strings)154     @Nullable AnswerType getAnswerType(List<String> strings);
155 
156     /**
157      * @return the list of voices supported by this TTS. Can only be called after
158      * {@link Listener#onReady(TextToSpeech)}
159      */
getVoices()160     List<String> getVoices();
161 
162     /**
163      * Sets the voice that should be used. The value provided should be one of items returned by
164      * {@link #getVoices()}
165      */
setSelectedVoice(String name)166     void setSelectedVoice(String name);
167 }
168