1 /*
2  * Copyright (C) 2023 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 
17 package android.service.voice;
18 
19 import static android.Manifest.permission.CAMERA;
20 import static android.Manifest.permission.RECORD_AUDIO;
21 
22 import android.annotation.CallbackExecutor;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SuppressLint;
27 import android.annotation.SystemApi;
28 import android.content.Context;
29 import android.hardware.soundtrigger.SoundTrigger;
30 import android.media.AudioFormat;
31 import android.os.Binder;
32 import android.os.ParcelFileDescriptor;
33 import android.os.PersistableBundle;
34 import android.os.RemoteException;
35 import android.os.SharedMemory;
36 import android.text.TextUtils;
37 import android.util.Slog;
38 
39 import com.android.internal.app.IHotwordRecognitionStatusCallback;
40 import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener;
41 import com.android.internal.app.IVoiceInteractionManagerService;
42 import com.android.internal.infra.AndroidFuture;
43 
44 import java.io.File;
45 import java.io.FileNotFoundException;
46 import java.io.PrintWriter;
47 import java.util.concurrent.Executor;
48 import java.util.function.Consumer;
49 
50 /**
51  * Manages VisualQueryDetectionService.
52  *
53  * This detector provides necessary functionalities to initialize, start, update and destroy a
54  * {@link VisualQueryDetectionService}.
55  *
56  * @hide
57  **/
58 @SystemApi
59 @SuppressLint("NotCloseable")
60 public class VisualQueryDetector {
61     private static final String TAG = VisualQueryDetector.class.getSimpleName();
62     private static final boolean DEBUG = false;
63     private static final int SETTINGS_DISABLE_BIT = 0;
64     private static final int SETTINGS_ENABLE_BIT = 1;
65 
66     private final Callback mCallback;
67     private final Executor mExecutor;
68     private final Context mContext;
69     private final IVoiceInteractionManagerService mManagerService;
70     private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
71     private final String mAttributionTag;
72     // Used to manage the internal mapping of exposed listener API and internal aidl impl
73     private AccessibilityDetectionEnabledListenerWrapper mActiveAccessibilityListenerWrapper = null;
74 
VisualQueryDetector( IVoiceInteractionManagerService managerService, @NonNull @CallbackExecutor Executor executor, Callback callback, Context context, @Nullable String attributionTag)75     VisualQueryDetector(
76             IVoiceInteractionManagerService managerService,
77             @NonNull @CallbackExecutor Executor executor, Callback callback, Context context,
78             @Nullable String attributionTag) {
79         mManagerService = managerService;
80         mCallback = callback;
81         mExecutor = executor;
82         mInitializationDelegate = new VisualQueryDetectorInitializationDelegate();
83         mContext = context;
84         mAttributionTag = attributionTag;
85     }
86 
87     /**
88      * Initialize the {@link VisualQueryDetectionService} by passing configurations and read-only
89      * data.
90      */
initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)91     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
92         mInitializationDelegate.initialize(options, sharedMemory);
93     }
94 
95     /**
96      * Set configuration and pass read-only data to {@link VisualQueryDetectionService}.
97      *
98      * @see HotwordDetector#updateState(PersistableBundle, SharedMemory)
99      */
updateState(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)100     public void updateState(@Nullable PersistableBundle options,
101             @Nullable SharedMemory sharedMemory) {
102         synchronized (mInitializationDelegate.getLock()) {
103             mInitializationDelegate.updateState(options, sharedMemory);
104         }
105     }
106 
107 
108     /**
109      * On calling this method, {@link VisualQueryDetectionService
110      * #onStartDetection(VisualQueryDetectionService.Callback)} will be called to start using
111      * visual signals such as camera frames and microphone audio to perform detection. When user
112      * attention is captured and the {@link VisualQueryDetectionService} streams queries,
113      * {@link VisualQueryDetector.Callback#onQueryDetected(String)} is called to control the
114      * behavior of handling {@code transcribedText}. When the query streaming is finished,
115      * {@link VisualQueryDetector.Callback#onQueryFinished()} is called. If the current streamed
116      * query is invalid, {@link VisualQueryDetector.Callback#onQueryRejected()} is called to abandon
117      * the streamed query.
118      *
119      * @see HotwordDetector#startRecognition()
120      */
121     @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
startRecognition()122     public boolean startRecognition() {
123         if (DEBUG) {
124             Slog.i(TAG, "#startRecognition");
125         }
126         synchronized (mInitializationDelegate.getLock()) {
127             // check if the detector is active with the initialization delegate
128             mInitializationDelegate.startRecognition();
129 
130             try {
131                 mManagerService.startPerceiving(new BinderCallback(
132                         mExecutor, mCallback, mInitializationDelegate.getLock()));
133             } catch (SecurityException e) {
134                 Slog.e(TAG, "startRecognition failed: " + e);
135                 return false;
136             } catch (RemoteException e) {
137                 e.rethrowFromSystemServer();
138             }
139             return true;
140         }
141     }
142 
143     /**
144      * Stops visual query detection recognition.
145      *
146      * @see HotwordDetector#stopRecognition()
147      */
148     @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
stopRecognition()149     public boolean stopRecognition() {
150         if (DEBUG) {
151             Slog.i(TAG, "#stopRecognition");
152         }
153         synchronized (mInitializationDelegate.getLock()) {
154             // check if the detector is active with the initialization delegate
155             mInitializationDelegate.stopRecognition();
156 
157             try {
158                 mManagerService.stopPerceiving();
159             } catch (RemoteException e) {
160                 e.rethrowFromSystemServer();
161             }
162             return true;
163         }
164     }
165 
166     /**
167      * Destroy the current detector.
168      *
169      * @see HotwordDetector#destroy()
170      */
destroy()171     public void destroy() {
172         if (DEBUG) {
173             Slog.i(TAG, "#destroy");
174         }
175         synchronized (mInitializationDelegate.getLock()) {
176             mInitializationDelegate.destroy();
177         }
178     }
179 
180     /**
181      * Gets the binary value that controls the egress of accessibility data from
182      * {@link VisualQueryDetectedResult#setAccessibilityDetectionData(byte[])} is enabled.
183      *
184      * @return boolean value denoting if the setting is on. Default is {@code false}.
185      * @hide
186      */
187     @SystemApi
188     @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
isAccessibilityDetectionEnabled()189     public boolean isAccessibilityDetectionEnabled() {
190         Slog.d(TAG, "Fetching accessibility setting");
191         synchronized (mInitializationDelegate.getLock()) {
192             try {
193                 return mManagerService.getAccessibilityDetectionEnabled();
194             } catch (RemoteException e) {
195                 e.rethrowFromSystemServer();
196             }
197             return false;
198         }
199     }
200 
201     /**
202      * Sets a listener subscribing to the value of the system setting that controls the egress of
203      * accessibility data from
204      * {@link VisualQueryDetectedResult#setAccessibilityDetectionData(byte[])} is enabled.
205      *
206      * Only one listener can be set at a time. The listener set must be unset with
207      * {@link clearAccessibilityDetectionEnabledListener(Consumer<Boolean>)}
208      * in order to set a new listener. Otherwise, this method will throw a
209      * {@link IllegalStateException}.
210      *
211      * @param listener Listener of type {@code Consumer<Boolean>} to subscribe to the value update.
212      * @hide
213      */
214     @SystemApi
215     @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
setAccessibilityDetectionEnabledListener(@onNull Consumer<Boolean> listener)216     public void setAccessibilityDetectionEnabledListener(@NonNull Consumer<Boolean> listener) {
217         Slog.d(TAG, "Registering Accessibility settings listener.");
218         synchronized (mInitializationDelegate.getLock()) {
219             try {
220                 if (mActiveAccessibilityListenerWrapper != null) {
221                     Slog.e(TAG, "Fail to register accessibility setting listener: "
222                             + "already registered and not unregistered.");
223                     throw new IllegalStateException(
224                             "Cannot register listener with listeners already set.");
225                 }
226                 mActiveAccessibilityListenerWrapper =
227                         new AccessibilityDetectionEnabledListenerWrapper(listener);
228                 mManagerService.registerAccessibilityDetectionSettingsListener(
229                         mActiveAccessibilityListenerWrapper);
230             } catch (RemoteException e) {
231                 e.rethrowFromSystemServer();
232             }
233         }
234     }
235 
236     /**
237      * Clear the listener that has been set with
238      * {@link setAccessibilityDetectionEnabledListener(Consumer<Boolean>)} such that when the value
239      * of the setting that controls the egress of accessibility data is changed the listener gets
240      * notified.
241      *
242      * If there is not listener that has been registered, the call to this method will lead to a
243      * {@link IllegalStateException}.
244      *
245      * @hide
246      */
247     @SystemApi
248     @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
clearAccessibilityDetectionEnabledListener()249     public void clearAccessibilityDetectionEnabledListener() {
250         Slog.d(TAG, "Unregistering Accessibility settings listener.");
251         synchronized (mInitializationDelegate.getLock()) {
252             try {
253                 if (mActiveAccessibilityListenerWrapper == null) {
254                     Slog.e(TAG, "Not able to remove the listener: listener does not exist.");
255                     throw new IllegalStateException("Cannot clear listener since it is not set.");
256                 }
257                 mManagerService.unregisterAccessibilityDetectionSettingsListener(
258                         mActiveAccessibilityListenerWrapper);
259                 mActiveAccessibilityListenerWrapper = null;
260             } catch (RemoteException e) {
261                 e.rethrowFromSystemServer();
262             }
263         }
264     }
265 
266 
267     private final class AccessibilityDetectionEnabledListenerWrapper
268             extends IVoiceInteractionAccessibilitySettingsListener.Stub {
269 
270         private Consumer<Boolean> mListener;
271 
AccessibilityDetectionEnabledListenerWrapper(Consumer<Boolean> listener)272         AccessibilityDetectionEnabledListenerWrapper(Consumer<Boolean> listener) {
273             mListener = listener;
274         }
275 
276         @Override
onAccessibilityDetectionChanged(boolean enabled)277         public void onAccessibilityDetectionChanged(boolean enabled) {
278             mListener.accept(enabled);
279         }
280     }
281 
282     /** @hide */
dump(String prefix, PrintWriter pw)283     public void dump(String prefix, PrintWriter pw) {
284         synchronized (mInitializationDelegate.getLock()) {
285             mInitializationDelegate.dump(prefix, pw);
286         }
287     }
288 
289     /** @hide */
getInitializationDelegate()290     public HotwordDetector getInitializationDelegate() {
291         return mInitializationDelegate;
292     }
293 
294     /** @hide */
registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener)295     void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
296         synchronized (mInitializationDelegate.getLock()) {
297             mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
298         }
299     }
300 
301     /**
302      * A class that lets a VoiceInteractionService implementation interact with visual query
303      * detection APIs.
304      *
305      * Note that methods in this callbacks are not thread-safe so the invocation of each
306      * methods will have different order from how they are called in the
307      * {@link VisualQueryDetectionService}. It is expected to pass a single thread executor or a
308      * serial executor as the callback executor when creating the {@link VisualQueryDetector}
309      * with {@link VoiceInteractionService#createVisualQueryDetector(
310      * PersistableBundle, SharedMemory, Executor, Callback)}.
311      */
312     public interface Callback {
313 
314         /**
315          * Called when the {@link VisualQueryDetectionService} starts to stream partial queries
316          * with {@link VisualQueryDetectionService#streamQuery(String)}.
317          *
318          * @param partialQuery The partial query in a text form being streamed.
319          */
onQueryDetected(@onNull String partialQuery)320         void onQueryDetected(@NonNull String partialQuery);
321 
322         /**
323          * Called when the {@link VisualQueryDetectionService} starts to stream partial results
324          * with {@link VisualQueryDetectionService#streamQuery(VisualQueryDetectedResult)}.
325          *
326          * @param partialResult The partial query in a text form being streamed.
327          */
328         @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
onQueryDetected(@onNull VisualQueryDetectedResult partialResult)329         default void onQueryDetected(@NonNull VisualQueryDetectedResult partialResult) {
330             throw new UnsupportedOperationException("This emthod must be implemented for use.");
331         }
332 
333         /**
334          * Called when the {@link VisualQueryDetectionService} decides to abandon the streamed
335          * partial queries with {@link VisualQueryDetectionService#rejectQuery()}.
336          */
onQueryRejected()337         void onQueryRejected();
338 
339         /**
340          *  Called when the {@link VisualQueryDetectionService} finishes streaming partial queries
341          *  with {@link VisualQueryDetectionService#finishQuery()}.
342          */
onQueryFinished()343         void onQueryFinished();
344 
345         /**
346          * Called when the {@link VisualQueryDetectionService} is created by the system and given a
347          * short amount of time to report its initialization state.
348          *
349          * @param status Info about initialization state of {@link VisualQueryDetectionService}; the
350          * allowed values are
351          * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_SUCCESS},
352          * 1<->{@link SandboxedDetectionInitializer#getMaxCustomInitializationStatus()},
353          * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_UNKNOWN}.
354          */
onVisualQueryDetectionServiceInitialized(int status)355         void onVisualQueryDetectionServiceInitialized(int status);
356 
357          /**
358          * Called with the {@link VisualQueryDetectionService} is restarted.
359          *
360          * Clients are expected to call {@link HotwordDetector#updateState} to share the state with
361          * the newly created service.
362          */
onVisualQueryDetectionServiceRestarted()363         void onVisualQueryDetectionServiceRestarted();
364 
365         /**
366          * Called when the detection fails due to an error occurs in the
367          * {@link VisualQueryDetectionService}, {@link VisualQueryDetectionServiceFailure} will be
368          * reported to the detector.
369          *
370          * @param visualQueryDetectionServiceFailure It provides the error code, error message and
371          *                                           suggested action.
372          */
onFailure( @onNull VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)373         void onFailure(
374                 @NonNull VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure);
375 
376         /**
377          * Called when the detection fails due to an unknown error occurs, an error message
378          * will be reported to the detector.
379          *
380          * @param errorMessage It provides the error message.
381          */
onUnknownFailure(@onNull String errorMessage)382         void onUnknownFailure(@NonNull String errorMessage);
383     }
384 
385     private class VisualQueryDetectorInitializationDelegate extends AbstractDetector {
386 
VisualQueryDetectorInitializationDelegate()387         VisualQueryDetectorInitializationDelegate() {
388             super(mManagerService, mExecutor, /* callback= */ null);
389         }
390 
391         @Override
initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)392         void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
393             initAndVerifyDetector(options, sharedMemory,
394                     new InitializationStateListener(mExecutor, mCallback, mContext),
395                     DETECTOR_TYPE_VISUAL_QUERY_DETECTOR, mAttributionTag);
396         }
397 
398         @Override
stopRecognition()399         public boolean stopRecognition() {
400             throwIfDetectorIsNoLongerActive();
401             return true;
402         }
403 
404         @Override
startRecognition()405         public boolean startRecognition() {
406             throwIfDetectorIsNoLongerActive();
407             return true;
408         }
409 
410         @Override
startRecognition( @onNull ParcelFileDescriptor audioStream, @NonNull AudioFormat audioFormat, @Nullable PersistableBundle options)411         public final boolean startRecognition(
412                 @NonNull ParcelFileDescriptor audioStream,
413                 @NonNull AudioFormat audioFormat,
414                 @Nullable PersistableBundle options) {
415             //No-op, not supported by VisualQueryDetector as it should be trusted.
416             return false;
417         }
418 
419         @Override
isUsingSandboxedDetectionService()420         public boolean isUsingSandboxedDetectionService() {
421             return true;
422         }
423 
424         @Override
dump(String prefix, PrintWriter pw)425         public void dump(String prefix, PrintWriter pw) {
426             // No-op
427         }
428 
getLock()429         private Object getLock() {
430             return mLock;
431         }
432     }
433 
434     private static class BinderCallback
435             extends IVisualQueryDetectionVoiceInteractionCallback.Stub {
436         private final Executor mExecutor;
437         private final VisualQueryDetector.Callback mCallback;
438 
439         private final Object mLock;
440 
BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock)441         BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock) {
442             this.mExecutor = executor;
443             this.mCallback = callback;
444             this.mLock = lock;
445         }
446 
447         /** Called when the detected query is valid. */
448         @Override
onQueryDetected(@onNull String partialQuery)449         public void onQueryDetected(@NonNull String partialQuery) {
450             Slog.v(TAG, "BinderCallback#onQueryDetected");
451             Binder.withCleanCallingIdentity(() -> {
452                 synchronized (mLock) {
453                     mExecutor.execute(()->mCallback.onQueryDetected(partialQuery));
454                 }
455             });
456         }
457 
458         /** Called when the detected result is valid. */
459         @Override
onResultDetected(@onNull VisualQueryDetectedResult partialResult)460         public void onResultDetected(@NonNull VisualQueryDetectedResult partialResult) {
461             Slog.v(TAG, "BinderCallback#onResultDetected");
462             Binder.withCleanCallingIdentity(() -> {
463                 synchronized (mLock) {
464                     mExecutor.execute(()->mCallback.onQueryDetected(partialResult));
465                 }
466             });
467         }
468 
469         @Override
onQueryFinished()470         public void onQueryFinished() {
471             Slog.v(TAG, "BinderCallback#onQueryFinished");
472             Binder.withCleanCallingIdentity(() -> {
473                 synchronized (mLock) {
474                     mExecutor.execute(()->mCallback.onQueryFinished());
475                 }
476             });
477         }
478 
479         @Override
onQueryRejected()480         public void onQueryRejected() {
481             Slog.v(TAG, "BinderCallback#onQueryRejected");
482             Binder.withCleanCallingIdentity(() -> {
483                 synchronized (mLock) {
484                     mExecutor.execute(()->mCallback.onQueryRejected());
485                 }
486             });
487         }
488 
489         /** Called when the detection fails due to an error. */
490         @Override
onVisualQueryDetectionServiceFailure( VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)491         public void onVisualQueryDetectionServiceFailure(
492                 VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure) {
493             Slog.v(TAG, "BinderCallback#onVisualQueryDetectionServiceFailure: "
494                     + visualQueryDetectionServiceFailure);
495             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
496                 if (visualQueryDetectionServiceFailure != null) {
497                     mCallback.onFailure(visualQueryDetectionServiceFailure);
498                 } else {
499                     mCallback.onUnknownFailure("Error data is null");
500                 }
501             }));
502         }
503     }
504 
505 
506     private static class InitializationStateListener
507             extends IHotwordRecognitionStatusCallback.Stub {
508         private final Executor mExecutor;
509         private final Callback mCallback;
510 
511         private final Context mContext;
512 
InitializationStateListener(Executor executor, Callback callback, Context context)513         InitializationStateListener(Executor executor, Callback callback, Context context) {
514             this.mExecutor = executor;
515             this.mCallback = callback;
516             this.mContext = context;
517         }
518 
519         @Override
onKeyphraseDetected( SoundTrigger.KeyphraseRecognitionEvent recognitionEvent, HotwordDetectedResult result)520         public void onKeyphraseDetected(
521                 SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
522                 HotwordDetectedResult result) {
523             if (DEBUG) {
524                 Slog.i(TAG, "Ignored #onKeyphraseDetected event");
525             }
526         }
527 
528         @Override
onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result)529         public void onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result) {
530             if (DEBUG) {
531                 Slog.i(TAG, "Ignored #onKeyphraseDetectedFromExternalSource event");
532             }
533         }
534 
535         @Override
onGenericSoundTriggerDetected( SoundTrigger.GenericRecognitionEvent recognitionEvent)536         public void onGenericSoundTriggerDetected(
537                 SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException {
538             if (DEBUG) {
539                 Slog.i(TAG, "Ignored #onGenericSoundTriggerDetected event");
540             }
541         }
542 
543         @Override
onRejected(HotwordRejectedResult result)544         public void onRejected(HotwordRejectedResult result) throws RemoteException {
545             if (DEBUG) {
546                 Slog.i(TAG, "Ignored #onRejected event");
547             }
548         }
549 
550         @Override
onRecognitionPaused()551         public void onRecognitionPaused() throws RemoteException {
552             if (DEBUG) {
553                 Slog.i(TAG, "Ignored #onRecognitionPaused event");
554             }
555         }
556 
557         @Override
onRecognitionResumed()558         public void onRecognitionResumed() throws RemoteException {
559             if (DEBUG) {
560                 Slog.i(TAG, "Ignored #onRecognitionResumed event");
561             }
562         }
563 
564         @Override
onStatusReported(int status)565         public void onStatusReported(int status) {
566             Slog.v(TAG, "onStatusReported" + (DEBUG ? "(" + status + ")" : ""));
567             //TODO: rename the target callback with a more general term
568             Binder.withCleanCallingIdentity(() -> mExecutor.execute(
569                     () -> mCallback.onVisualQueryDetectionServiceInitialized(status)));
570 
571         }
572 
573         @Override
onProcessRestarted()574         public void onProcessRestarted() throws RemoteException {
575             Slog.v(TAG, "onProcessRestarted()");
576             //TODO: rename the target callback with a more general term
577             Binder.withCleanCallingIdentity(() -> mExecutor.execute(
578                     () -> mCallback.onVisualQueryDetectionServiceRestarted()));
579         }
580 
581         @Override
onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure)582         public void onHotwordDetectionServiceFailure(
583                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
584                 throws RemoteException {
585             // It should never be called here.
586             Slog.w(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
587         }
588 
589         @Override
onVisualQueryDetectionServiceFailure( VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)590         public void onVisualQueryDetectionServiceFailure(
591                 VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)
592                 throws RemoteException {
593             Slog.v(TAG, "onVisualQueryDetectionServiceFailure: "
594                     + visualQueryDetectionServiceFailure);
595             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
596                 if (visualQueryDetectionServiceFailure != null) {
597                     mCallback.onFailure(visualQueryDetectionServiceFailure);
598                 } else {
599                     mCallback.onUnknownFailure("Error data is null");
600                 }
601             }));
602         }
603 
604         @Override
onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure)605         public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
606             Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure);
607         }
608 
609         @Override
onUnknownFailure(String errorMessage)610         public void onUnknownFailure(String errorMessage) throws RemoteException {
611             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
612             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
613                 mCallback.onUnknownFailure(
614                         !TextUtils.isEmpty(errorMessage) ? errorMessage : "Error data is null");
615             }));
616         }
617         @Override
onOpenFile(String filename, AndroidFuture future)618         public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
619             Slog.v(TAG, "BinderCallback#onOpenFile " + filename);
620             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
621                 Slog.v(TAG, "onOpenFile: " + filename + "under internal app storage.");
622                 File f = new File(mContext.getFilesDir(), filename);
623                 ParcelFileDescriptor pfd = null;
624                 try {
625                     pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
626                     Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
627                 } catch (FileNotFoundException e) {
628                     Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
629                 } finally {
630                     future.complete(pfd);
631                 }
632             }));
633         }
634     }
635 }
636