blob: dbe1089746840def505703afc7f2638a32024a12 [file] [log] [blame]
/*
* Copyright (C) 2021 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.service.voice;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
/** Base implementation of {@link HotwordDetector}. */
abstract class AbstractHotwordDetector implements HotwordDetector {
private static final String TAG = AbstractHotwordDetector.class.getSimpleName();
private static final boolean DEBUG = false;
protected final Object mLock = new Object();
private final IVoiceInteractionManagerService mManagerService;
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
AbstractHotwordDetector(
IVoiceInteractionManagerService managerService,
HotwordDetector.Callback callback) {
mManagerService = managerService;
// TODO: this needs to be supplied from above
mHandler = new Handler(Looper.getMainLooper());
mCallback = callback;
}
/**
* Detect hotword from an externally supplied stream of data.
*
* @return true if the request to start recognition succeeded
*/
@Override
public boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
@Nullable PersistableBundle options) {
if (DEBUG) {
Slog.i(TAG, "#recognizeHotword");
}
// TODO: consider closing existing session.
try {
mManagerService.startListeningFromExternalSource(
audioStream,
audioFormat,
options,
new BinderCallback(mHandler, mCallback));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return true;
}
/**
* Set configuration and pass read-only data to hotword detection service.
*
* @param options Application configuration data to provide to the
* {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
* other contents that can be used to communicate with other processes.
* @param sharedMemory The unrestricted data blob to provide to the
* {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
*
* @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
* {@link HotwordDetectionService} when it was created. In addition, if this
* AlwaysOnHotwordDetector is in an invalid or error state.
*/
@Override
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
if (DEBUG) {
Slog.d(TAG, "updateState()");
}
synchronized (mLock) {
updateStateLocked(options, sharedMemory, null /* callback */);
}
}
protected void updateStateLocked(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
Slog.d(TAG, "updateStateLocked()");
}
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try {
mManagerService.updateState(identity, options, sharedMemory, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private static class BinderCallback
extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub {
private final Handler mHandler;
// TODO: these need to be weak references.
private final HotwordDetector.Callback mCallback;
BinderCallback(Handler handler, HotwordDetector.Callback callback) {
this.mHandler = handler;
this.mCallback = callback;
}
/** TODO: onDetected */
@Override
public void onDetected(
@Nullable HotwordDetectedResult hotwordDetectedResult,
@Nullable AudioFormat audioFormat,
@Nullable ParcelFileDescriptor audioStreamIgnored) {
mHandler.sendMessage(obtainMessage(
HotwordDetector.Callback::onDetected,
mCallback,
new AlwaysOnHotwordDetector.EventPayload(audioFormat, hotwordDetectedResult)));
}
}
}