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.app.ambientcontext; 18 19 import android.Manifest; 20 import android.annotation.CallbackExecutor; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.annotation.SystemService; 26 import android.app.PendingIntent; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.os.Binder; 30 import android.os.RemoteCallback; 31 import android.os.RemoteException; 32 33 import com.android.internal.util.Preconditions; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Set; 40 import java.util.concurrent.Executor; 41 import java.util.function.Consumer; 42 43 /** 44 * Allows granted apps to register for event types defined in {@link AmbientContextEvent}. 45 * After registration, the app receives a Consumer callback of the service status. 46 * If it is {@link STATUS_SUCCESSFUL}, when the requested events are detected, the provided 47 * {@link PendingIntent} callback will receive the list of detected {@link AmbientContextEvent}s. 48 * If it is {@link STATUS_ACCESS_DENIED}, the app can call {@link #startConsentActivity} 49 * to load the consent screen. 50 * 51 * @hide 52 */ 53 @SystemApi 54 @SystemService(Context.AMBIENT_CONTEXT_SERVICE) 55 public final class AmbientContextManager { 56 /** 57 * The bundle key for the service status query result, used in 58 * {@code RemoteCallback#sendResult}. 59 * 60 * @hide 61 */ 62 public static final String STATUS_RESPONSE_BUNDLE_KEY = 63 "android.app.ambientcontext.AmbientContextStatusBundleKey"; 64 65 /** 66 * The key of an intent extra indicating a list of detected {@link AmbientContextEvent}s. 67 * The intent is sent to the app in the app's registered {@link PendingIntent}. 68 */ 69 public static final String EXTRA_AMBIENT_CONTEXT_EVENTS = 70 "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS"; 71 72 /** 73 * An unknown status. 74 */ 75 public static final int STATUS_UNKNOWN = 0; 76 77 /** 78 * The value of the status code that indicates success. 79 */ 80 public static final int STATUS_SUCCESS = 1; 81 82 /** 83 * The value of the status code that indicates one or more of the 84 * requested events are not supported. 85 */ 86 public static final int STATUS_NOT_SUPPORTED = 2; 87 88 /** 89 * The value of the status code that indicates service not available. 90 */ 91 public static final int STATUS_SERVICE_UNAVAILABLE = 3; 92 93 /** 94 * The value of the status code that microphone is disabled. 95 */ 96 public static final int STATUS_MICROPHONE_DISABLED = 4; 97 98 /** 99 * The value of the status code that the app is not granted access. 100 */ 101 public static final int STATUS_ACCESS_DENIED = 5; 102 103 /** @hide */ 104 @IntDef(prefix = { "STATUS_" }, value = { 105 STATUS_UNKNOWN, 106 STATUS_SUCCESS, 107 STATUS_NOT_SUPPORTED, 108 STATUS_SERVICE_UNAVAILABLE, 109 STATUS_MICROPHONE_DISABLED, 110 STATUS_ACCESS_DENIED 111 }) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface StatusCode {} 114 115 /** 116 * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent. 117 * 118 * @param intent received from the PendingIntent callback 119 * 120 * @return the list of events, or an empty list if the intent doesn't have such events. 121 */ getEventsFromIntent(@onNull Intent intent)122 @NonNull public static List<AmbientContextEvent> getEventsFromIntent(@NonNull Intent intent) { 123 if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) { 124 return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS, 125 android.app.ambientcontext.AmbientContextEvent.class); 126 } else { 127 return new ArrayList<>(); 128 } 129 } 130 131 private final Context mContext; 132 private final IAmbientContextManager mService; 133 134 /** 135 * {@hide} 136 */ AmbientContextManager(Context context, IAmbientContextManager service)137 public AmbientContextManager(Context context, IAmbientContextManager service) { 138 mContext = context; 139 mService = service; 140 } 141 142 /** 143 * Queries the {@link AmbientContextEvent} service status for the calling package, and 144 * sends the result to the {@link Consumer} right after the call. This is used by foreground 145 * apps to check whether the requested events are enabled for detection on the device. 146 * If all events are enabled for detection, the response has 147 * {@link AmbientContextManager#STATUS_SUCCESS}. 148 * If any of the events are not consented by user, the response has 149 * {@link AmbientContextManager#STATUS_ACCESS_DENIED}, and the app can 150 * call {@link #startConsentActivity} to redirect the user to the consent screen. 151 * If the AmbientContextRequest contains a mixed set of events containing values both greater 152 * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request 153 * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. 154 * <p /> 155 * 156 * Example: 157 * 158 * <pre><code> 159 * Set<Integer> eventTypes = new HashSet<>(); 160 * eventTypes.add(AmbientContextEvent.EVENT_COUGH); 161 * eventTypes.add(AmbientContextEvent.EVENT_SNORE); 162 * 163 * // Create Consumer 164 * Consumer<Integer> statusConsumer = status -> { 165 * int status = status.getStatusCode(); 166 * if (status == AmbientContextManager.STATUS_SUCCESS) { 167 * // Show user it's enabled 168 * } else if (status == AmbientContextManager.STATUS_ACCESS_DENIED) { 169 * // Send user to grant access 170 * startConsentActivity(eventTypes); 171 * } 172 * }; 173 * 174 * // Query status 175 * AmbientContextManager ambientContextManager = 176 * context.getSystemService(AmbientContextManager.class); 177 * ambientContextManager.queryAmbientContextStatus(eventTypes, executor, statusConsumer); 178 * </code></pre> 179 * 180 * @param eventTypes The set of event codes to check status on. 181 * @param executor Executor on which to run the consumer callback. 182 * @param consumer The consumer that handles the status code. 183 */ 184 @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) queryAmbientContextServiceStatus( @onNull @mbientContextEvent.EventCode Set<Integer> eventTypes, @NonNull @CallbackExecutor Executor executor, @NonNull @StatusCode Consumer<Integer> consumer)185 public void queryAmbientContextServiceStatus( 186 @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes, 187 @NonNull @CallbackExecutor Executor executor, 188 @NonNull @StatusCode Consumer<Integer> consumer) { 189 try { 190 RemoteCallback callback = new RemoteCallback(result -> { 191 int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); 192 final long identity = Binder.clearCallingIdentity(); 193 try { 194 executor.execute(() -> consumer.accept(status)); 195 } finally { 196 Binder.restoreCallingIdentity(identity); 197 } 198 }); 199 mService.queryServiceStatus(integerSetToIntArray(eventTypes), 200 mContext.getOpPackageName(), callback); 201 } catch (RemoteException e) { 202 throw e.rethrowFromSystemServer(); 203 } 204 } 205 206 /** 207 * Requests the consent data host to open an activity that allows users to modify consent. 208 * If the eventTypes contains a mixed set of events containing values both greater than and less 209 * than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request will be rejected 210 * with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. 211 * 212 * @param eventTypes The set of event codes to be consented. 213 */ 214 @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) startConsentActivity( @onNull @mbientContextEvent.EventCode Set<Integer> eventTypes)215 public void startConsentActivity( 216 @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes) { 217 try { 218 mService.startConsentActivity( 219 integerSetToIntArray(eventTypes), mContext.getOpPackageName()); 220 } catch (RemoteException e) { 221 throw e.rethrowFromSystemServer(); 222 } 223 } 224 225 @NonNull integerSetToIntArray(@onNull Set<Integer> integerSet)226 private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) { 227 int[] intArray = new int[integerSet.size()]; 228 int i = 0; 229 for (Integer type : integerSet) { 230 intArray[i++] = type; 231 } 232 return intArray; 233 } 234 235 /** 236 * Allows app to register as a {@link AmbientContextEvent} observer. The 237 * observer receives a callback on the provided {@link PendingIntent} when the requested 238 * event is detected. Registering another observer from the same package that has already been 239 * registered will override the previous observer. 240 * If the AmbientContextRequest contains a mixed set of events containing values both greater 241 * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request 242 * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. 243 * <p /> 244 * 245 * Example: 246 * 247 * <pre><code> 248 * // Create request 249 * AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() 250 * .addEventType(AmbientContextEvent.EVENT_COUGH) 251 * .addEventType(AmbientContextEvent.EVENT_SNORE) 252 * .build(); 253 * 254 * // Create PendingIntent for delivering detection results to my receiver 255 * Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class) 256 * .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 257 * PendingIntent pendingIntent = 258 * PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 259 * 260 * // Create Consumer of service status 261 * Consumer<Integer> statusConsumer = status -> { 262 * if (status == AmbientContextManager.STATUS_ACCESS_DENIED) { 263 * // User did not consent event detection. See #queryAmbientContextServiceStatus and 264 * // #startConsentActivity 265 * } 266 * }; 267 * 268 * // Register as observer 269 * AmbientContextManager ambientContextManager = 270 * context.getSystemService(AmbientContextManager.class); 271 * ambientContextManager.registerObserver(request, pendingIntent, executor, statusConsumer); 272 * 273 * // Handle the list of {@link AmbientContextEvent}s in your receiver 274 * {@literal @}Override 275 * protected void onReceive(Context context, Intent intent) { 276 * List<AmbientContextEvent> events = AmbientContextManager.getEventsFromIntent(intent); 277 * if (!events.isEmpty()) { 278 * // Do something useful with the events. 279 * } 280 * } 281 * </code></pre> 282 * 283 * @param request The request with events to observe. 284 * @param resultPendingIntent A mutable {@link PendingIntent} that will be dispatched after the 285 * requested events are detected. 286 * @param executor Executor on which to run the consumer callback. 287 * @param statusConsumer A consumer that handles the status code, which is returned 288 * right after the call. 289 */ 290 @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) registerObserver( @onNull AmbientContextEventRequest request, @NonNull PendingIntent resultPendingIntent, @NonNull @CallbackExecutor Executor executor, @NonNull @StatusCode Consumer<Integer> statusConsumer)291 public void registerObserver( 292 @NonNull AmbientContextEventRequest request, 293 @NonNull PendingIntent resultPendingIntent, 294 @NonNull @CallbackExecutor Executor executor, 295 @NonNull @StatusCode Consumer<Integer> statusConsumer) { 296 Preconditions.checkArgument(!resultPendingIntent.isImmutable()); 297 try { 298 RemoteCallback callback = new RemoteCallback(result -> { 299 int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); 300 final long identity = Binder.clearCallingIdentity(); 301 try { 302 executor.execute(() -> statusConsumer.accept(statusCode)); 303 } finally { 304 Binder.restoreCallingIdentity(identity); 305 } 306 }); 307 mService.registerObserver(request, resultPendingIntent, callback); 308 } catch (RemoteException e) { 309 throw e.rethrowFromSystemServer(); 310 } 311 } 312 313 /** 314 * Allows app to register as a {@link AmbientContextEvent} observer. Same as {@link 315 * #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)}, 316 * but use {@link AmbientContextCallback} instead of {@link PendingIntent} as a callback on 317 * detected events. 318 * Registering another observer from the same package that has already been 319 * registered will override the previous observer. If the same app previously calls 320 * {@link #registerObserver(AmbientContextEventRequest, AmbientContextCallback, Executor)}, 321 * and now calls 322 * {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)}, 323 * the previous observer will be replaced with the new observer with the PendingIntent callback. 324 * Or vice versa. 325 * If the AmbientContextRequest contains a mixed set of events containing values both greater 326 * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request 327 * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. 328 * 329 * When the registration completes, a status will be returned to client through 330 * {@link AmbientContextCallback#onRegistrationComplete(int)}. 331 * If the AmbientContextManager service is not enabled yet, or the underlying detection service 332 * is not running yet, {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} will be 333 * returned, and the detection won't be really started. 334 * If the underlying detection service feature is not enabled, or the requested event type is 335 * not enabled yet, {@link AmbientContextManager#STATUS_NOT_SUPPORTED} will be returned, and the 336 * detection won't be really started. 337 * If there is no user consent, {@link AmbientContextManager#STATUS_ACCESS_DENIED} will be 338 * returned, and the detection won't be really started. 339 * Otherwise, it will try to start the detection. And if it starts successfully, it will return 340 * {@link AmbientContextManager#STATUS_SUCCESS}. If it fails to start the detection, then 341 * it will return {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} 342 * After registerObserver succeeds and when the service detects an event, the service will 343 * trigger {@link AmbientContextCallback#onEvents(List)}. 344 * 345 * @hide 346 */ 347 @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) registerObserver( @onNull AmbientContextEventRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull AmbientContextCallback ambientContextCallback)348 public void registerObserver( 349 @NonNull AmbientContextEventRequest request, 350 @NonNull @CallbackExecutor Executor executor, 351 @NonNull AmbientContextCallback ambientContextCallback) { 352 try { 353 IAmbientContextObserver observer = new IAmbientContextObserver.Stub() { 354 @Override 355 public void onEvents(List<AmbientContextEvent> events) throws RemoteException { 356 final long identity = Binder.clearCallingIdentity(); 357 try { 358 executor.execute(() -> ambientContextCallback.onEvents(events)); 359 } finally { 360 Binder.restoreCallingIdentity(identity); 361 } 362 } 363 364 @Override 365 public void onRegistrationComplete(int statusCode) throws RemoteException { 366 final long identity = Binder.clearCallingIdentity(); 367 try { 368 executor.execute( 369 () -> ambientContextCallback.onRegistrationComplete(statusCode)); 370 } finally { 371 Binder.restoreCallingIdentity(identity); 372 } 373 } 374 }; 375 376 mService.registerObserverWithCallback(request, mContext.getPackageName(), observer); 377 } catch (RemoteException e) { 378 throw e.rethrowFromSystemServer(); 379 } 380 } 381 382 /** 383 * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an 384 * observer that was already unregistered or never registered will have no effect. 385 */ 386 @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) unregisterObserver()387 public void unregisterObserver() { 388 try { 389 mService.unregisterObserver(mContext.getOpPackageName()); 390 } catch (RemoteException e) { 391 throw e.rethrowFromSystemServer(); 392 } 393 } 394 } 395