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