blob: f82e91b253db6cf91402d608280f407e80c62bfd [file] [log] [blame]
package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.speech.IRecognitionService;
import android.speech.RecognitionListener;
import android.speech.RecognitionSupport;
import android.speech.RecognitionSupportCallback;
import android.speech.SpeechRecognizer;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.util.Queue;
import java.util.concurrent.Executor;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
/** Robolectric shadow for SpeechRecognizer. */
@Implements(SpeechRecognizer.class)
public class ShadowSpeechRecognizer {
@RealObject SpeechRecognizer realSpeechRecognizer;
protected static SpeechRecognizer latestSpeechRecognizer;
private Intent recognizerIntent;
private RecognitionListener recognitionListener;
private static boolean isOnDeviceRecognitionAvailable = true;
private RecognitionSupportCallback recognitionSupportCallback;
private Executor recognitionSupportExecutor;
@Nullable private Intent latestModelDownloadIntent;
/**
* Returns the latest SpeechRecognizer. This method can only be called after {@link
* SpeechRecognizer#createSpeechRecognizer()} is called.
*/
public static SpeechRecognizer getLatestSpeechRecognizer() {
return latestSpeechRecognizer;
}
/** Returns the argument passed to the last call to {@link SpeechRecognizer#startListening}. */
public Intent getLastRecognizerIntent() {
return recognizerIntent;
}
@Resetter
public static void reset() {
latestSpeechRecognizer = null;
isOnDeviceRecognitionAvailable = true;
}
@Implementation
protected static SpeechRecognizer createSpeechRecognizer(
final Context context, final ComponentName serviceComponent) {
SpeechRecognizer result =
reflector(SpeechRecognizerReflector.class)
.createSpeechRecognizer(context, serviceComponent);
latestSpeechRecognizer = result;
return result;
}
@Implementation
protected void startListening(Intent recognizerIntent) {
this.recognizerIntent = recognizerIntent;
// the real implementation connects to a service
// simulate the resulting behavior once the service is connected
Handler mainHandler = new Handler(Looper.getMainLooper());
// perform the onServiceConnected logic
mainHandler.post(
() -> {
SpeechRecognizerReflector recognizerReflector =
reflector(SpeechRecognizerReflector.class, realSpeechRecognizer);
recognizerReflector.setService(
ReflectionHelpers.createNullProxy(IRecognitionService.class));
Queue<Message> pendingTasks = recognizerReflector.getPendingTasks();
while (!pendingTasks.isEmpty()) {
recognizerReflector.getHandler().sendMessage(pendingTasks.poll());
}
});
}
/**
* Handles changing the listener and allows access to the internal listener to trigger events and
* sets the latest SpeechRecognizer.
*/
@Implementation
protected void handleChangeListener(RecognitionListener listener) {
recognitionListener = listener;
}
public void triggerOnEndOfSpeech() {
recognitionListener.onEndOfSpeech();
}
public void triggerOnError(int error) {
recognitionListener.onError(error);
}
public void triggerOnReadyForSpeech(Bundle bundle) {
recognitionListener.onReadyForSpeech(bundle);
}
public void triggerOnPartialResults(Bundle bundle) {
recognitionListener.onPartialResults(bundle);
}
public void triggerOnResults(Bundle bundle) {
recognitionListener.onResults(bundle);
}
public void triggerOnRmsChanged(float rmsdB) {
recognitionListener.onRmsChanged(rmsdB);
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected static SpeechRecognizer createOnDeviceSpeechRecognizer(final Context context) {
SpeechRecognizer result =
reflector(SpeechRecognizerReflector.class).createOnDeviceSpeechRecognizer(context);
latestSpeechRecognizer = result;
return result;
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected static boolean isOnDeviceRecognitionAvailable(final Context context) {
return isOnDeviceRecognitionAvailable;
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected void checkRecognitionSupport(
Intent recognizerIntent, Executor executor, RecognitionSupportCallback supportListener) {
recognitionSupportExecutor = executor;
recognitionSupportCallback = supportListener;
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected void triggerModelDownload(Intent recognizerIntent) {
latestModelDownloadIntent = recognizerIntent;
}
public static void setIsOnDeviceRecognitionAvailable(boolean available) {
isOnDeviceRecognitionAvailable = available;
}
@RequiresApi(VERSION_CODES.TIRAMISU)
public void triggerSupportResult(RecognitionSupport recognitionSupport) {
recognitionSupportExecutor.execute(
() -> recognitionSupportCallback.onSupportResult(recognitionSupport));
}
@RequiresApi(VERSION_CODES.TIRAMISU)
public void triggerSupportError(int error) {
recognitionSupportExecutor.execute(() -> recognitionSupportCallback.onError(error));
}
@RequiresApi(VERSION_CODES.TIRAMISU)
@Nullable
public Intent getLatestModelDownloadIntent() {
return latestModelDownloadIntent;
}
/** Reflector interface for {@link SpeechRecognizer}'s internals. */
@ForType(SpeechRecognizer.class)
interface SpeechRecognizerReflector {
@Static
@Direct
SpeechRecognizer createSpeechRecognizer(Context context, ComponentName serviceComponent);
@Accessor("mService")
void setService(IRecognitionService service);
@Accessor("mPendingTasks")
Queue<Message> getPendingTasks();
@Accessor("mHandler")
Handler getHandler();
@Static
@Direct
SpeechRecognizer createOnDeviceSpeechRecognizer(Context context);
}
}