blob: 72973fab12bcdf8e268dc87beda462103c624318 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.voiceinteraction.cts.services;
import static android.Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE;
import static android.Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE;
import static android.Manifest.permission.MANAGE_HOTWORD_DETECTION;
import static android.voiceinteraction.cts.testcore.Helper.WAIT_TIMEOUT_IN_MS;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.DetectorFailure;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.SandboxedDetectionServiceBase;
import android.service.voice.VisualQueryDetector;
import android.service.voice.VoiceInteractionService;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* The {@link VoiceInteractionService} included a basic HotwordDetectionService for testing.
*/
public class CtsBasicVoiceInteractionService extends BaseVoiceInteractionService {
private static final String TAG = "CtsBasicVoiceInteractionService";
private final Handler mHandler;
// The CountDownLatch waits for a service Availability change result
private CountDownLatch mAvailabilityChangeLatch;
// The CountDownLatch waits for a service detect or reject result
private CountDownLatch mOnDetectRejectLatch;
// The CountDownLatch waits for a service onError called
private CountDownLatch mOnErrorLatch;
// The CountDownLatch waits for a service onFailure called
private CountDownLatch mOnFailureLatch;
// The CountDownLatch waits for vqds
private CountDownLatch mOnQueryFinishRejectLatch;
// The CountDownLatch waits for a service onHotwordDetectionServiceRestarted called
private CountDownLatch mOnHotwordDetectionServiceRestartedLatch;
private AlwaysOnHotwordDetector.EventPayload mDetectedResult;
private HotwordRejectedResult mRejectedResult;
private ArrayList<String> mStreamedQueries = new ArrayList<>();
private String mCurrentQuery = "";
private DetectorFailure mDetectorFailure = null;
public CtsBasicVoiceInteractionService() {
HandlerThread handlerThread = new HandlerThread("CtsBasicVoiceInteractionService");
handlerThread.start();
mHandler = Handler.createAsync(handlerThread.getLooper());
}
public void createAlwaysOnHotwordDetectorNoHotwordDetectionService(boolean useExecutor,
boolean runOnMainThread) {
Log.i(TAG, "createAlwaysOnHotwordDetectorNoHotwordDetectionService");
mServiceTriggerLatch = new CountDownLatch(1);
AlwaysOnHotwordDetector.Callback callback = new AlwaysOnHotwordDetector.Callback() {
@Override
public void onAvailabilityChanged(int status) {
Log.i(TAG, "onAvailabilityChanged(" + status + ")");
mAvailabilityStatus = status;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mAvailabilityChangeLatch != null) {
mAvailabilityChangeLatch.countDown();
}
}
@Override
public void onDetected(AlwaysOnHotwordDetector.EventPayload eventPayload) {
// no-op
}
@Override
public void onRejected(@NonNull HotwordRejectedResult result) {
// no-op
}
@Override
public void onError() {
// no-op
}
@Override
public void onRecognitionPaused() {
// no-op
}
@Override
public void onRecognitionResumed() {
// no-op
}
@Override
public void onHotwordDetectionServiceInitialized(int status) {
// no-op
}
@Override
public void onHotwordDetectionServiceRestarted() {
// no-op
}
};
final Handler handler = runOnMainThread ? new Handler(Looper.getMainLooper()) : mHandler;
handler.post(() -> runWithShellPermissionIdentity(() -> {
mAlwaysOnHotwordDetector = callCreateAlwaysOnHotwordDetectorNoHotwordDetectionService(
callback, useExecutor);
if (mServiceTriggerLatch != null) {
mServiceTriggerLatch.countDown();
}
}, MANAGE_HOTWORD_DETECTION));
}
/**
* Create an AlwaysOnHotwordDetector, but it will not implement the onFailure method of
* AlwaysOnHotwordDetector.Callback. It will implement the onFailure method by using
* createAlwaysOnHotwordDetectorWithOnFailureCallback method.
*/
public void createAlwaysOnHotwordDetector() {
createAlwaysOnHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ false);
}
/**
* Create an AlwaysOnHotwordDetector, but it will not implement the onFailure method of
* AlwaysOnHotwordDetector.Callback. It will implement the onFailure method by using
* createAlwaysOnHotwordDetectorWithOnFailureCallback method.
*/
public void createAlwaysOnHotwordDetector(boolean useExecutor, boolean runOnMainThread) {
Log.i(TAG, "createAlwaysOnHotwordDetector!!!!");
mServiceTriggerLatch = new CountDownLatch(1);
final AlwaysOnHotwordDetector.Callback callback = new AlwaysOnHotwordDetector.Callback() {
@Override
public void onAvailabilityChanged(int status) {
Log.i(TAG, "onAvailabilityChanged(" + status + ")");
mAvailabilityStatus = status;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mAvailabilityChangeLatch != null) {
mAvailabilityChangeLatch.countDown();
}
}
@Override
public void onDetected(AlwaysOnHotwordDetector.EventPayload eventPayload) {
Log.i(TAG, "onDetected");
mDetectedResult = eventPayload;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnDetectRejectLatch != null) {
mOnDetectRejectLatch.countDown();
}
}
@Override
public void onRejected(@NonNull HotwordRejectedResult result) {
Log.i(TAG, "onRejected");
mRejectedResult = result;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnDetectRejectLatch != null) {
mOnDetectRejectLatch.countDown();
}
}
@Override
public void onError() {
Log.i(TAG, "onError");
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnErrorLatch != null) {
mOnErrorLatch.countDown();
}
}
@Override
public void onRecognitionPaused() {
Log.i(TAG, "onRecognitionPaused");
}
@Override
public void onRecognitionResumed() {
Log.i(TAG, "onRecognitionResumed");
}
@Override
public void onHotwordDetectionServiceInitialized(int status) {
Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
return;
}
mInitializedStatus = status;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mServiceTriggerLatch != null) {
mServiceTriggerLatch.countDown();
}
}
@Override
public void onHotwordDetectionServiceRestarted() {
Log.i(TAG, "onHotwordDetectionServiceRestarted");
if (mOnHotwordDetectionServiceRestartedLatch != null) {
mOnHotwordDetectionServiceRestartedLatch.countDown();
}
}
};
final Handler handler = runOnMainThread ? new Handler(Looper.getMainLooper()) : mHandler;
handler.post(() -> runWithShellPermissionIdentity(() -> {
mAlwaysOnHotwordDetector = callCreateAlwaysOnHotwordDetector(callback, useExecutor);
}, MANAGE_HOTWORD_DETECTION));
}
/**
* Create an AlwaysOnHotwordDetector but doesn't hold MANAGE_HOTWORD_DETECTION
*/
public void createAlwaysOnHotwordDetectorWithoutManageHotwordDetectionPermission() {
mServiceTriggerLatch = new CountDownLatch(1);
mHandler.post(() -> runWithShellPermissionIdentity(
() -> callCreateAlwaysOnHotwordDetector(mNoOpHotwordDetectorCallback)));
}
/**
* Create an AlwaysOnHotwordDetector but doesn't hold MANAGE_HOTWORD_DETECTION but hold
* BIND_HOTWORD_DETECTION_SERVICE.
*/
public void createAlwaysOnHotwordDetectorHoldBindHotwordDetectionPermission() {
mServiceTriggerLatch = new CountDownLatch(1);
mHandler.post(() -> runWithShellPermissionIdentity(
() -> callCreateAlwaysOnHotwordDetector(mNoOpHotwordDetectorCallback),
BIND_HOTWORD_DETECTION_SERVICE));
}
/**
* Create an AlwaysOnHotwordDetector with onFailure callback. The onFailure provides the error
* code, error message and suggested action the assistant application should take.
*/
public void createAlwaysOnHotwordDetectorWithOnFailureCallback(boolean useExecutor,
boolean runOnMainThread) {
Log.i(TAG, "createAlwaysOnHotwordDetectorWithOnFailureCallback");
mServiceTriggerLatch = new CountDownLatch(1);
final AlwaysOnHotwordDetector.Callback callback = new AlwaysOnHotwordDetector.Callback() {
@Override
public void onAvailabilityChanged(int status) {
Log.i(TAG, "onAvailabilityChanged(" + status + ")");
mAvailabilityStatus = status;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mAvailabilityChangeLatch != null) {
mAvailabilityChangeLatch.countDown();
}
}
@Override
public void onDetected(AlwaysOnHotwordDetector.EventPayload eventPayload) {
Log.i(TAG, "onDetected");
mDetectedResult = eventPayload;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnDetectRejectLatch != null) {
mOnDetectRejectLatch.countDown();
}
}
@Override
public void onRejected(@NonNull HotwordRejectedResult result) {
Log.i(TAG, "onRejected");
mRejectedResult = result;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnDetectRejectLatch != null) {
mOnDetectRejectLatch.countDown();
}
}
@Override
public void onError() {
Log.i(TAG, "onError");
}
@Override
public void onFailure(DetectorFailure detectorFailure) {
Log.i(TAG, "onFailure detectorFailure=" + detectorFailure);
mDetectorFailure = detectorFailure;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnFailureLatch != null) {
mOnFailureLatch.countDown();
}
}
@Override
public void onRecognitionPaused() {
Log.i(TAG, "onRecognitionPaused");
}
@Override
public void onRecognitionResumed() {
Log.i(TAG, "onRecognitionResumed");
}
@Override
public void onHotwordDetectionServiceInitialized(int status) {
Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
return;
}
mInitializedStatus = status;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mServiceTriggerLatch != null) {
mServiceTriggerLatch.countDown();
}
}
@Override
public void onHotwordDetectionServiceRestarted() {
Log.i(TAG, "onHotwordDetectionServiceRestarted");
}
};
final Handler handler = runOnMainThread ? new Handler(Looper.getMainLooper()) : mHandler;
handler.post(() -> runWithShellPermissionIdentity(() -> {
mAlwaysOnHotwordDetector = callCreateAlwaysOnHotwordDetector(callback, useExecutor);
}, MANAGE_HOTWORD_DETECTION));
}
public void createSoftwareHotwordDetector() {
createSoftwareHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ false);
}
public void createSoftwareHotwordDetector(boolean useExecutor, boolean runOnMainThread) {
mServiceTriggerLatch = new CountDownLatch(1);
final HotwordDetector.Callback callback = new HotwordDetector.Callback() {
@Override
public void onDetected(AlwaysOnHotwordDetector.EventPayload eventPayload) {
Log.i(TAG, "onDetected");
mDetectedResult = eventPayload;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnDetectRejectLatch != null) {
mOnDetectRejectLatch.countDown();
}
}
@Override
public void onError() {
Log.i(TAG, "onError");
}
@Override
public void onRecognitionPaused() {
Log.i(TAG, "onRecognitionPaused");
}
@Override
public void onRecognitionResumed() {
Log.i(TAG, "onRecognitionResumed");
}
@Override
public void onRejected(HotwordRejectedResult result) {
Log.i(TAG, "onRejected");
mRejectedResult = result;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mOnDetectRejectLatch != null) {
mOnDetectRejectLatch.countDown();
}
}
@Override
public void onHotwordDetectionServiceInitialized(int status) {
Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
return;
}
mInitializedStatus = status;
setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
if (mServiceTriggerLatch != null) {
mServiceTriggerLatch.countDown();
}
}
@Override
public void onHotwordDetectionServiceRestarted() {
Log.i(TAG, "onHotwordDetectionServiceRestarted");
}
};
final Handler handler = runOnMainThread ? new Handler(Looper.getMainLooper()) : mHandler;
handler.post(() -> runWithShellPermissionIdentity(() -> {
mSoftwareHotwordDetector = callCreateSoftwareHotwordDetector(callback, useExecutor);
}, MANAGE_HOTWORD_DETECTION));
}
/**
* Create a VisualQueryDetector but doesn't hold MANAGE_HOTWORD_DETECTION
*/
public void createVisualQueryDetectorWithoutManageHotwordDetectionPermission() {
mServiceTriggerLatch = new CountDownLatch(1);
mHandler.post(() -> runWithShellPermissionIdentity(
() -> callCreateVisualQueryDetector(mNoOpVisualQueryDetectorCallback)));
}
/**
* Create a VisualQueryDetector but doesn't hold MANAGE_HOTWORD_DETECTION but hold
* BIND_VISUAL_QUERY_DETECTION_SERVICE.
*/
public void createVisualQueryDetectorHoldBindVisualQueryDetectionPermission() {
mServiceTriggerLatch = new CountDownLatch(1);
mHandler.post(() -> runWithShellPermissionIdentity(
() -> callCreateVisualQueryDetector(mNoOpVisualQueryDetectorCallback),
BIND_VISUAL_QUERY_DETECTION_SERVICE));
}
public void createVisualQueryDetector() {
mServiceTriggerLatch = new CountDownLatch(1);
mHandler.post(() -> runWithShellPermissionIdentity(() -> {
VisualQueryDetector.Callback callback = new VisualQueryDetector.Callback() {
@Override
public void onQueryDetected(@NonNull String transcribedText) {
Log.i(TAG, "onQueryDetected");
mCurrentQuery += transcribedText;
}
@Override
public void onQueryRejected() {
Log.i(TAG, "onQueryRejected");
// mStreamedQueries are used to store previously streamed queries for testing
// reason, regardless of the queries being rejected or finished.
mStreamedQueries.add(mCurrentQuery);
mCurrentQuery = "";
if (mOnQueryFinishRejectLatch != null) {
mOnQueryFinishRejectLatch.countDown();
}
}
@Override
public void onQueryFinished() {
Log.i(TAG, "onQueryFinished");
mStreamedQueries.add(mCurrentQuery);
mCurrentQuery = "";
if (mOnQueryFinishRejectLatch != null) {
mOnQueryFinishRejectLatch.countDown();
}
}
@Override
public void onVisualQueryDetectionServiceInitialized(int status) {
Log.i(TAG, "onVisualQueryDetectionServiceInitialized status = " + status);
if (status != SandboxedDetectionServiceBase.INITIALIZATION_STATUS_SUCCESS) {
return;
}
mInitializedStatus = status;
if (mServiceTriggerLatch != null) {
mServiceTriggerLatch.countDown();
}
}
@Override
public void onVisualQueryDetectionServiceRestarted() {
Log.i(TAG, "onVisualQueryDetectionServiceRestarted");
}
@Override
public void onFailure(@NonNull DetectorFailure detectorFailure) {
Log.i(TAG, "onFailure");
}
};
mVisualQueryDetector = callCreateVisualQueryDetector(callback);
}, MANAGE_HOTWORD_DETECTION));
}
/**
* Create a CountDownLatch that is used to wait for onHotwordDetectionServiceRestarted() called
*/
public void initOnHotwordDetectionServiceRestartedLatch() {
mOnHotwordDetectionServiceRestartedLatch = new CountDownLatch(1);
}
/**
* Create a CountDownLatch that is used to wait for availability change
*/
public void initAvailabilityChangeLatch() {
mAvailabilityChangeLatch = new CountDownLatch(1);
}
/**
* Create a CountDownLatch that is used to wait for onDetected() or onRejected() result
*/
public void initDetectRejectLatch() {
mOnDetectRejectLatch = new CountDownLatch(1);
}
/**
* Create a CountDownLatch that wait for onQueryFinished() or onQueryRejected() result
*/
public void initQueryFinishRejectLatch(int numQueries) {
mOnQueryFinishRejectLatch = new CountDownLatch(numQueries);
}
/**
* Create a CountDownLatch that is used to wait for onError()
*/
public void initOnErrorLatch() {
mOnErrorLatch = new CountDownLatch(1);
}
/**
* Create a CountDownLatch that is used to wait for onFailure()
*/
public void initOnFailureLatch() {
mOnFailureLatch = new CountDownLatch(1);
}
/**
* Returns the onDetected() result.
*/
public AlwaysOnHotwordDetector.EventPayload getHotwordServiceOnDetectedResult() {
return mDetectedResult;
}
/**
* Returns the OnRejected() result.
*/
public HotwordRejectedResult getHotwordServiceOnRejectedResult() {
return mRejectedResult;
}
/**
* Returns the OnQueryDetected() result.
*/
public ArrayList<String> getStreamedQueriesResult() {
return mStreamedQueries;
}
public void waitOnHotwordDetectionServiceRestartedCalled() throws InterruptedException {
Log.d(TAG, "waitOnHotwordDetectionServiceRestartedCalled(), latch="
+ mOnHotwordDetectionServiceRestartedLatch);
if (mOnHotwordDetectionServiceRestartedLatch == null
|| !mOnHotwordDetectionServiceRestartedLatch.await(WAIT_TIMEOUT_IN_MS,
TimeUnit.MILLISECONDS)) {
mOnHotwordDetectionServiceRestartedLatch = null;
throw new AssertionError(
"HotwordDetectionService onHotwordDetectionServiceRestarted not called.");
}
mOnHotwordDetectionServiceRestartedLatch = null;
}
/**
* Returns the OnFailure() result.
*/
public DetectorFailure getDetectorFailure() {
return mDetectorFailure;
}
/**
* Wait for onAvailabilityChanged() callback called.
*/
public void waitAvailabilityChangedCalled() throws InterruptedException {
Log.d(TAG, "waitAvailabilityChangedCalled(), latch=" + mAvailabilityChangeLatch);
if (mAvailabilityChangeLatch == null
|| !mAvailabilityChangeLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)) {
mAvailabilityChangeLatch = null;
throw new AssertionError("HotwordDetectionService get ability fail.");
}
mAvailabilityChangeLatch = null;
}
/**
* Return the result for onAvailabilityChanged().
*/
public int getHotwordDetectionServiceAvailabilityResult() {
return mAvailabilityStatus;
}
/**
* Wait for onDetected() or OnRejected() callback called.
*/
public void waitOnDetectOrRejectCalled() throws InterruptedException {
Log.d(TAG, "waitOnDetectOrRejectCalled(), latch=" + mOnDetectRejectLatch);
if (mOnDetectRejectLatch == null
|| !mOnDetectRejectLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)) {
mOnDetectRejectLatch = null;
throw new AssertionError("onDetected or OnRejected() fail.");
}
mOnDetectRejectLatch = null;
}
/**
* Wait for onQueryFinished() or OnQueryRejected() callback called.
*/
public void waitOnQueryFinishedRejectCalled() throws InterruptedException {
Log.d(TAG, "waitOnQueryFinishedRejectCalled(), latch=" + mOnQueryFinishRejectLatch);
if (mOnQueryFinishRejectLatch == null
|| !mOnQueryFinishRejectLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)) {
mOnQueryFinishRejectLatch = null;
throw new AssertionError("onDetected or OnRejected() fail.");
}
mOnQueryFinishRejectLatch = null;
}
/**
* Wait for onError() callback called.
*/
public void waitOnErrorCalled() throws InterruptedException {
Log.d(TAG, "waitOnErrorCalled(), latch=" + mOnErrorLatch);
if (mOnErrorLatch == null
|| !mOnErrorLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)) {
mOnErrorLatch = null;
throw new AssertionError("OnError() fail.");
}
mOnErrorLatch = null;
}
/**
* Wait for onFailure() callback called.
*/
public void waitOnFailureCalled() throws InterruptedException {
Log.d(TAG, "waitOnFailureCalled(), latch=" + mOnFailureLatch);
if (mOnFailureLatch == null
|| !mOnFailureLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)) {
mOnFailureLatch = null;
throw new AssertionError("OnFailure() fail.");
}
mOnFailureLatch = null;
}
}