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