From f8f605c44e40526afd94adec590f7418da7d097f Mon Sep 17 00:00:00 2001 From: Kathy Xiaoming Chen Date: Wed, 26 Jan 2022 14:31:42 -0800 Subject: [PATCH] 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. It implements Option 5 in this proposal doc: http://go/ambient-consent-api Test: atest CtsAmbientContextDetectionServiceDeviceTest Bug: 192476579 Change-Id: I923911fa8f73d973fd22587af74ed27c5045da34 --- core/api/system-current.txt | 64 ++-- .../android/app/SystemServiceRegistry.java | 6 +- .../AmbientContextEventRequest.java | 25 +- .../AmbientContextEventResponse.java | 293 ------------------ .../ambientcontext/AmbientContextManager.java | 284 +++++++++++++---- ...erver.aidl => IAmbientContextManager.aidl} | 12 +- core/java/android/provider/Settings.java | 28 ++ .../AmbientContextDetectionResult.aidl} | 6 +- .../AmbientContextDetectionResult.java | 180 +++++++++++ .../AmbientContextDetectionService.java | 94 ++++-- .../AmbientContextDetectionServiceStatus.aidl | 19 ++ .../AmbientContextDetectionServiceStatus.java | 171 ++++++++++ .../IAmbientContextDetectionService.aidl | 4 +- core/res/res/values/config.xml | 10 + .../AmbientContextManagerPerUserService.java | 220 ++++++++++--- .../AmbientContextManagerService.java | 76 ++++- .../AmbientContextShellCommand.java | 76 +++-- .../RemoteAmbientContextDetectionService.java | 20 +- 18 files changed, 1089 insertions(+), 499 deletions(-) delete mode 100644 core/java/android/app/ambientcontext/AmbientContextEventResponse.java rename core/java/android/app/ambientcontext/{IAmbientContextEventObserver.aidl => IAmbientContextManager.aidl} (66%) rename core/java/android/{app/ambientcontext/AmbientContextEventResponse.aidl => service/ambientcontext/AmbientContextDetectionResult.aidl} (80%) create mode 100644 core/java/android/service/ambientcontext/AmbientContextDetectionResult.java create mode 100644 core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl create mode 100644 core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6267dbf376f77..e46f6fbd984ab 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1353,14 +1353,13 @@ package android.app.ambientcontext { method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle); } - public final class AmbientContextEventResponse implements android.os.Parcelable { - method public int describeContents(); - method @Nullable public android.app.PendingIntent getActionPendingIntent(); - method @NonNull public java.util.List getEvents(); - method @NonNull public String getPackageName(); - method public int getStatusCode(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; + public final class AmbientContextManager { + method @NonNull public static java.util.List getEventsFromIntent(@NonNull android.content.Intent); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void queryAmbientContextServiceStatus(@NonNull java.util.Set, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void startConsentActivity(@NonNull java.util.Set); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver(); + field public static final String EXTRA_AMBIENT_CONTEXT_EVENTS = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS"; field public static final int STATUS_ACCESS_DENIED = 5; // 0x5 field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4 field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2 @@ -1369,22 +1368,6 @@ package android.app.ambientcontext { field public static final int STATUS_UNKNOWN = 0; // 0x0 } - public static final class AmbientContextEventResponse.Builder { - ctor public AmbientContextEventResponse.Builder(); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build(); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int); - } - - public final class AmbientContextManager { - method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent); - method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent); - method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver(); - field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; - } - } package android.app.assist { @@ -10694,14 +10677,45 @@ package android.security.keystore.recovery { package android.service.ambientcontext { + public final class AmbientContextDetectionResult implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List getEvents(); + method @NonNull public String getPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class AmbientContextDetectionResult.Builder { + ctor public AmbientContextDetectionResult.Builder(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult build(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder setPackageName(@NonNull String); + } + public abstract class AmbientContextDetectionService extends android.app.Service { ctor public AmbientContextDetectionService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); - method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer); + method @BinderThread public abstract void onQueryServiceStatus(@NonNull int[], @NonNull String, @NonNull java.util.function.Consumer); + method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer, @NonNull java.util.function.Consumer); method public abstract void onStopDetection(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; } + public final class AmbientContextDetectionServiceStatus implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getPackageName(); + method public int getStatusCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class AmbientContextDetectionServiceStatus.Builder { + ctor public AmbientContextDetectionServiceStatus.Builder(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus build(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setPackageName(@NonNull String); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setStatusCode(int); + } + } package android.service.appprediction { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f5f2fe0d02921..eeb4705fc7d30 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 82b16a2db0ce6..0557acb1130d6 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 mEventTypes; @NonNull private final PersistableBundle mOptions; - AmbientContextEventRequest( + private AmbientContextEventRequest( @NonNull Set 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 eventTypes = (Set) 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 472a78b177c9b..0000000000000 --- 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 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 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 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 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 CREATOR = - new Parcelable.Creator() { - @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 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 6841d1bbfc1f2..7f913e798bc9a 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. - *

- * - * Example: - * - *


- *     // 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 ...
- *      }
- *    }
- * 
+ * 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 key of an Intent extra indicating the response. + * The bundle key for the service status query result, used in + * {@code RemoteCallback#sendResult}. + * + * @hide */ - public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = - "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + public static final String STATUS_RESPONSE_BUNDLE_KEY = + "android.app.ambientcontext.AmbientContextStatusBundleKey"; /** - * Allows clients to retrieve the response from the intent. + * 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 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 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. + *

+ * + * Example: + * + *


+     *   Set eventTypes = new HashSet<>();
+     *   eventTypes.add(AmbientContextEvent.EVENT_COUGH);
+     *   eventTypes.add(AmbientContextEvent.EVENT_SNORE);
+     *
+     *   // Create Consumer
+     *   Consumer 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);
+     * 
+ * + * @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 eventTypes, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer 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 eventTypes) { + try { + mService.startConsentActivity( + integerSetToIntArray(eventTypes), mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @NonNull + private static int[] integerSetToIntArray(@NonNull Set 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. + *

+ * + * Example: + * + *


+     *   // 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 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 events = AmbientContextManager.getEventsFromIntent(intent);
+     *     if (!events.isEmpty()) {
+     *       // Do something useful with the events.
+     *     }
+     *   }
+     * 
* * @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 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 similarity index 66% rename from core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl rename to core/java/android/app/ambientcontext/IAmbientContextManager.aidl index 9032fe1ee0455..3b15bcb9846d1 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 3f41458886a31..a72b0d2c02408 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10313,6 +10313,34 @@ public final class Settings { public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT = "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 similarity index 80% rename from core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl rename to core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl index 4dc6466c7365a..4bb29b2ef16da 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 0000000000000..227194e60a7e1 --- /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 mEvents; + @NonNull private final String mPackageName; + + AmbientContextDetectionResult( + @NonNull List 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 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 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 CREATOR = + new Creator() { + @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 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 dccfe3693b35b..6224aa1d102e6 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 consumer = - response -> { + Objects.requireNonNull(packageName); + Objects.requireNonNull(detectionResultCallback); + Objects.requireNonNull(statusCallback); + Consumer 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 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 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 consumer); + @NonNull Consumer detectionResultConsumer, + @NonNull Consumer 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 consumer); } diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl new file mode 100644 index 0000000000000..979cf7b69ddf3 --- /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 0000000000000..3e92f39893de2 --- /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 CREATOR = + new Creator() { + @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 1c6e25efeabee..50c89c0832eb5 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 diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 53cf4639f11b8..84dc26ee0c663 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4078,6 +4078,16 @@ --> + + + + + + + + +