File: AmbientContextManager.java

package info (click to toggle)
android-platform-frameworks-base 1%3A14~beta1-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 326,096 kB
  • sloc: java: 2,032,373; xml: 343,016; cpp: 304,183; python: 3,683; ansic: 2,090; sh: 1,871; makefile: 120; sed: 19
file content (377 lines) | stat: -rw-r--r-- 15,910 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.ambientcontext;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.RemoteCallback;
import android.os.RemoteException;

import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * Allows granted apps to register for event types defined in {@link AmbientContextEvent}.
 * After registration, the app receives a Consumer callback of the service status.
 * If it is {@link STATUS_SUCCESSFUL}, when the requested events are detected, the provided
 * {@link PendingIntent} callback will receive the list of detected {@link AmbientContextEvent}s.
 * If it is {@link STATUS_ACCESS_DENIED}, the app can call {@link #startConsentActivity}
 * to load the consent screen.
 *
 * @hide
 */
@SystemApi
@SystemService(Context.AMBIENT_CONTEXT_SERVICE)
public final class AmbientContextManager {
    /**
     * The bundle key for the service status query result, used in
     * {@code RemoteCallback#sendResult}.
     *
     * @hide
     */
    public static final String STATUS_RESPONSE_BUNDLE_KEY =
            "android.app.ambientcontext.AmbientContextStatusBundleKey";

    /**
     * The key of an intent extra indicating a list of detected {@link AmbientContextEvent}s.
     * The intent is sent to the app in the app's registered {@link PendingIntent}.
     */
    public static final String EXTRA_AMBIENT_CONTEXT_EVENTS =
            "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS";

    /**
     * An unknown status.
     */
    public static final int STATUS_UNKNOWN = 0;

    /**
     * The value of the status code that indicates success.
     */
    public static final int STATUS_SUCCESS = 1;

    /**
     * The value of the status code that indicates one or more of the
     * requested events are not supported.
     */
    public static final int STATUS_NOT_SUPPORTED = 2;

    /**
     * The value of the status code that indicates service not available.
     */
    public static final int STATUS_SERVICE_UNAVAILABLE = 3;

    /**
     * The value of the status code that microphone is disabled.
     */
    public static final int STATUS_MICROPHONE_DISABLED = 4;

    /**
     * The value of the status code that the app is not granted access.
     */
    public static final int STATUS_ACCESS_DENIED = 5;

    /** @hide */
    @IntDef(prefix = { "STATUS_" }, value = {
            STATUS_UNKNOWN,
            STATUS_SUCCESS,
            STATUS_NOT_SUPPORTED,
            STATUS_SERVICE_UNAVAILABLE,
            STATUS_MICROPHONE_DISABLED,
            STATUS_ACCESS_DENIED
    }) public @interface StatusCode {}

    /**
     * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent.
     *
     * @param intent received from the PendingIntent callback
     *
     * @return the list of events, or an empty list if the intent doesn't have such events.
     */
    @NonNull public static List<AmbientContextEvent> getEventsFromIntent(@NonNull Intent intent) {
        if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) {
            return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS);
        } else {
            return new ArrayList<>();
        }
    }

    private final Context mContext;
    private final IAmbientContextManager mService;

    /**
     * {@hide}
     */
    public AmbientContextManager(Context context, IAmbientContextManager service) {
        mContext = context;
        mService = service;
    }

    /**
     * Queries the {@link AmbientContextEvent} service status for the calling package, and
     * sends the result to the {@link Consumer} right after the call. This is used by foreground
     * apps to check whether the requested events are enabled for detection on the device.
     * If all events are enabled for detection, the response has
     * {@link AmbientContextManager#STATUS_SUCCESS}.
     * If any of the events are not consented by user, the response has
     * {@link AmbientContextManager#STATUS_ACCESS_DENIED}, and the app can
     * call {@link #startConsentActivity} to redirect the user to the consent screen.
     * <p />
     *
     * Example:
     *
     * <pre><code>
     *   Set<Integer> eventTypes = new HashSet<>();
     *   eventTypes.add(AmbientContextEvent.EVENT_COUGH);
     *   eventTypes.add(AmbientContextEvent.EVENT_SNORE);
     *
     *   // Create Consumer
     *   Consumer<Integer> statusConsumer = status -> {
     *     int status = status.getStatusCode();
     *     if (status == AmbientContextManager.STATUS_SUCCESS) {
     *       // Show user it's enabled
     *     } else if (status == AmbientContextManager.STATUS_ACCESS_DENIED) {
     *       // Send user to grant access
     *       startConsentActivity(eventTypes);
     *     }
     *   };
     *
     *   // Query status
     *   AmbientContextManager ambientContextManager =
     *       context.getSystemService(AmbientContextManager.class);
     *   ambientContextManager.queryAmbientContextStatus(eventTypes, executor, statusConsumer);
     * </code></pre>
     *
     * @param eventTypes The set of event codes to check status on.
     * @param executor Executor on which to run the consumer callback.
     * @param consumer The consumer that handles the status code.
     */
    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
    public void queryAmbientContextServiceStatus(
            @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull @StatusCode Consumer<Integer> consumer) {
        try {
            RemoteCallback callback = new RemoteCallback(result -> {
                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
                final long identity = Binder.clearCallingIdentity();
                try {
                    executor.execute(() -> consumer.accept(status));
                } finally {
                    Binder.restoreCallingIdentity(identity);
                }
            });
            mService.queryServiceStatus(integerSetToIntArray(eventTypes),
                    mContext.getOpPackageName(), callback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests the consent data host to open an activity that allows users to modify consent.
     *
     * @param eventTypes The set of event codes to be consented.
     */
    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
    public void startConsentActivity(
            @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes) {
        try {
            mService.startConsentActivity(
                    integerSetToIntArray(eventTypes), mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) {
        int[] intArray = new int[integerSet.size()];
        int i = 0;
        for (Integer type : integerSet) {
            intArray[i++] = type;
        }
        return intArray;
    }

    /**
     * Allows app to register as a {@link AmbientContextEvent} observer. The
     * observer receives a callback on the provided {@link PendingIntent} when the requested
     * event is detected. Registering another observer from the same package that has already been
     * registered will override the previous observer.
     * <p />
     *
     * Example:
     *
     * <pre><code>
     *   // Create request
     *   AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
     *       .addEventType(AmbientContextEvent.EVENT_COUGH)
     *       .addEventType(AmbientContextEvent.EVENT_SNORE)
     *       .build();
     *
     *   // Create PendingIntent for delivering detection results to my receiver
     *   Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class)
     *       .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
     *   PendingIntent pendingIntent =
     *       PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
     *
     *   // Create Consumer of service status
     *   Consumer<Integer> statusConsumer = status -> {
     *       if (status == AmbientContextManager.STATUS_ACCESS_DENIED) {
     *         // User did not consent event detection. See #queryAmbientContextServiceStatus and
     *         // #startConsentActivity
     *       }
     *   };
     *
     *   // Register as observer
     *   AmbientContextManager ambientContextManager =
     *       context.getSystemService(AmbientContextManager.class);
     *   ambientContextManager.registerObserver(request, pendingIntent, executor, statusConsumer);
     *
     *   // Handle the list of {@link AmbientContextEvent}s in your receiver
     *   {@literal @}Override
     *   protected void onReceive(Context context, Intent intent) {
     *     List<AmbientContextEvent> events = AmbientContextManager.getEventsFromIntent(intent);
     *     if (!events.isEmpty()) {
     *       // Do something useful with the events.
     *     }
     *   }
     * </code></pre>
     *
     * @param request The request with events to observe.
     * @param resultPendingIntent A mutable {@link PendingIntent} that will be dispatched after the
     *                            requested events are detected.
     * @param executor Executor on which to run the consumer callback.
     * @param statusConsumer A consumer that handles the status code, which is returned
     *                      right after the call.
     */
    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
    public void registerObserver(
            @NonNull AmbientContextEventRequest request,
            @NonNull PendingIntent resultPendingIntent,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
        Preconditions.checkArgument(!resultPendingIntent.isImmutable());
        try {
            RemoteCallback callback = new RemoteCallback(result -> {
                int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
                final long identity = Binder.clearCallingIdentity();
                try {
                    executor.execute(() -> statusConsumer.accept(statusCode));
                } finally {
                    Binder.restoreCallingIdentity(identity);
                }
            });
            mService.registerObserver(request, resultPendingIntent, callback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Allows app to register as a {@link AmbientContextEvent} observer. Same as {@link
     * #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
     * but use {@link AmbientContextCallback} instead of {@link PendingIntent} as a callback on
     * detected events.
     * Registering another observer from the same package that has already been
     * registered will override the previous observer. If the same app previously calls
     * {@link #registerObserver(AmbientContextEventRequest, AmbientContextCallback, Executor)},
     * and now calls
     * {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
     * the previous observer will be replaced with the new observer with the PendingIntent callback.
     * Or vice versa.
     *
     * When the registration completes, a status will be returned to client through
     * {@link AmbientContextCallback#onRegistrationComplete(int)}.
     * If the AmbientContextManager service is not enabled yet, or the underlying detection service
     * is not running yet, {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} will be
     * returned, and the detection won't be really started.
     * If the underlying detection service feature is not enabled, or the requested event type is
     * not enabled yet, {@link AmbientContextManager#STATUS_NOT_SUPPORTED} will be returned, and the
     * detection won't be really started.
     * If there is no user consent,  {@link AmbientContextManager#STATUS_ACCESS_DENIED} will be
     * returned, and the detection won't be really started.
     * Otherwise, it will try to start the detection. And if it starts successfully, it will return
     * {@link AmbientContextManager#STATUS_SUCCESS}. If it fails to start the detection, then
     * it will return {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE}
     * After registerObserver succeeds and when the service detects an event, the service will
     * trigger {@link AmbientContextCallback#onEvents(List)}.
     *
     * @hide
     */
    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
    public void registerObserver(
            @NonNull AmbientContextEventRequest request,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull AmbientContextCallback ambientContextCallback) {
        try {
            IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
                @Override
                public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
                    final long identity = Binder.clearCallingIdentity();
                    try {
                        executor.execute(() -> ambientContextCallback.onEvents(events));
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }

                @Override
                public void onRegistrationComplete(int statusCode) throws RemoteException {
                    final long identity = Binder.clearCallingIdentity();
                    try {
                        executor.execute(
                                () -> ambientContextCallback.onRegistrationComplete(statusCode));
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            };

            mService.registerObserverWithCallback(request, mContext.getPackageName(), observer);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an
     * observer that was already unregistered or never registered will have no effect.
     */
    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
    public void unregisterObserver() {
        try {
            mService.unregisterObserver(mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}