mSessions = new HashMap<>();
/** Binder of the recognition service */
private final RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
private static final int MSG_START_LISTENING = 1;
private static final int MSG_STOP_LISTENING = 2;
private static final int MSG_CANCEL = 3;
private static final int MSG_RESET = 4;
private static final int MSG_CHECK_RECOGNITION_SUPPORT = 5;
private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_START_LISTENING:
StartListeningArgs args = (StartListeningArgs) msg.obj;
dispatchStartListening(args.mIntent, args.mListener, args.mAttributionSource);
break;
case MSG_STOP_LISTENING:
dispatchStopListening((IRecognitionListener) msg.obj);
break;
case MSG_CANCEL:
dispatchCancel((IRecognitionListener) msg.obj);
break;
case MSG_RESET:
dispatchClearCallback((IRecognitionListener) msg.obj);
break;
case MSG_CHECK_RECOGNITION_SUPPORT:
CheckRecognitionSupportArgs checkArgs = (CheckRecognitionSupportArgs) msg.obj;
dispatchCheckRecognitionSupport(
checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
break;
case MSG_TRIGGER_MODEL_DOWNLOAD:
ModelDownloadArgs modelDownloadArgs = (ModelDownloadArgs) msg.obj;
dispatchTriggerModelDownload(
modelDownloadArgs.mIntent,
modelDownloadArgs.mAttributionSource,
modelDownloadArgs.mListener);
break;
}
}
};
private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
Callback currentCallback = null;
SessionState sessionState = mSessions.get(listener.asBinder());
try {
if (sessionState == null) {
if (mSessions.size() >= getMaxConcurrentSessionsCount()) {
listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
Log.i(TAG, "#startListening received "
+ "when the service's capacity is full - ignoring this call.");
return;
}
boolean preflightPermissionCheckPassed =
intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
|| checkPermissionForPreflightNotHardDenied(attributionSource);
if (preflightPermissionCheckPassed) {
currentCallback = new Callback(listener, attributionSource);
sessionState = new SessionState(currentCallback);
mSessions.put(listener.asBinder(), sessionState);
if (DBG) {
Log.d(TAG, "Added a new session to the map, pending permission checks");
}
RecognitionService.this.onStartListening(intent, currentCallback);
}
if (!preflightPermissionCheckPassed
|| !checkPermissionAndStartDataDelivery(sessionState)) {
listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
if (preflightPermissionCheckPassed) {
// If start listening was attempted, cancel the callback.
RecognitionService.this.onCancel(currentCallback);
mSessions.remove(listener.asBinder());
finishDataDelivery(sessionState);
sessionState.reset();
}
Log.i(TAG, "#startListening received from a caller "
+ "without permission " + Manifest.permission.RECORD_AUDIO + ".");
}
} else {
listener.onError(SpeechRecognizer.ERROR_CLIENT);
Log.i(TAG, "#startListening received "
+ "for a listener which is already in session - ignoring this call.");
}
} catch (RemoteException e) {
Log.d(TAG, "#onError call from #startListening failed.");
}
}
private void dispatchStopListening(IRecognitionListener listener) {
SessionState sessionState = mSessions.get(listener.asBinder());
if (sessionState == null) {
try {
listener.onError(SpeechRecognizer.ERROR_CLIENT);
} catch (RemoteException e) {
Log.d(TAG, "#onError call from #stopListening failed.");
}
Log.w(TAG, "#stopListening received for a listener "
+ "which has not started a session - ignoring this call.");
} else {
RecognitionService.this.onStopListening(sessionState.mCallback);
}
}
private void dispatchCancel(IRecognitionListener listener) {
SessionState sessionState = mSessions.get(listener.asBinder());
if (sessionState == null) {
Log.w(TAG, "#cancel received for a listener which has not started a session "
+ "- ignoring this call.");
} else {
RecognitionService.this.onCancel(sessionState.mCallback);
dispatchClearCallback(listener);
}
}
private void dispatchClearCallback(IRecognitionListener listener) {
SessionState sessionState = mSessions.remove(listener.asBinder());
if (sessionState != null) {
if (DBG) {
Log.d(TAG, "Removed session from the map for listener = "
+ listener.asBinder() + ".");
}
finishDataDelivery(sessionState);
sessionState.reset();
}
}
private void dispatchCheckRecognitionSupport(
Intent intent, IRecognitionSupportCallback callback,
AttributionSource attributionSource) {
RecognitionService.this.onCheckRecognitionSupport(
intent,
attributionSource,
new SupportCallback(callback));
}
private void dispatchTriggerModelDownload(
Intent intent,
AttributionSource attributionSource,
IModelDownloadListener listener) {
if (listener == null) {
RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
} else {
RecognitionService.this.onTriggerModelDownload(
intent,
attributionSource,
new ModelDownloadListener() {
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mIsTerminated = false;
@Override
public void onProgress(int completedPercent) {
synchronized (mLock) {
if (mIsTerminated) {
return;
}
try {
listener.onProgress(completedPercent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
@Override
public void onSuccess() {
synchronized (mLock) {
if (mIsTerminated) {
return;
}
mIsTerminated = true;
try {
listener.onSuccess();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
@Override
public void onScheduled() {
synchronized (mLock) {
if (mIsTerminated) {
return;
}
mIsTerminated = true;
try {
listener.onScheduled();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
@Override
public void onError(int error) {
synchronized (mLock) {
if (mIsTerminated) {
return;
}
mIsTerminated = true;
try {
listener.onError(error);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
});
}
}
private static class StartListeningArgs {
public final Intent mIntent;
public final IRecognitionListener mListener;
@NonNull public final AttributionSource mAttributionSource;
public StartListeningArgs(Intent intent, IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
this.mIntent = intent;
this.mListener = listener;
this.mAttributionSource = attributionSource;
}
}
private static class CheckRecognitionSupportArgs {
public final Intent mIntent;
public final IRecognitionSupportCallback callback;
public final AttributionSource mAttributionSource;
private CheckRecognitionSupportArgs(
Intent intent,
IRecognitionSupportCallback callback,
AttributionSource attributionSource) {
this.mIntent = intent;
this.callback = callback;
this.mAttributionSource = attributionSource;
}
}
private static class ModelDownloadArgs {
final Intent mIntent;
final AttributionSource mAttributionSource;
@Nullable final IModelDownloadListener mListener;
private ModelDownloadArgs(
Intent intent,
AttributionSource attributionSource,
@Nullable IModelDownloadListener listener) {
this.mIntent = intent;
this.mAttributionSource = attributionSource;
this.mListener = listener;
}
}
/**
* Notifies the service that it should start listening for speech.
*
* If you are recognizing speech from the microphone, in this callback you
* should create an attribution context for the caller such that when you access
* the mic the caller would be properly blamed (and their permission checked in
* the process) for accessing the microphone and that you served as a proxy for
* this sensitive data (and your permissions would be checked in the process).
* You should also open the mic in this callback via the attribution context
* and close the mic before returning the recognized result. If you don't do
* that then the caller would be blamed and you as being a proxy as well as you
* would get one more blame on yourself when you open the microphone.
*
*
* Context attributionContext = context.createContext(new ContextParams.Builder()
* .setNextAttributionSource(callback.getCallingAttributionSource())
* .build());
*
* AudioRecord recorder = AudioRecord.Builder()
* .setContext(attributionContext);
* . . .
* .build();
*
* recorder.startRecording()
*
*
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
* may also contain optional extras, see {@link RecognizerIntent}. If these values are
* not set explicitly, default values should be used by the recognizer.
* @param listener that will receive the service's callbacks
*/
protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
/**
* Notifies the service that it should cancel the speech recognition.
*/
protected abstract void onCancel(Callback listener);
/**
* Notifies the service that it should stop listening for speech. Speech captured so far should
* be recognized as if the user had stopped speaking at this point. This method is only called
* if the application calls it explicitly.
*/
protected abstract void onStopListening(Callback listener);
/**
* Queries the service on whether it would support a {@link #onStartListening(Intent, Callback)}
* for the same {@code recognizerIntent}.
*
* The service will notify the caller about the level of support or error via
* {@link SupportCallback}.
*
*
If the service does not offer the support check it will notify the caller with
* {@link SpeechRecognizer#ERROR_CANNOT_CHECK_SUPPORT}.
*/
public void onCheckRecognitionSupport(
@NonNull Intent recognizerIntent,
@NonNull SupportCallback supportCallback) {
if (DBG) {
Log.i(TAG, String.format("#onSupports [%s]", recognizerIntent));
}
supportCallback.onError(SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT);
}
/**
* Queries the service on whether it would support a {@link #onStartListening(Intent, Callback)}
* for the same {@code recognizerIntent}.
*
*
The service will notify the caller about the level of support or error via
* {@link SupportCallback}.
*
*
If the service does not offer the support check it will notify the caller with
* {@link SpeechRecognizer#ERROR_CANNOT_CHECK_SUPPORT}.
*
*
Provides the calling AttributionSource to the service implementation so that permissions
* and bandwidth could be correctly blamed.
*/
public void onCheckRecognitionSupport(
@NonNull Intent recognizerIntent,
@NonNull AttributionSource attributionSource,
@NonNull SupportCallback supportCallback) {
onCheckRecognitionSupport(recognizerIntent, supportCallback);
}
/**
* Requests the download of the recognizer support for {@code recognizerIntent}.
*/
public void onTriggerModelDownload(@NonNull Intent recognizerIntent) {
if (DBG) {
Log.i(TAG, String.format("#downloadModel [%s]", recognizerIntent));
}
}
/**
* Requests the download of the recognizer support for {@code recognizerIntent}.
*
* Provides the calling AttributionSource to the service implementation so that permissions
* and bandwidth could be correctly blamed.
*/
public void onTriggerModelDownload(
@NonNull Intent recognizerIntent,
@NonNull AttributionSource attributionSource) {
onTriggerModelDownload(recognizerIntent);
}
/**
* Requests the download of the recognizer support for {@code recognizerIntent}.
*
* Provides the calling {@link AttributionSource} to the service implementation so that
* permissions and bandwidth could be correctly blamed.
*
*
Client will receive the progress updates via the given {@link ModelDownloadListener}:
*
*
If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
* called directly. The model can be safely used afterwards.
*
* If the {@link RecognitionService} has started the download,
* {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
* number of times until the download is complete.
* When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
* The model can be safely used afterwards.
*
* If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
* immediately, {@link ModelDownloadListener#onScheduled()} will be called.
* There will be no further updates on this listener.
*
* If the request fails at any time due to a network or scheduling error,
* {@link ModelDownloadListener#onError(int)} will be called.
*
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
* may also contain optional extras, see {@link RecognizerIntent}.
* @param attributionSource the attribution source of the caller.
* @param listener on which to receive updates about the model download request.
*/
public void onTriggerModelDownload(
@NonNull Intent recognizerIntent,
@NonNull AttributionSource attributionSource,
@NonNull ModelDownloadListener listener) {
listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
}
@Override
@SuppressLint("MissingNullability")
public Context createContext(@NonNull ContextParams contextParams) {
if (contextParams.getNextAttributionSource() != null) {
if (mHandler.getLooper().equals(Looper.myLooper())) {
handleAttributionContextCreation(contextParams.getNextAttributionSource());
} else {
mHandler.sendMessage(
PooledLambda.obtainMessage(this::handleAttributionContextCreation,
contextParams.getNextAttributionSource()));
}
}
return super.createContext(contextParams);
}
private void handleAttributionContextCreation(@NonNull AttributionSource attributionSource) {
for (SessionState sessionState : mSessions.values()) {
Callback currentCallback = sessionState.mCallback;
if (currentCallback != null
&& currentCallback.mCallingAttributionSource.equals(attributionSource)) {
currentCallback.mAttributionContextCreated = true;
}
}
}
@Override
public final IBinder onBind(final Intent intent) {
if (DBG) Log.d(TAG, "#onBind, intent=" + intent);
onBindInternal();
return mBinder;
}
/** @hide */
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
public void onBindInternal() { }
@Override
public void onDestroy() {
if (DBG) Log.d(TAG, "#onDestroy");
for (SessionState sessionState : mSessions.values()) {
finishDataDelivery(sessionState);
sessionState.reset();
}
mSessions.clear();
mBinder.clearReference();
super.onDestroy();
}
/**
* Returns the maximal number of recognition sessions ongoing at the same time.
*
* The default value is 1, meaning concurrency should be enabled by overriding this method.
*/
public int getMaxConcurrentSessionsCount() {
return DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT;
}
/**
* This class receives callbacks from the speech recognition service and forwards them to the
* user. An instance of this class is passed to the
* {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
* these methods on any thread.
*/
public class Callback {
private final IRecognitionListener mListener;
@NonNull private final AttributionSource mCallingAttributionSource;
@Nullable private Context mAttributionContext;
private boolean mAttributionContextCreated;
private Callback(IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
mListener = listener;
mCallingAttributionSource = attributionSource;
}
/**
* The service should call this method when the user has started to speak.
*/
public void beginningOfSpeech() throws RemoteException {
mListener.onBeginningOfSpeech();
}
/**
* The service should call this method when sound has been received. The purpose of this
* function is to allow giving feedback to the user regarding the captured audio.
*
* @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
* single channel audio stream. The sample rate is implementation dependent.
*/
public void bufferReceived(byte[] buffer) throws RemoteException {
mListener.onBufferReceived(buffer);
}
/**
* The service should call this method after the user stops speaking.
*/
public void endOfSpeech() throws RemoteException {
mListener.onEndOfSpeech();
}
/**
* The service should call this method when a network or recognition error occurred.
*
* @param error code is defined in {@link SpeechRecognizer}
*/
public void error(@SpeechRecognizer.RecognitionError int error) throws RemoteException {
Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onError(error);
}
/**
* The service should call this method when partial recognition results are available. This
* method can be called at any time between {@link #beginningOfSpeech()} and
* {@link #results(Bundle)} when partial results are ready. This method may be called zero,
* one or multiple times for each call to {@link SpeechRecognizer#startListening(Intent)},
* depending on the speech recognition service implementation.
*
* @param partialResults the returned results. To retrieve the results in
* ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
public void partialResults(Bundle partialResults) throws RemoteException {
mListener.onPartialResults(partialResults);
}
/**
* The service should call this method when the endpointer is ready for the user to start
* speaking.
*
* @param params parameters set by the recognition service. Reserved for future use.
*/
public void readyForSpeech(Bundle params) throws RemoteException {
mListener.onReadyForSpeech(params);
}
/**
* The service should call this method when recognition results are ready.
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList} format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
public void results(Bundle results) throws RemoteException {
Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onResults(results);
}
/**
* The service should call this method when the sound level in the audio stream has changed.
* There is no guarantee that this method will be called.
*
* @param rmsdB the new RMS dB value
*/
public void rmsChanged(float rmsdB) throws RemoteException {
mListener.onRmsChanged(rmsdB);
}
/**
* The service should call this method for each ready segment of a long recognition session.
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList} format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
@SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
public void segmentResults(@NonNull Bundle results) throws RemoteException {
mListener.onSegmentResults(results);
}
/**
* The service should call this method to end a segmented session.
*/
@SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
public void endOfSegmentedSession() throws RemoteException {
Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onEndOfSegmentedSession();
}
/**
* The service should call this method when the language detection (and switching)
* results are available. This method can be called on any number of occasions
* at any time between {@link #beginningOfSpeech()} and {@link #endOfSpeech()},
* depending on the speech recognition service implementation.
*
* @param results the returned language detection (and switching) results.
* To retrieve the most confidently detected language IETF tag
* (as defined by BCP 47, e.g., "en-US", "de-DE"),
* use {@link Bundle#getString(String)}
* with {@link SpeechRecognizer#DETECTED_LANGUAGE} as the parameter.
*
To retrieve the language detection confidence level represented by a value
* prefixed by {@code LANGUAGE_DETECTION_CONFIDENCE_LEVEL_} defined in
* {@link SpeechRecognizer}, use {@link Bundle#getInt(String)} with
* {@link SpeechRecognizer#LANGUAGE_DETECTION_CONFIDENCE_LEVEL} as the parameter.
*
To retrieve the alternative locales for the same language
* retrieved by the key {@link SpeechRecognizer#DETECTED_LANGUAGE},
* use {@link Bundle#getStringArrayList(String)}
* with {@link SpeechRecognizer#TOP_LOCALE_ALTERNATIVES} as the parameter.
*
To retrieve the language switching results represented by a value
* prefixed by {@code LANGUAGE_SWITCH_RESULT_}
* and defined in {@link SpeechRecognizer}, use {@link Bundle#getInt(String)}
* with {@link SpeechRecognizer#LANGUAGE_SWITCH_RESULT} as the parameter.
*/
@SuppressLint("CallbackMethodName") // For consistency with existing methods.
public void languageDetection(@NonNull Bundle results) {
try {
mListener.onLanguageDetection(results);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the Linux uid assigned to the process that sent you the current transaction that
* is being processed. This is obtained from {@link Binder#getCallingUid()}.
*/
public int getCallingUid() {
return mCallingAttributionSource.getUid();
}
/**
* Gets the permission identity of the calling app. If you want to attribute
* the mic access to the calling app you can create an attribution context
* via {@link android.content.Context#createContext(android.content.ContextParams)}
* and passing this identity to {@link
* android.content.ContextParams.Builder#setNextAttributionSource(AttributionSource)}.
*
* @return The permission identity of the calling app.
*
* @see android.content.ContextParams.Builder#setNextAttributionSource(
* AttributionSource)
*/
@SuppressLint("CallbackMethodName")
@NonNull
public AttributionSource getCallingAttributionSource() {
return mCallingAttributionSource;
}
@NonNull Context getAttributionContextForCaller() {
if (mAttributionContext == null) {
mAttributionContext = createContext(new ContextParams.Builder()
.setNextAttributionSource(mCallingAttributionSource)
.build());
}
return mAttributionContext;
}
}
/**
* This class receives callbacks from the speech recognition service and forwards them to the
* user. An instance of this class is passed to the
* {@link RecognitionService#onCheckRecognitionSupport(Intent, SupportCallback)} method. Recognizers may call
* these methods on any thread.
*/
public static class SupportCallback {
private final IRecognitionSupportCallback mCallback;
private SupportCallback(
IRecognitionSupportCallback callback) {
this.mCallback = callback;
}
/** The service should call this method to notify the caller about the level of support. */
public void onSupportResult(@NonNull RecognitionSupport recognitionSupport) {
try {
mCallback.onSupportResult(recognitionSupport);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* The service should call this method when an error occurred and can't satisfy the support
* request.
*
* @param errorCode code is defined in {@link SpeechRecognizer}
*/
public void onError(@SpeechRecognizer.RecognitionError int errorCode) {
try {
mCallback.onError(errorCode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/** Binder of the recognition service. */
private static final class RecognitionServiceBinder extends IRecognitionService.Stub {
private final WeakReference mServiceRef;
public RecognitionServiceBinder(RecognitionService service) {
mServiceRef = new WeakReference<>(service);
}
@Override
public void startListening(Intent recognizerIntent, IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
Objects.requireNonNull(attributionSource);
attributionSource.enforceCallingUid();
if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
MSG_START_LISTENING, new StartListeningArgs(
recognizerIntent, listener, attributionSource)));
}
}
@Override
public void stopListening(IRecognitionListener listener) {
if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(service.mHandler, MSG_STOP_LISTENING, listener));
}
}
@Override
public void cancel(IRecognitionListener listener, boolean isShutdown) {
if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(service.mHandler, MSG_CANCEL, listener));
}
}
@Override
public void checkRecognitionSupport(
Intent recognizerIntent,
@NonNull AttributionSource attributionSource,
IRecognitionSupportCallback callback) {
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(service.mHandler, MSG_CHECK_RECOGNITION_SUPPORT,
new CheckRecognitionSupportArgs(
recognizerIntent, callback, attributionSource)));
}
}
@Override
public void triggerModelDownload(
Intent recognizerIntent,
@NonNull AttributionSource attributionSource,
IModelDownloadListener listener) {
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(
Message.obtain(
service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
new ModelDownloadArgs(
recognizerIntent,
attributionSource,
listener)));
}
}
public void clearReference() {
mServiceRef.clear();
}
}
private boolean checkPermissionAndStartDataDelivery(SessionState sessionState) {
if (sessionState.mCallback.mAttributionContextCreated) {
return true;
}
if (PermissionChecker.checkPermissionAndStartDataDelivery(
RecognitionService.this,
Manifest.permission.RECORD_AUDIO,
sessionState.mCallback.getAttributionContextForCaller().getAttributionSource(),
/* message */ null)
== PermissionChecker.PERMISSION_GRANTED) {
sessionState.mStartedDataDelivery = true;
}
return sessionState.mStartedDataDelivery;
}
private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
int result = PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
Manifest.permission.RECORD_AUDIO, attributionSource);
return result == PermissionChecker.PERMISSION_GRANTED
|| result == PermissionChecker.PERMISSION_SOFT_DENIED;
}
void finishDataDelivery(SessionState sessionState) {
if (sessionState.mStartedDataDelivery) {
sessionState.mStartedDataDelivery = false;
final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
PermissionChecker.finishDataDelivery(RecognitionService.this, op,
sessionState.mCallback.getAttributionContextForCaller().getAttributionSource());
}
}
/**
* Data class containing information about an ongoing session:
*
* - {@link SessionState#mCallback} - callback of the client that invoked the
* {@link RecognitionService#onStartListening(Intent, Callback)} method;
*
- {@link SessionState#mStartedDataDelivery} - flag denoting if data
* is being delivered to the client.
*/
private static class SessionState {
private Callback mCallback;
private boolean mStartedDataDelivery;
SessionState(Callback callback, boolean startedDataDelivery) {
mCallback = callback;
mStartedDataDelivery = startedDataDelivery;
}
SessionState(Callback currentCallback) {
this(currentCallback, false);
}
void reset() {
mCallback = null;
mStartedDataDelivery = false;
}
}
}