1 /* 2 * Copyright (C) 2022 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.wearable; 18 19 import android.annotation.BinderThread; 20 import android.annotation.FlaggedApi; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.app.Service; 26 import android.app.ambientcontext.AmbientContextEvent; 27 import android.app.ambientcontext.AmbientContextEventRequest; 28 import android.app.wearable.Flags; 29 import android.app.wearable.IWearableSensingCallback; 30 import android.app.wearable.WearableSensingDataRequest; 31 import android.app.wearable.WearableSensingManager; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.os.Bundle; 35 import android.os.IBinder; 36 import android.os.ParcelFileDescriptor; 37 import android.os.PersistableBundle; 38 import android.os.Process; 39 import android.os.RemoteCallback; 40 import android.os.RemoteException; 41 import android.os.SharedMemory; 42 import android.service.ambientcontext.AmbientContextDetectionResult; 43 import android.service.ambientcontext.AmbientContextDetectionServiceStatus; 44 import android.service.voice.HotwordAudioStream; 45 import android.text.TextUtils; 46 import android.util.Slog; 47 import android.util.SparseArray; 48 49 import com.android.internal.infra.AndroidFuture; 50 51 import java.io.FileInputStream; 52 import java.io.FileNotFoundException; 53 import java.time.Duration; 54 import java.util.Arrays; 55 import java.util.HashSet; 56 import java.util.Objects; 57 import java.util.Set; 58 import java.util.concurrent.ExecutionException; 59 import java.util.concurrent.Executor; 60 import java.util.concurrent.TimeUnit; 61 import java.util.concurrent.TimeoutException; 62 import java.util.function.Consumer; 63 64 /** 65 * Abstract base class for sensing with wearable devices. An example of this is {@link 66 *AmbientContextEvent} detection. 67 * 68 * <p> A service that provides requested sensing events to the system, such as a {@link 69 *AmbientContextEvent}. The system's default WearableSensingService implementation is configured in 70 * {@code config_defaultWearableSensingService}. If this config has no value, a stub is 71 * returned. 72 * 73 * <p> An implementation of a WearableSensingService should be an isolated service. Using the 74 * "isolatedProcess=true" attribute in the service's configurations. </p> 75 ** 76 * <pre> 77 * {@literal 78 * <service android:name=".YourWearableSensingService" 79 * android:permission="android.permission.BIND_WEARABLE_SENSING_SERVICE" 80 * android:isolatedProcess="true"> 81 * </service>} 82 * </pre> 83 * 84 * <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated 85 * separately. </p> 86 * 87 * @hide 88 */ 89 @SystemApi 90 public abstract class WearableSensingService extends Service { 91 private static final String TAG = WearableSensingService.class.getSimpleName(); 92 93 /** 94 * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. 95 * 96 * @hide 97 */ 98 public static final String STATUS_RESPONSE_BUNDLE_KEY = 99 "android.app.wearable.WearableSensingStatusBundleKey"; 100 101 /** 102 * The bundle key for hotword audio stream, used in {@code RemoteCallback#sendResult}. 103 * 104 * @hide 105 */ 106 public static final String HOTWORD_AUDIO_STREAM_BUNDLE_KEY = 107 "android.app.wearable.HotwordAudioStreamBundleKey"; 108 109 /** 110 * The {@link Intent} that must be declared as handled by the service. To be supported, the 111 * service must also require the 112 * {@link android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE} 113 * permission so that other applications can not abuse it. 114 */ 115 public static final String SERVICE_INTERFACE = 116 "android.service.wearable.WearableSensingService"; 117 118 // Timeout to prevent thread from waiting on the openFile future indefinitely. 119 private static final Duration OPEN_FILE_TIMEOUT = Duration.ofSeconds(5); 120 121 private final SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap = 122 new SparseArray<>(); 123 124 private IWearableSensingCallback mWearableSensingCallback; 125 126 @Nullable 127 @Override onBind(@onNull Intent intent)128 public final IBinder onBind(@NonNull Intent intent) { 129 if (SERVICE_INTERFACE.equals(intent.getAction())) { 130 return new IWearableSensingService.Stub() { 131 /** {@inheritDoc} */ 132 @Override 133 public void provideSecureConnection( 134 ParcelFileDescriptor secureWearableConnection, 135 IWearableSensingCallback wearableSensingCallback, 136 RemoteCallback callback) { 137 Objects.requireNonNull(secureWearableConnection); 138 if (wearableSensingCallback != null) { 139 mWearableSensingCallback = wearableSensingCallback; 140 } 141 Consumer<Integer> consumer = createWearableStatusConsumer(callback); 142 WearableSensingService.this.onSecureConnectionProvided( 143 secureWearableConnection, consumer); 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 public void provideDataStream( 149 ParcelFileDescriptor parcelFileDescriptor, 150 IWearableSensingCallback wearableSensingCallback, 151 RemoteCallback callback) { 152 Objects.requireNonNull(parcelFileDescriptor); 153 if (wearableSensingCallback != null) { 154 mWearableSensingCallback = wearableSensingCallback; 155 } 156 Consumer<Integer> consumer = createWearableStatusConsumer(callback); 157 WearableSensingService.this.onDataStreamProvided( 158 parcelFileDescriptor, consumer); 159 } 160 161 /** {@inheritDoc} */ 162 @Override 163 public void provideData( 164 PersistableBundle data, 165 SharedMemory sharedMemory, 166 RemoteCallback callback) { 167 Objects.requireNonNull(data); 168 Consumer<Integer> consumer = createWearableStatusConsumer(callback); 169 WearableSensingService.this.onDataProvided(data, sharedMemory, consumer); 170 } 171 172 /** {@inheritDoc} */ 173 @Override 174 public void registerDataRequestObserver( 175 int dataType, 176 RemoteCallback dataRequestCallback, 177 int dataRequestObserverId, 178 String packageName, 179 RemoteCallback statusCallback) { 180 Objects.requireNonNull(dataRequestCallback); 181 Objects.requireNonNull(statusCallback); 182 WearableSensingDataRequester dataRequester; 183 synchronized (mDataRequestObserverIdToRequesterMap) { 184 dataRequester = 185 mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId); 186 if (dataRequester == null) { 187 dataRequester = createDataRequester(dataRequestCallback); 188 mDataRequestObserverIdToRequesterMap.put( 189 dataRequestObserverId, dataRequester); 190 } 191 } 192 Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback); 193 WearableSensingService.this.onDataRequestObserverRegistered( 194 dataType, packageName, dataRequester, statusConsumer); 195 } 196 197 @Override 198 public void unregisterDataRequestObserver( 199 int dataType, 200 int dataRequestObserverId, 201 String packageName, 202 RemoteCallback statusCallback) { 203 WearableSensingDataRequester dataRequester; 204 synchronized (mDataRequestObserverIdToRequesterMap) { 205 dataRequester = 206 mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId); 207 if (dataRequester == null) { 208 Slog.w( 209 TAG, 210 "dataRequestObserverId not found, cannot unregister data" 211 + " request observer."); 212 return; 213 } 214 mDataRequestObserverIdToRequesterMap.remove(dataRequestObserverId); 215 } 216 Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback); 217 WearableSensingService.this.onDataRequestObserverUnregistered( 218 dataType, packageName, dataRequester, statusConsumer); 219 } 220 221 @Override 222 public void startHotwordRecognition( 223 RemoteCallback wearableHotwordCallback, RemoteCallback statusCallback) { 224 Consumer<HotwordAudioStream> hotwordAudioConsumer = 225 (hotwordAudioStream) -> { 226 Bundle bundle = new Bundle(); 227 bundle.putParcelable( 228 HOTWORD_AUDIO_STREAM_BUNDLE_KEY, hotwordAudioStream); 229 wearableHotwordCallback.sendResult(bundle); 230 }; 231 Consumer<Integer> statusConsumer = 232 response -> { 233 Bundle bundle = new Bundle(); 234 bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response); 235 statusCallback.sendResult(bundle); 236 }; 237 WearableSensingService.this.onStartHotwordRecognition( 238 hotwordAudioConsumer, statusConsumer); 239 } 240 241 /** {@inheritDoc} */ 242 @Override 243 public void stopHotwordRecognition(RemoteCallback statusCallback) { 244 Consumer<Integer> statusConsumer = 245 response -> { 246 Bundle bundle = new Bundle(); 247 bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response); 248 statusCallback.sendResult(bundle); 249 }; 250 WearableSensingService.this.onStopHotwordRecognition(statusConsumer); 251 } 252 253 /** {@inheritDoc} */ 254 @Override 255 public void onValidatedByHotwordDetectionService() { 256 WearableSensingService.this.onValidatedByHotwordDetectionService(); 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 public void stopActiveHotwordAudio() { 262 WearableSensingService.this.onStopHotwordAudioStream(); 263 } 264 265 /** {@inheritDoc} */ 266 @Override 267 public void startDetection( 268 @NonNull AmbientContextEventRequest request, 269 String packageName, 270 RemoteCallback detectionResultCallback, 271 RemoteCallback statusCallback) { 272 Objects.requireNonNull(request); 273 Objects.requireNonNull(packageName); 274 Objects.requireNonNull(detectionResultCallback); 275 Objects.requireNonNull(statusCallback); 276 Consumer<AmbientContextDetectionResult> detectionResultConsumer = 277 result -> { 278 Bundle bundle = new Bundle(); 279 bundle.putParcelable( 280 AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, 281 result); 282 detectionResultCallback.sendResult(bundle); 283 }; 284 Consumer<AmbientContextDetectionServiceStatus> statusConsumer = 285 status -> { 286 Bundle bundle = new Bundle(); 287 bundle.putParcelable( 288 AmbientContextDetectionServiceStatus 289 .STATUS_RESPONSE_BUNDLE_KEY, 290 status); 291 statusCallback.sendResult(bundle); 292 }; 293 WearableSensingService.this.onStartDetection( 294 request, packageName, statusConsumer, detectionResultConsumer); 295 Slog.d(TAG, "startDetection " + request); 296 } 297 298 /** {@inheritDoc} */ 299 @Override 300 public void stopDetection(String packageName) { 301 Objects.requireNonNull(packageName); 302 WearableSensingService.this.onStopDetection(packageName); 303 } 304 305 /** {@inheritDoc} */ 306 @Override 307 public void queryServiceStatus( 308 @AmbientContextEvent.EventCode int[] eventTypes, 309 String packageName, 310 RemoteCallback callback) { 311 Objects.requireNonNull(eventTypes); 312 Objects.requireNonNull(packageName); 313 Objects.requireNonNull(callback); 314 Consumer<AmbientContextDetectionServiceStatus> consumer = 315 response -> { 316 Bundle bundle = new Bundle(); 317 bundle.putParcelable( 318 AmbientContextDetectionServiceStatus 319 .STATUS_RESPONSE_BUNDLE_KEY, 320 response); 321 callback.sendResult(bundle); 322 }; 323 Integer[] events = intArrayToIntegerArray(eventTypes); 324 WearableSensingService.this.onQueryServiceStatus( 325 new HashSet<>(Arrays.asList(events)), packageName, consumer); 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 public void killProcess() { 331 Slog.d(TAG, "#killProcess"); 332 Process.killProcess(Process.myPid()); 333 } 334 }; 335 } 336 Slog.w(TAG, "Incorrect service interface, returning null."); 337 return null; 338 } 339 340 /** 341 * Called when a secure connection to the wearable is available. See {@link 342 * WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)} 343 * for details about the secure connection. 344 * 345 * <p>When the {@code secureWearableConnection} is closed, the system will send a {@link 346 * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by 347 * the caller of {@link WearableSensingManager#provideConnection(ParcelFileDescriptor, 348 * Executor, Consumer)}. 349 * 350 * <p>The implementing class should override this method. It should return an appropriate status 351 * code via {@code statusConsumer} after receiving the {@code secureWearableConnection}. 352 * 353 * @param secureWearableConnection The secure connection to the wearable. 354 * @param statusConsumer The consumer for the service status. 355 */ 356 @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) 357 @BinderThread 358 public void onSecureConnectionProvided( 359 @NonNull ParcelFileDescriptor secureWearableConnection, 360 @NonNull Consumer<Integer> statusConsumer) { 361 statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); 362 } 363 364 /** 365 * Called when a data stream to the wearable is provided. This data stream can be used to obtain 366 * data from a wearable device. It is up to the implementation to maintain the data stream and 367 * close the data stream when it is finished. 368 * 369 * @param parcelFileDescriptor The data stream to the wearable 370 * @param statusConsumer the consumer for the service status. 371 */ 372 @BinderThread 373 public abstract void onDataStreamProvided(@NonNull ParcelFileDescriptor parcelFileDescriptor, 374 @NonNull Consumer<Integer> statusConsumer); 375 376 /** 377 * Called when configurations and read-only data in a {@link PersistableBundle} can be used by 378 * the WearableSensingService and sends the result to the {@link Consumer} right after the call. 379 * It is dependent on the application to define the type of data to provide. This is used by 380 * applications that will also provide an implementation of an isolated WearableSensingService. 381 * If the data was provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be 382 * provided. 383 * 384 * @param data Application configuration data to provide to the {@link WearableSensingService}. 385 * PersistableBundle does not allow any remotable objects or other contents that can be used 386 * to communicate with other processes. 387 * @param sharedMemory The unrestricted data blob to provide to the {@link 388 * WearableSensingService}. Use this to provide the sensing models data or other such data 389 * to the trusted process. 390 * @param statusConsumer the consumer for the service status. 391 */ 392 @BinderThread 393 public abstract void onDataProvided( 394 @NonNull PersistableBundle data, 395 @Nullable SharedMemory sharedMemory, 396 @NonNull Consumer<Integer> statusConsumer); 397 398 /** 399 * Called when a data request observer is registered. Each request must not be larger than 400 * {@link WearableSensingDataRequest#getMaxRequestSize()}. In addition, at most {@link 401 * WearableSensingDataRequest#getRateLimit()} requests can be sent every rolling {@link 402 * WearableSensingDataRequest#getRateLimitWindowSize()}. Requests that are too large or too 403 * frequent will be dropped by the system. See {@link 404 * WearableSensingDataRequester#requestData(WearableSensingDataRequest, Consumer)} for details 405 * about the status code returned for each request. 406 * 407 * <p>The implementing class should override this method. After the data requester is received, 408 * it should send a {@link WearableSensingManager#STATUS_SUCCESS} status code to the {@code 409 * statusConsumer} unless it encounters an error condition described by a status code listed in 410 * {@link WearableSensingManager}, such as {@link 411 * WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, in which case it should return the 412 * corresponding status code. 413 * 414 * @param dataType The data type the observer is registered for. Values are defined by the 415 * application that implements this class. 416 * @param packageName The package name of the app that will receive the requests. 417 * @param dataRequester A handle to the observer registered. It can be used to request data of 418 * the specified data type. 419 * @param statusConsumer the consumer for the status of the data request observer registration. 420 * This is different from the status for each data request. 421 */ 422 @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) 423 @BinderThread 424 public void onDataRequestObserverRegistered( 425 int dataType, 426 @NonNull String packageName, 427 @NonNull WearableSensingDataRequester dataRequester, 428 @NonNull Consumer<Integer> statusConsumer) { 429 statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); 430 } 431 432 /** 433 * Called when a data request observer is unregistered. 434 * 435 * <p>The implementing class should override this method. It should send a {@link 436 * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it 437 * encounters an error condition described by a status code listed in {@link 438 * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, 439 * in which case it should return the corresponding status code. 440 * 441 * @param dataType The data type the observer is for. 442 * @param packageName The package name of the app that will receive the requests sent to the 443 * dataRequester. 444 * @param dataRequester A handle to the observer to be unregistered. It is the exact same 445 * instance provided in a previous {@link #onDataRequestObserverRegistered(int, String, 446 * WearableSensingDataRequester, Consumer)} invocation. 447 * @param statusConsumer the consumer for the status of the data request observer 448 * unregistration. This is different from the status for each data request. 449 */ 450 @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) 451 @BinderThread 452 public void onDataRequestObserverUnregistered( 453 int dataType, 454 @NonNull String packageName, 455 @NonNull WearableSensingDataRequester dataRequester, 456 @NonNull Consumer<Integer> statusConsumer) { 457 statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); 458 } 459 460 /** 461 * Called when the wearable is requested to start hotword recognition. 462 * 463 * <p>This method is expected to be overridden by a derived class. The implementation should 464 * store the {@code hotwordAudioConsumer} and send it the audio data when first-stage hotword is 465 * detected from the wearable. It should also send a {@link 466 * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it 467 * encounters an error condition described by a status code listed in {@link 468 * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, 469 * in which case it should return the corresponding status code. 470 * 471 * <p>The implementation should also store the {@code statusConsumer}. If the wearable stops 472 * listening for hotword for any reason other than {@link #onStopHotwordRecognition(Consumer)} 473 * being invoked, it should send an appropriate status code listed in {@link 474 * WearableSensingManager} to {@code statusConsumer}. If the error condition cannot be described 475 * by any of those status codes, it should send a {@link WearableSensingManager#STATUS_UNKNOWN}. 476 * 477 * <p>If this method is called again, the implementation should use the new {@code 478 * hotwordAudioConsumer} and discard any previous ones it received. 479 * 480 * <p>At this time, the {@code timestamp} field in the {@link HotwordAudioStream} is not used 481 * and will be discarded by the system. 482 * 483 * @param hotwordAudioConsumer The consumer for the wearable hotword audio data. 484 * @param statusConsumer The consumer for the service status. 485 */ 486 @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) 487 @BinderThread 488 public void onStartHotwordRecognition( 489 @NonNull Consumer<HotwordAudioStream> hotwordAudioConsumer, 490 @NonNull Consumer<Integer> statusConsumer) { 491 if (Flags.enableUnsupportedOperationStatusCode()) { 492 statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); 493 } 494 } 495 496 /** 497 * Called when the wearable is requested to stop hotword recognition. 498 * 499 * <p>This method is expected to be overridden by a derived class. It should send a {@link 500 * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it 501 * encounters an error condition described by a status code listed in {@link 502 * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, 503 * in which case it should return the corresponding status code. 504 * 505 * @param statusConsumer The consumer for the service status. 506 */ 507 @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) 508 @BinderThread 509 public void onStopHotwordRecognition(@NonNull Consumer<Integer> statusConsumer) { 510 if (Flags.enableUnsupportedOperationStatusCode()) { 511 statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); 512 } 513 } 514 515 /** 516 * Called when hotword audio data sent to the {@code hotwordAudioConsumer} in {@link 517 * #onStartHotwordRecognition(Consumer, Consumer)} is accepted by the 518 * {@link android.service.voice.HotwordDetectionService} as valid hotword. 519 * 520 * <p>After the implementation of this class sends the hotword audio data to the {@code 521 * hotwordAudioConsumer} in {@link #onStartHotwordRecognition(Consumer, 522 * Consumer)}, the system will forward the data into {@link 523 * android.service.voice.HotwordDetectionService} (which runs in an isolated process) for 524 * second-stage hotword detection. If accepted as valid hotword there, this method will be 525 * called, and then the system will send the data to the currently active {@link 526 * android.service.voice.AlwaysOnHotwordDetector} (which may not run in an isolated process). 527 * 528 * <p>This method is expected to be overridden by a derived class. The implementation must 529 * request the wearable to turn on the microphone indicator to notify the user that audio data 530 * is being used outside of the isolated environment. 531 */ 532 @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) 533 @BinderThread 534 public void onValidatedByHotwordDetectionService() {} 535 536 /** 537 * Called when the currently active hotword audio stream is no longer needed. 538 * 539 * <p>This method can be called as a result of hotword rejection by {@link 540 * android.service.voice.HotwordDetectionService}, or the {@link 541 * android.service.voice.AlwaysOnHotwordDetector} closing the data stream it received, or a 542 * non-recoverable error occurred before the data reaches the {@link 543 * android.service.voice.HotwordDetectionService} or the {@link 544 * android.service.voice.AlwaysOnHotwordDetector}. 545 * 546 * <p>This method is expected to be overridden by a derived class. The implementation should 547 * stop sending hotword audio data to the {@code hotwordAudioConsumer} in {@link 548 * #onStartHotwordRecognition(Consumer, Consumer)} 549 */ 550 @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) 551 @BinderThread 552 public void onStopHotwordAudioStream() {} 553 554 /** 555 * Called when a client app requests starting detection of the events in the request. The 556 * implementation should keep track of whether the user has explicitly consented to detecting 557 * the events using on-going ambient sensor (e.g. microphone), and agreed to share the 558 * detection results with this client app. If the user has not consented, the detection 559 * should not start, and the statusConsumer should get a response with STATUS_ACCESS_DENIED. 560 * If the user has made the consent and the underlying services are available, the 561 * implementation should start detection and provide detected events to the 562 * detectionResultConsumer. If the type of event needs immediate attention, the implementation 563 * should send result as soon as detected. Otherwise, the implementation can batch response. 564 * The ongoing detection will keep running, until onStopDetection is called. If there were 565 * previously requested detections from the same package, regardless of the type of events in 566 * the request, the previous request will be replaced with the new request and pending events 567 * are discarded. 568 * 569 * @param request The request with events to detect. 570 * @param packageName the requesting app's package name 571 * @param statusConsumer the consumer for the service status. 572 * @param detectionResultConsumer the consumer for the detected event 573 */ 574 @BinderThread 575 public abstract void onStartDetection(@NonNull AmbientContextEventRequest request, 576 @NonNull String packageName, 577 @NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer, 578 @NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer); 579 580 /** 581 * Stops detection of the events. Events that are not being detected will be ignored. 582 * 583 * @param packageName stops detection for the given package. 584 */ 585 public abstract void onStopDetection(@NonNull String packageName); 586 587 /** 588 * Called when a query for the detection status occurs. The implementation should check 589 * the detection status of the requested events for the package, and provide results in a 590 * {@link AmbientContextDetectionServiceStatus} for the consumer. 591 * 592 * @param eventTypes The events to check for status. 593 * @param packageName the requesting app's package name 594 * @param consumer the consumer for the query results 595 */ 596 @BinderThread 597 public abstract void onQueryServiceStatus(@NonNull Set<Integer> eventTypes, 598 @NonNull String packageName, 599 @NonNull Consumer<AmbientContextDetectionServiceStatus> consumer); 600 601 /** 602 * Overrides {@link Context#openFileInput} to read files with the given {@code fileName} under 603 * the internal app storage of the APK providing the implementation for this class. {@link 604 * Context#getFilesDir()} will be added as a prefix to the provided {@code fileName}. 605 * 606 * <p>This method is only functional after {@link 607 * #onSecureConnectionProvided(ParcelFileDescriptor, Consumer)} or {@link 608 * #onDataStreamProvided(ParcelFileDescriptor, Consumer)} has been called as a result of a 609 * process owned by the same APK calling {@link 610 * WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)} or {@link 611 * WearableSensingManager#provideDataStream(ParcelFileDescriptor, Executor, Consumer)}. 612 * Otherwise, it will throw an {@link IllegalStateException}. This is because this method 613 * proxies the file read via that process. Also, the APK needs to have a targetSdkVersion of 35 614 * or newer. 615 * 616 * @param fileName Relative path of a file under {@link Context#getFilesDir()}. 617 * @throws IllegalStateException if the above condition is not satisfied. 618 * @throws FileNotFoundException if the file does not exist or cannot be opened, or an error 619 * occurred during the RPC to proxy the file read via a non-isolated process. 620 */ 621 // SuppressLint is needed because the parent Context class does not specify the nullability of 622 // the parameter filename. If we remove the @NonNull annotation, the linter will complain about 623 // MissingNullability 624 @Override 625 public @NonNull FileInputStream openFileInput( 626 @SuppressLint("InvalidNullabilityOverride") @NonNull String fileName) 627 throws FileNotFoundException { 628 if (fileName == null) { 629 throw new IllegalArgumentException("filename cannot be null"); 630 } 631 try { 632 if (mWearableSensingCallback == null) { 633 throw new IllegalStateException( 634 "Cannot open file from WearableSensingService. WearableSensingCallback is" 635 + " not available."); 636 } 637 AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>(); 638 mWearableSensingCallback.openFile(fileName, future); 639 ParcelFileDescriptor pfd = 640 future.get(OPEN_FILE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS); 641 if (pfd == null) { 642 throw new FileNotFoundException( 643 TextUtils.formatSimple( 644 "File %s not found or unable to be opened in read-only mode.", 645 fileName)); 646 } 647 return new FileInputStream(pfd.getFileDescriptor()); 648 } catch (RemoteException | ExecutionException | TimeoutException e) { 649 throw (FileNotFoundException) 650 new FileNotFoundException("Cannot open file due to remote service failure") 651 .initCause(e); 652 } catch (InterruptedException e) { 653 Thread.currentThread().interrupt(); 654 throw (FileNotFoundException) 655 new FileNotFoundException("Interrupted when opening a file.").initCause(e); 656 } 657 } 658 659 @NonNull 660 private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) { 661 Integer[] intArray = new Integer[integerSet.length]; 662 int i = 0; 663 for (Integer type : integerSet) { 664 intArray[i++] = type; 665 } 666 return intArray; 667 } 668 669 private static WearableSensingDataRequester createDataRequester( 670 RemoteCallback dataRequestCallback) { 671 return (request, requestStatusConsumer) -> { 672 Bundle bundle = new Bundle(); 673 bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request); 674 RemoteCallback requestStatusCallback = 675 new RemoteCallback( 676 requestStatusBundle -> { 677 requestStatusConsumer.accept( 678 requestStatusBundle.getInt( 679 WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY)); 680 }); 681 bundle.putParcelable( 682 WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY, 683 requestStatusCallback); 684 dataRequestCallback.sendResult(bundle); 685 }; 686 } 687 688 @NonNull 689 private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) { 690 return response -> { 691 Bundle bundle = new Bundle(); 692 bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response); 693 statusCallback.sendResult(bundle); 694 }; 695 } 696 697 698 } 699