diff options
| author | Kathy Chen <kxchen@google.com> | 2022-02-09 19:05:21 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-02-09 19:05:21 +0000 |
| commit | 4ff2a4e611586fc7f5c9b2458bc783174fcd16d8 (patch) | |
| tree | 4b313a50815255eb87ade2aeb2a415ccdad54658 /core/java | |
| parent | d8ef185f330b3417dff29d09fbd86305f0ce50f1 (diff) | |
| parent | f8f605c44e40526afd94adec590f7418da7d097f (diff) | |
Merge "Add query API for apps to check the event status. The detection service sends back the response with a status. Add client API for apps to start the consent activity."
Diffstat (limited to 'core/java')
12 files changed, 723 insertions, 397 deletions
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f5f2fe0d0292..eeb4705fc7d3 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -25,7 +25,7 @@ import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.ambientcontext.AmbientContextManager; -import android.app.ambientcontext.IAmbientContextEventObserver; +import android.app.ambientcontext.IAmbientContextManager; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.cloudsearch.CloudSearchManager; @@ -1542,8 +1542,8 @@ public final class SystemServiceRegistry { throws ServiceNotFoundException { IBinder iBinder = ServiceManager.getServiceOrThrow( Context.AMBIENT_CONTEXT_SERVICE); - IAmbientContextEventObserver manager = - IAmbientContextEventObserver.Stub.asInterface(iBinder); + IAmbientContextManager manager = + IAmbientContextManager.Stub.asInterface(iBinder); return new AmbientContextManager(ctx.getOuterContext(), manager); }}); diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java index 82b16a2db0ce..0557acb1130d 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java @@ -23,6 +23,9 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.util.ArraySet; +import com.android.internal.util.AnnotationValidations; +import com.android.internal.util.Preconditions; + import java.util.HashSet; import java.util.Set; @@ -36,15 +39,17 @@ public final class AmbientContextEventRequest implements Parcelable { @NonNull private final Set<Integer> mEventTypes; @NonNull private final PersistableBundle mOptions; - AmbientContextEventRequest( + private AmbientContextEventRequest( @NonNull Set<Integer> eventTypes, @NonNull PersistableBundle options) { this.mEventTypes = eventTypes; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mEventTypes); + AnnotationValidations.validate(NonNull.class, null, mEventTypes); + Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty"); + for (int eventType : eventTypes) { + AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType); + } this.mOptions = options; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mOptions); + AnnotationValidations.validate(NonNull.class, null, mOptions); } /** @@ -80,16 +85,20 @@ public final class AmbientContextEventRequest implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) - AmbientContextEventRequest(@NonNull Parcel in) { + private AmbientContextEventRequest(@NonNull Parcel in) { Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader()); PersistableBundle options = (PersistableBundle) in.readTypedObject( PersistableBundle.CREATOR); this.mEventTypes = eventTypes; - com.android.internal.util.AnnotationValidations.validate( + AnnotationValidations.validate( NonNull.class, null, mEventTypes); + Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty"); + for (int eventType : eventTypes) { + AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType); + } this.mOptions = options; - com.android.internal.util.AnnotationValidations.validate( + AnnotationValidations.validate( NonNull.class, null, mOptions); } diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java deleted file mode 100644 index 472a78b177c9..000000000000 --- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * 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.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.app.PendingIntent; -import android.os.Parcelable; - -import com.android.internal.util.AnnotationValidations; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents a response from the {@code AmbientContextEvent} service. - * - * @hide - */ -@SystemApi -public final class AmbientContextEventResponse implements Parcelable { - /** - * 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 {} - - @StatusCode private final int mStatusCode; - @NonNull private final List<AmbientContextEvent> mEvents; - @NonNull private final String mPackageName; - @Nullable private final PendingIntent mActionPendingIntent; - - /** @hide */ - public static String statusToString(@StatusCode int value) { - switch (value) { - case STATUS_UNKNOWN: - return "STATUS_UNKNOWN"; - case STATUS_SUCCESS: - return "STATUS_SUCCESS"; - case STATUS_NOT_SUPPORTED: - return "STATUS_NOT_SUPPORTED"; - case STATUS_SERVICE_UNAVAILABLE: - return "STATUS_SERVICE_UNAVAILABLE"; - case STATUS_MICROPHONE_DISABLED: - return "STATUS_MICROPHONE_DISABLED"; - case STATUS_ACCESS_DENIED: - return "STATUS_ACCESS_DENIED"; - default: return Integer.toHexString(value); - } - } - - AmbientContextEventResponse( - @StatusCode int statusCode, - @NonNull List<AmbientContextEvent> events, - @NonNull String packageName, - @Nullable PendingIntent actionPendingIntent) { - this.mStatusCode = statusCode; - AnnotationValidations.validate(StatusCode.class, null, mStatusCode); - this.mEvents = events; - AnnotationValidations.validate(NonNull.class, null, mEvents); - this.mPackageName = packageName; - AnnotationValidations.validate(NonNull.class, null, mPackageName); - this.mActionPendingIntent = actionPendingIntent; - } - - /** - * The status of the response. - */ - public @StatusCode int getStatusCode() { - return mStatusCode; - } - - /** - * The detected event. - */ - public @NonNull List<AmbientContextEvent> getEvents() { - return mEvents; - } - - /** - * The package to deliver the response to. - */ - public @NonNull String getPackageName() { - return mPackageName; - } - - /** - * A {@link PendingIntent} that the client should call to allow further actions by user. - * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the - * grant access activity. - */ - public @Nullable PendingIntent getActionPendingIntent() { - return mActionPendingIntent; - } - - @Override - public String toString() { - return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", " - + "events = " + mEvents + ", " + "packageName = " + mPackageName + ", " - + "callbackPendingIntent = " + mActionPendingIntent + " }"; - } - - @Override - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - byte flg = 0; - if (mActionPendingIntent != null) flg |= 0x8; - dest.writeByte(flg); - dest.writeInt(mStatusCode); - dest.writeParcelableList(mEvents, flags); - dest.writeString(mPackageName); - if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags); - } - - @Override - public int describeContents() { - return 0; - } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - AmbientContextEventResponse(@NonNull android.os.Parcel in) { - byte flg = in.readByte(); - int statusCode = in.readInt(); - List<AmbientContextEvent> events = new ArrayList<>(); - in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(), - AmbientContextEvent.class); - String packageName = in.readString(); - PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null - : (PendingIntent) in.readTypedObject(PendingIntent.CREATOR); - - this.mStatusCode = statusCode; - AnnotationValidations.validate( - StatusCode.class, null, mStatusCode); - this.mEvents = events; - AnnotationValidations.validate( - NonNull.class, null, mEvents); - this.mPackageName = packageName; - AnnotationValidations.validate( - NonNull.class, null, mPackageName); - this.mActionPendingIntent = callbackPendingIntent; - } - - public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR = - new Parcelable.Creator<AmbientContextEventResponse>() { - @Override - public AmbientContextEventResponse[] newArray(int size) { - return new AmbientContextEventResponse[size]; - } - - @Override - public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) { - return new AmbientContextEventResponse(in); - } - }; - - /** - * A builder for {@link AmbientContextEventResponse} - */ - @SuppressWarnings("WeakerAccess") - public static final class Builder { - private @StatusCode int mStatusCode; - private @NonNull List<AmbientContextEvent> mEvents; - private @NonNull String mPackageName; - private @Nullable PendingIntent mCallbackPendingIntent; - private long mBuilderFieldsSet = 0L; - - public Builder() { - } - - /** - * The status of the response. - */ - public @NonNull Builder setStatusCode(@StatusCode int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mStatusCode = value; - return this; - } - - /** - * Adds an event to the builder. - */ - public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) { - checkNotUsed(); - if (mEvents == null) { - mBuilderFieldsSet |= 0x2; - mEvents = new ArrayList<>(); - } - mEvents.add(value); - return this; - } - - /** - * The package to deliver the response to. - */ - public @NonNull Builder setPackageName(@NonNull String value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; - mPackageName = value; - return this; - } - - /** - * A {@link PendingIntent} that the client should call to allow further actions by user. - * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to - * the grant access activity. - */ - public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x8; - mCallbackPendingIntent = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull AmbientContextEventResponse build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x10; // Mark builder used - - if ((mBuilderFieldsSet & 0x1) == 0) { - mStatusCode = STATUS_UNKNOWN; - } - if ((mBuilderFieldsSet & 0x2) == 0) { - mEvents = new ArrayList<>(); - } - if ((mBuilderFieldsSet & 0x4) == 0) { - mPackageName = ""; - } - if ((mBuilderFieldsSet & 0x8) == 0) { - mCallbackPendingIntent = null; - } - AmbientContextEventResponse o = new AmbientContextEventResponse( - mStatusCode, - mEvents, - mPackageName, - mCallbackPendingIntent); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x10) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } -} diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index 6841d1bbfc1f..7f913e798bc9 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -17,116 +17,280 @@ package android.app.ambientcontext; import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; 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 particular pre-defined {@link AmbientContextEvent}s. - * After successful registration, the app receives a callback on the provided {@link PendingIntent} - * when the requested event is detected. - * <p /> - * - * Example: - * - * <pre><code> - * // Create request - * AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() - * .addEventType(AmbientContextEvent.EVENT_COUGH) - * .addEventTYpe(AmbientContextEvent.EVENT_SNORE) - * .build(); - * // Create PendingIntent - * Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class) - * .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - * PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0); - * // Register for events - * AmbientContextManager ambientContextManager = - * context.getSystemService(AmbientContextManager.class); - * ambientContextManager.registerObserver(request, pendingIntent); - * - * // Handle the callback intent in your receiver - * {@literal @}Override - * protected void onReceive(Context context, Intent intent) { - * AmbientContextEventResponse response = - * AmbientContextManager.getResponseFromIntent(intent); - * if (response != null) { - * if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) { - * // Do something useful with response.getEvent() - * } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) { - * // Redirect users to grant access - * PendingIntent callbackPendingIntent = response.getCallbackPendingIntent(); - * if (callbackPendingIntent != null) { - * callbackPendingIntent.send(); - * } - * } else ... - * } - * } - * </code></pre> + * 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 key of an Intent extra indicating the response. + * The value of the status code that indicates service not available. */ - public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = - "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + public static final int STATUS_SERVICE_UNAVAILABLE = 3; /** - * Allows clients to retrieve the response from the intent. + * 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 AmbientContextEventResponse, or null if not present + * @return the list of events, or an empty list if the intent doesn't have such events. */ - @Nullable - public static AmbientContextEventResponse getResponseFromIntent( - @NonNull Intent intent) { - if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) { - return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE); + @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 null; + return new ArrayList<>(); } } private final Context mContext; - private final IAmbientContextEventObserver mService; + private final IAmbientContextManager mService; /** * {@hide} */ - public AmbientContextManager(Context context, IAmbientContextEventObserver service) { + 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 = response -> { + * 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 pendingIntent A mutable {@link PendingIntent} that will be dispatched when any - * requested event is detected. + * @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 pendingIntent) { - Preconditions.checkArgument(!pendingIntent.isImmutable()); + @NonNull PendingIntent resultPendingIntent, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + Preconditions.checkArgument(!resultPendingIntent.isImmutable()); try { - mService.registerObserver(request, pendingIntent); + 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(); } diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl index 9032fe1ee045..3b15bcb9846d 100644 --- a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl +++ b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl @@ -18,13 +18,19 @@ package android.app.ambientcontext; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEventRequest; +import android.os.RemoteCallback; /** - * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents. + * Interface for an AmbientContextManager that provides access to AmbientContextEvents. * * @hide */ -oneway interface IAmbientContextEventObserver { - void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent); +oneway interface IAmbientContextManager { + void registerObserver(in AmbientContextEventRequest request, + in PendingIntent resultPendingIntent, + in RemoteCallback statusCallback); void unregisterObserver(in String callingPackage); + void queryServiceStatus(in int[] eventTypes, in String callingPackage, + in RemoteCallback statusCallback); + void startConsentActivity(in int[] eventTypes, in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9323d1797f0e..464567b85270 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10321,6 +10321,34 @@ public final class Settings { "nearby_fast_pair_settings_devices_component"; /** + * Current provider of the component for requesting ambient context consent. + * Default value in @string/config_defaultAmbientContextConsentComponent. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String AMBIENT_CONTEXT_CONSENT_COMPONENT = + "ambient_context_consent_component"; + + /** + * Current provider of the intent extra key for the caller's package name while + * requesting ambient context consent. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY = + "ambient_context_package_name_key"; + + /** + * Current provider of the intent extra key for the event code int array while + * requesting ambient context consent. + * Default value in @string/config_ambientContextEventArrayExtraKey. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY = + "ambient_context_event_array_key"; + + /** * Controls whether aware is enabled. * @hide */ diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl index 4dc6466c7365..4bb29b2ef16d 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.app.ambientcontext; +package android.service.ambientcontext; -parcelable AmbientContextEventResponse;
\ No newline at end of file +parcelable AmbientContextDetectionResult;
\ No newline at end of file diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java new file mode 100644 index 000000000000..227194e60a7e --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java @@ -0,0 +1,180 @@ +/* + * 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.service.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.app.ambientcontext.AmbientContextEvent; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a {@code AmbientContextEvent} detection result reported by the detection service. + * + * @hide + */ +@SystemApi +public final class AmbientContextDetectionResult implements Parcelable { + + /** + * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String RESULT_RESPONSE_BUNDLE_KEY = + "android.app.ambientcontext.AmbientContextDetectionResultBundleKey"; + @NonNull private final List<AmbientContextEvent> mEvents; + @NonNull private final String mPackageName; + + AmbientContextDetectionResult( + @NonNull List<AmbientContextEvent> events, + @NonNull String packageName) { + this.mEvents = events; + AnnotationValidations.validate(NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate(NonNull.class, null, mPackageName); + } + + /** + * A list of detected event. + */ + @SuppressLint("ConcreteCollection") + public @NonNull List<AmbientContextEvent> getEvents() { + return mEvents; + } + + /** + * The package to deliver the response to. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + @Override + public String toString() { + return "AmbientContextEventResponse { " + + "events = " + mEvents + ", " + "packageName = " + mPackageName + " }"; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + dest.writeByte(flg); + dest.writeParcelableList(mEvents, flags); + dest.writeString(mPackageName); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextDetectionResult(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); + ArrayList<AmbientContextEvent> events = new ArrayList<>(); + in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(), + AmbientContextEvent.class); + String packageName = in.readString(); + + this.mEvents = events; + AnnotationValidations.validate( + NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate( + NonNull.class, null, mPackageName); + } + + public static final @NonNull Creator<AmbientContextDetectionResult> CREATOR = + new Creator<AmbientContextDetectionResult>() { + @Override + public AmbientContextDetectionResult[] newArray(int size) { + return new AmbientContextDetectionResult[size]; + } + + @Override + public AmbientContextDetectionResult createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextDetectionResult(in); + } + }; + + /** + * A builder for {@link AmbientContextDetectionResult} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @NonNull ArrayList<AmbientContextEvent> mEvents; + private @NonNull String mPackageName; + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Adds an event to the builder. + */ + public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) { + checkNotUsed(); + if (mEvents == null) { + mBuilderFieldsSet |= 0x1; + mEvents = new ArrayList<>(); + } + mEvents.add(value); + return this; + } + + /** + * The package to deliver the response to. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPackageName = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextDetectionResult build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEvents = new ArrayList<>(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPackageName = ""; + } + AmbientContextDetectionResult o = new AmbientContextDetectionResult( + mEvents, + mPackageName); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java index dccfe3693b35..6224aa1d102e 100644 --- a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java @@ -16,13 +16,13 @@ package android.service.ambientcontext; +import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; -import android.app.ambientcontext.AmbientContextEventResponse; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; @@ -64,15 +64,6 @@ public abstract class AmbientContextDetectionService extends Service { public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; - /** - * The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation - * should set bundle result with this key. - * - * @hide - */ - public static final String RESPONSE_BUNDLE_KEY = - "android.service.ambientcontext.EventResponseKey"; - @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { @@ -82,19 +73,30 @@ public abstract class AmbientContextDetectionService extends Service { @Override public void startDetection( @NonNull AmbientContextEventRequest request, String packageName, - RemoteCallback callback) { + RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - Consumer<AmbientContextEventResponse> consumer = - response -> { + Objects.requireNonNull(packageName); + Objects.requireNonNull(detectionResultCallback); + Objects.requireNonNull(statusCallback); + Consumer<AmbientContextDetectionResult> detectionResultConsumer = + result -> { Bundle bundle = new Bundle(); bundle.putParcelable( - AmbientContextDetectionService.RESPONSE_BUNDLE_KEY, - response); - callback.sendResult(bundle); + AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, + result); + detectionResultCallback.sendResult(bundle); + }; + Consumer<AmbientContextDetectionServiceStatus> statusConsumer = + status -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionServiceStatus + .STATUS_RESPONSE_BUNDLE_KEY, + status); + statusCallback.sendResult(bundle); }; AmbientContextDetectionService.this.onStartDetection( - request, packageName, consumer); + request, packageName, detectionResultConsumer, statusConsumer); Slog.d(TAG, "startDetection " + request); } @@ -104,29 +106,52 @@ public abstract class AmbientContextDetectionService extends Service { Objects.requireNonNull(packageName); AmbientContextDetectionService.this.onStopDetection(packageName); } + + /** {@inheritDoc} */ + @Override + public void queryServiceStatus( + @AmbientContextEvent.EventCode int[] eventTypes, + String packageName, + RemoteCallback callback) { + Objects.requireNonNull(eventTypes); + Objects.requireNonNull(packageName); + Objects.requireNonNull(callback); + Consumer<AmbientContextDetectionServiceStatus> consumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionServiceStatus + .STATUS_RESPONSE_BUNDLE_KEY, + response); + callback.sendResult(bundle); + }; + AmbientContextDetectionService.this.onQueryServiceStatus( + eventTypes, packageName, consumer); + } }; } return null; } /** - * Starts detection and provides detected events to the consumer. The ongoing detection will - * keep running, until onStopDetection is called. If there were previously requested + * Starts detection and provides detected events to the statusConsumer. The ongoing detection + * will keep running, until onStopDetection is called. If there were previously requested * detection from the same package, the previous request will be replaced with the new request. * The implementation should keep track of whether the user consented each requested - * AmbientContextEvent for the app. If not consented, the response should set status - * STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user - * to the consent screen. + * AmbientContextEvent for the app. If not consented, the statusConsumer should get a response + * with STATUS_ACCESS_DENIED. * - * @param request The request with events to detect, optional detection window and other - * options. + * @param request The request with events to detect. * @param packageName the requesting app's package name - * @param consumer the consumer for the detected event + * @param detectionResultConsumer the consumer for the detected event + * @param statusConsumer the consumer for the service status. */ + @BinderThread public abstract void onStartDetection( @NonNull AmbientContextEventRequest request, @NonNull String packageName, - @NonNull Consumer<AmbientContextEventResponse> consumer); + @NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer, + @NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer); /** * Stops detection of the events. Events that are not being detected will be ignored. @@ -134,4 +159,19 @@ public abstract class AmbientContextDetectionService extends Service { * @param packageName stops detection for the given package. */ public abstract void onStopDetection(@NonNull String packageName); + + /** + * Called when a query for the detection status occurs. The implementation should check + * the detection status of the requested events for the package, and provide results in a + * {@link AmbientContextDetectionServiceStatus} for the consumer. + * + * @param eventTypes The events to check for status. + * @param packageName the requesting app's package name + * @param consumer the consumer for the query results + */ + @BinderThread + public abstract void onQueryServiceStatus( + @NonNull int[] eventTypes, + @NonNull String packageName, + @NonNull Consumer<AmbientContextDetectionServiceStatus> consumer); } diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl new file mode 100644 index 000000000000..979cf7b69ddf --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl @@ -0,0 +1,19 @@ +/* + * 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.service.ambientcontext; + +parcelable AmbientContextDetectionServiceStatus;
\ No newline at end of file diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java new file mode 100644 index 000000000000..3e92f39893de --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java @@ -0,0 +1,171 @@ +/* + * 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.service.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.AmbientContextManager.StatusCode; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +/** + * Represents a status for the {@code AmbientContextDetectionService}. + * + * @hide + */ +@SystemApi +public final class AmbientContextDetectionServiceStatus implements Parcelable { + /** + * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String STATUS_RESPONSE_BUNDLE_KEY = + "android.app.ambientcontext.AmbientContextServiceStatusBundleKey"; + + @StatusCode private final int mStatusCode; + @NonNull private final String mPackageName; + + AmbientContextDetectionServiceStatus( + @StatusCode int statusCode, + @NonNull String packageName) { + this.mStatusCode = statusCode; + AnnotationValidations.validate(StatusCode.class, null, mStatusCode); + this.mPackageName = packageName; + } + + /** + * The status of the service. + */ + public @StatusCode int getStatusCode() { + return mStatusCode; + } + + /** + * The package to deliver the response to. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + @Override + public String toString() { + return "AmbientContextDetectionServiceStatus { " + "statusCode = " + mStatusCode + ", " + + "packageName = " + mPackageName + " }"; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + dest.writeByte(flg); + dest.writeInt(mStatusCode); + dest.writeString(mPackageName); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextDetectionServiceStatus(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); + int statusCode = in.readInt(); + String packageName = in.readString(); + + this.mStatusCode = statusCode; + AnnotationValidations.validate( + StatusCode.class, null, mStatusCode); + this.mPackageName = packageName; + AnnotationValidations.validate( + NonNull.class, null, mPackageName); + } + + public static final @NonNull Creator<AmbientContextDetectionServiceStatus> CREATOR = + new Creator<AmbientContextDetectionServiceStatus>() { + @Override + public AmbientContextDetectionServiceStatus[] newArray(int size) { + return new AmbientContextDetectionServiceStatus[size]; + } + + @Override + public AmbientContextDetectionServiceStatus createFromParcel( + @NonNull android.os.Parcel in) { + return new AmbientContextDetectionServiceStatus(in); + } + }; + + /** + * A builder for {@link AmbientContextDetectionServiceStatus} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @StatusCode int mStatusCode; + private @NonNull String mPackageName; + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Sets the status of the service. + */ + public @NonNull Builder setStatusCode(@StatusCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mStatusCode = value; + return this; + } + + /** + * The package to deliver the response to. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPackageName = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextDetectionServiceStatus build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mStatusCode = AmbientContextManager.STATUS_UNKNOWN; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPackageName = ""; + } + AmbientContextDetectionServiceStatus o = new AmbientContextDetectionServiceStatus( + mStatusCode, + mPackageName); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl index 1c6e25efeabe..50c89c0832eb 100644 --- a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl +++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl @@ -26,6 +26,8 @@ import android.os.RemoteCallback; */ oneway interface IAmbientContextDetectionService { void startDetection(in AmbientContextEventRequest request, in String packageName, - in RemoteCallback callback); + in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback); void stopDetection(in String packageName); + void queryServiceStatus(in int[] eventTypes, in String packageName, + in RemoteCallback callback); }
\ No newline at end of file |
