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