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