diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 88b0086b9fdaf..1d1b5c7d2f5e8 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2,6 +2,7 @@ package android { public static final class Manifest.permission { + field public static final String ACCESS_AMBIENT_CONTEXT_EVENT = "android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"; field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS"; field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO"; field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM"; @@ -37,6 +38,7 @@ package android { field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; + field public static final String BIND_AMBIENT_CONTEXT_DETECTION_SERVICE = "android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"; @@ -1192,6 +1194,87 @@ package android.app.admin { } +package android.app.ambientcontext { + + public final class AmbientContextEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getConfidenceLevel(); + method public int getDensityLevel(); + method @NonNull public java.time.Instant getEndTime(); + method public int getEventType(); + method @NonNull public java.time.Instant getStartTime(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int EVENT_COUGH = 1; // 0x1 + field public static final int EVENT_SNORE = 2; // 0x2 + field public static final int EVENT_UNKNOWN = 0; // 0x0 + field public static final int LEVEL_HIGH = 5; // 0x5 + field public static final int LEVEL_LOW = 1; // 0x1 + field public static final int LEVEL_MEDIUM = 3; // 0x3 + field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4 + field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2 + field public static final int LEVEL_UNKNOWN = 0; // 0x0 + } + + public static final class AmbientContextEvent.Builder { + ctor public AmbientContextEvent.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEvent build(); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setConfidenceLevel(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant); + } + + public final class AmbientContextEventRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Set getEventTypes(); + method @NonNull public android.os.PersistableBundle getOptions(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class AmbientContextEventRequest.Builder { + ctor public AmbientContextEventRequest.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder addEventType(int); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest build(); + 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; + 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 + field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3 + field public static final int STATUS_SUCCESS = 1; // 0x1 + 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 { public class ActivityId { @@ -2643,6 +2726,7 @@ package android.content { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); + field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; field public static final String APP_HIBERNATION_SERVICE = "app_hibernation"; field public static final String APP_INTEGRITY_SERVICE = "app_integrity"; field public static final String APP_PREDICTION_SERVICE = "app_prediction"; @@ -9884,6 +9968,7 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean); field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager"; field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot"; + field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service"; field public static final String NAMESPACE_APPSEARCH = "appsearch"; field public static final String NAMESPACE_APP_COMPAT = "app_compat"; field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; @@ -10454,6 +10539,18 @@ package android.security.keystore.recovery { } +package android.service.ambientcontext { + + 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 public abstract void onStopDetection(@NonNull String); + field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; + } + +} + package android.service.appprediction { public abstract class AppPredictionService extends android.app.Service { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 67c42f69d0798..dac90cedc9332 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -24,6 +24,8 @@ import android.annotation.SystemApi; 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.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; @@ -1518,6 +1520,18 @@ public final class SystemServiceRegistry { } }); + registerService(Context.AMBIENT_CONTEXT_SERVICE, AmbientContextManager.class, + new CachedServiceFetcher() { + @Override + public AmbientContextManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = ServiceManager.getServiceOrThrow( + Context.AMBIENT_CONTEXT_SERVICE); + IAmbientContextEventObserver manager = + IAmbientContextEventObserver.Stub.asInterface(iBinder); + return new AmbientContextManager(ctx.getOuterContext(), manager); + }}); + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.aidl b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl new file mode 100644 index 0000000000000..0965b1a3f013b --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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; + +parcelable AmbientContextEvent; diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java new file mode 100644 index 0000000000000..11e695ad7fad8 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -0,0 +1,492 @@ +/* + * 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.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Instant; + + +/** + * Represents a detected ambient event. Each event has a type, start time, end time, + * plus some optional data. + * + * @hide + */ +@SystemApi +@DataClass( + genBuilder = true, + genConstructor = false, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +public final class AmbientContextEvent implements Parcelable { + /** + * The integer indicating an unknown event was detected. + */ + public static final int EVENT_UNKNOWN = 0; + + /** + * The integer indicating a cough event was detected. + */ + public static final int EVENT_COUGH = 1; + + /** + * The integer indicating a snore event was detected. + */ + public static final int EVENT_SNORE = 2; + + /** @hide */ + @IntDef(prefix = { "EVENT_" }, value = { + EVENT_UNKNOWN, + EVENT_COUGH, + EVENT_SNORE, + }) public @interface EventCode {} + + /** The integer indicating an unknown level. */ + public static final int LEVEL_UNKNOWN = 0; + + /** The integer indicating a low level. */ + public static final int LEVEL_LOW = 1; + + /** The integer indicating a medium low level. */ + public static final int LEVEL_MEDIUM_LOW = 2; + + /** The integer indicating a medium Level. */ + public static final int LEVEL_MEDIUM = 3; + + /** The integer indicating a medium high level. */ + public static final int LEVEL_MEDIUM_HIGH = 4; + + /** The integer indicating a high level. */ + public static final int LEVEL_HIGH = 5; + + /** @hide */ + @IntDef(prefix = {"LEVEL_"}, value = { + LEVEL_UNKNOWN, + LEVEL_LOW, + LEVEL_MEDIUM_LOW, + LEVEL_MEDIUM, + LEVEL_MEDIUM_HIGH, + LEVEL_HIGH + }) public @interface LevelValue {} + + @EventCode private final int mEventType; + private static int defaultEventType() { + return EVENT_UNKNOWN; + } + + /** Event start time */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class) + @NonNull private final Instant mStartTime; + @NonNull private static Instant defaultStartTime() { + return Instant.MIN; + } + + /** Event end time */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class) + @NonNull private final Instant mEndTime; + @NonNull private static Instant defaultEndTime() { + return Instant.MAX; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @LevelValue private final int mConfidenceLevel; + private static int defaultConfidenceLevel() { + return LEVEL_UNKNOWN; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @LevelValue private final int mDensityLevel; + private static int defaultDensityLevel() { + return LEVEL_UNKNOWN; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @IntDef(prefix = "EVENT_", value = { + EVENT_UNKNOWN, + EVENT_COUGH, + EVENT_SNORE + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Event {} + + /** @hide */ + @DataClass.Generated.Member + public static String eventToString(@Event int value) { + switch (value) { + case EVENT_UNKNOWN: + return "EVENT_UNKNOWN"; + case EVENT_COUGH: + return "EVENT_COUGH"; + case EVENT_SNORE: + return "EVENT_SNORE"; + default: return Integer.toHexString(value); + } + } + + /** @hide */ + @IntDef(prefix = "LEVEL_", value = { + LEVEL_UNKNOWN, + LEVEL_LOW, + LEVEL_MEDIUM_LOW, + LEVEL_MEDIUM, + LEVEL_MEDIUM_HIGH, + LEVEL_HIGH + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Level {} + + /** @hide */ + @DataClass.Generated.Member + public static String levelToString(@Level int value) { + switch (value) { + case LEVEL_UNKNOWN: + return "LEVEL_UNKNOWN"; + case LEVEL_LOW: + return "LEVEL_LOW"; + case LEVEL_MEDIUM_LOW: + return "LEVEL_MEDIUM_LOW"; + case LEVEL_MEDIUM: + return "LEVEL_MEDIUM"; + case LEVEL_MEDIUM_HIGH: + return "LEVEL_MEDIUM_HIGH"; + case LEVEL_HIGH: + return "LEVEL_HIGH"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + /* package-private */ AmbientContextEvent( + @EventCode int eventType, + @NonNull Instant startTime, + @NonNull Instant endTime, + @LevelValue int confidenceLevel, + @LevelValue int densityLevel) { + this.mEventType = eventType; + com.android.internal.util.AnnotationValidations.validate( + EventCode.class, null, mEventType); + this.mStartTime = startTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStartTime); + this.mEndTime = endTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEndTime); + this.mConfidenceLevel = confidenceLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mConfidenceLevel); + this.mDensityLevel = densityLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mDensityLevel); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @EventCode int getEventType() { + return mEventType; + } + + /** + * Event start time + */ + @DataClass.Generated.Member + public @NonNull Instant getStartTime() { + return mStartTime; + } + + /** + * Event end time + */ + @DataClass.Generated.Member + public @NonNull Instant getEndTime() { + return mEndTime; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @LevelValue int getConfidenceLevel() { + return mConfidenceLevel; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @LevelValue int getDensityLevel() { + return mDensityLevel; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "AmbientContextEvent { " + + "eventType = " + mEventType + ", " + + "startTime = " + mStartTime + ", " + + "endTime = " + mEndTime + ", " + + "confidenceLevel = " + mConfidenceLevel + ", " + + "densityLevel = " + mDensityLevel + + " }"; + } + + @DataClass.Generated.Member + static Parcelling sParcellingForStartTime = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInstant.class); + static { + if (sParcellingForStartTime == null) { + sParcellingForStartTime = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInstant()); + } + } + + @DataClass.Generated.Member + static Parcelling sParcellingForEndTime = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInstant.class); + static { + if (sParcellingForEndTime == null) { + sParcellingForEndTime = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInstant()); + } + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mEventType); + sParcellingForStartTime.parcel(mStartTime, dest, flags); + sParcellingForEndTime.parcel(mEndTime, dest, flags); + dest.writeInt(mConfidenceLevel); + dest.writeInt(mDensityLevel); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ AmbientContextEvent(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int eventType = in.readInt(); + Instant startTime = sParcellingForStartTime.unparcel(in); + Instant endTime = sParcellingForEndTime.unparcel(in); + int confidenceLevel = in.readInt(); + int densityLevel = in.readInt(); + + this.mEventType = eventType; + com.android.internal.util.AnnotationValidations.validate( + EventCode.class, null, mEventType); + this.mStartTime = startTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStartTime); + this.mEndTime = endTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEndTime); + this.mConfidenceLevel = confidenceLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mConfidenceLevel); + this.mDensityLevel = densityLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mDensityLevel); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public AmbientContextEvent[] newArray(int size) { + return new AmbientContextEvent[size]; + } + + @Override + public AmbientContextEvent createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextEvent(in); + } + }; + + /** + * A builder for {@link AmbientContextEvent} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @EventCode int mEventType; + private @NonNull Instant mStartTime; + private @NonNull Instant mEndTime; + private @LevelValue int mConfidenceLevel; + private @LevelValue int mDensityLevel; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + @DataClass.Generated.Member + public @NonNull Builder setEventType(@EventCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mEventType = value; + return this; + } + + /** + * Event start time + */ + @DataClass.Generated.Member + public @NonNull Builder setStartTime(@NonNull Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mStartTime = value; + return this; + } + + /** + * Event end time + */ + @DataClass.Generated.Member + public @NonNull Builder setEndTime(@NonNull Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mEndTime = value; + return this; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @NonNull Builder setConfidenceLevel(@LevelValue int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mConfidenceLevel = value; + return this; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @NonNull Builder setDensityLevel(@LevelValue int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mDensityLevel = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEvent build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEventType = defaultEventType(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mStartTime = defaultStartTime(); + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mEndTime = defaultEndTime(); + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mConfidenceLevel = defaultConfidenceLevel(); + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mDensityLevel = defaultDensityLevel(); + } + AmbientContextEvent o = new AmbientContextEvent( + mEventType, + mStartTime, + mEndTime, + mConfidenceLevel, + mDensityLevel); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x20) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1642040319323L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java", + inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl new file mode 100644 index 0000000000000..e24c6ad1bceaf --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.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.app.ambientcontext; + +parcelable AmbientContextEventRequest; diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java new file mode 100644 index 0000000000000..82b16a2db0ce6 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java @@ -0,0 +1,169 @@ +/* + * 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.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import java.util.HashSet; +import java.util.Set; + +/** + * Represents the request for ambient event detection. + * + * @hide + */ +@SystemApi +public final class AmbientContextEventRequest implements Parcelable { + @NonNull private final Set mEventTypes; + @NonNull private final PersistableBundle mOptions; + + AmbientContextEventRequest( + @NonNull Set eventTypes, + @NonNull PersistableBundle options) { + this.mEventTypes = eventTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEventTypes); + this.mOptions = options; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mOptions); + } + + /** + * The event types to detect. + */ + public @NonNull Set getEventTypes() { + return mEventTypes; + } + + /** + * Optional detection options. + */ + public @NonNull PersistableBundle getOptions() { + return mOptions; + } + + @Override + public String toString() { + return "AmbientContextEventRequest { " + "eventTypes = " + mEventTypes + ", " + + "options = " + mOptions + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeArraySet(new ArraySet<>(mEventTypes)); + dest.writeTypedObject(mOptions, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + 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( + NonNull.class, null, mEventTypes); + this.mOptions = options; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mOptions); + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public AmbientContextEventRequest[] newArray(int size) { + return new AmbientContextEventRequest[size]; + } + + @Override + public AmbientContextEventRequest createFromParcel(@NonNull Parcel in) { + return new AmbientContextEventRequest(in); + } + }; + + /** + * A builder for {@link AmbientContextEventRequest} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @NonNull Set mEventTypes; + private @NonNull PersistableBundle mOptions; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Add an event type to detect. + */ + public @NonNull Builder addEventType(@AmbientContextEvent.EventCode int value) { + checkNotUsed(); + if (mEventTypes == null) { + mBuilderFieldsSet |= 0x1; + mEventTypes = new HashSet<>(); + } + mEventTypes.add(value); + return this; + } + + /** + * Optional detection options. + */ + public @NonNull Builder setOptions(@NonNull PersistableBundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mOptions = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEventRequest build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEventTypes = new HashSet<>(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mOptions = new PersistableBundle(); + } + AmbientContextEventRequest o = new AmbientContextEventRequest( + mEventTypes, + mOptions); + 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/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl new file mode 100644 index 0000000000000..4dc6466c7365a --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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; + +parcelable AmbientContextEventResponse; \ No newline at end of file diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java new file mode 100644 index 0000000000000..472a78b177c9b --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java @@ -0,0 +1,293 @@ +/* + * 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 new file mode 100644 index 0000000000000..6841d1bbfc1f2 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.Manifest; +import android.annotation.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.RemoteException; + +import com.android.internal.util.Preconditions; + +/** + * 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 ...
+ *      }
+ *    }
+ * 
+ * + * @hide + */ +@SystemApi +@SystemService(Context.AMBIENT_CONTEXT_SERVICE) +public final class AmbientContextManager { + + /** + * The key of an Intent extra indicating the response. + */ + public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = + "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + + /** + * Allows clients to retrieve the response from the intent. + * @param intent received from the PendingIntent callback + * + * @return the AmbientContextEventResponse, or null if not present + */ + @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); + } else { + return null; + } + } + + private final Context mContext; + private final IAmbientContextEventObserver mService; + + /** + * {@hide} + */ + public AmbientContextManager(Context context, IAmbientContextEventObserver service) { + mContext = context; + mService = service; + } + + /** + * 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. + * + * @param request The request with events to observe. + * @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any + * requested event is detected. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void registerObserver( + @NonNull AmbientContextEventRequest request, + @NonNull PendingIntent pendingIntent) { + Preconditions.checkArgument(!pendingIntent.isImmutable()); + try { + mService.registerObserver(request, pendingIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an + * observer that was already unregistered or never registered will have no effect. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void unregisterObserver() { + try { + mService.unregisterObserver(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl new file mode 100644 index 0000000000000..9032fe1ee0455 --- /dev/null +++ b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 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.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEventRequest; + +/** + * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents. + * + * @hide + */ +oneway interface IAmbientContextEventObserver { + void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent); + void unregisterObserver(in String callingPackage); +} \ No newline at end of file diff --git a/core/java/android/app/ambientcontext/OWNERS b/core/java/android/app/ambientcontext/OWNERS new file mode 100644 index 0000000000000..a863297b84e86 --- /dev/null +++ b/core/java/android/app/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2309fb636d44d..b1a1d2020a91c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -41,6 +41,7 @@ import android.app.GameManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; +import android.app.ambientcontext.AmbientContextManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; import android.compat.annotation.UnsupportedAppUsage; @@ -5930,6 +5931,17 @@ public abstract class Context { @SystemApi public static final String NEARBY_SERVICE = "nearby"; + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.app.ambientcontext.AmbientContextManager}. + * + * @see #getSystemService(String) + * @see AmbientContextManager + * @hide + */ + @SystemApi + public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6349cde2b8fcf..35e4e3d2032b8 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -687,6 +687,15 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_UWB = "uwb"; + /** + * Namespace for AmbientContextEventManagerService related features. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = + "ambient_context_manager_service"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap> sListeners = diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java new file mode 100644 index 0000000000000..dccfe3693b35b --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java @@ -0,0 +1,137 @@ +/* + * 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.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; +import android.os.RemoteCallback; +import android.util.Slog; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Abstract base class for {@link AmbientContextEvent} detection service. + * + *

A service that provides requested ambient context events to the system. + * The system's default AmbientContextDetectionService implementation is configured in + * {@code config_defaultAmbientContextDetectionService}. If this config has no value, a stub is + * returned. + * + * See: {@code AmbientContextManagerService}. + * + *

+ * {@literal
+ * 
+ * }
+ * 
+ * + * @hide + */ +@SystemApi +public abstract class AmbientContextDetectionService extends Service { + private static final String TAG = AmbientContextDetectionService.class.getSimpleName(); + + /** + * The {@link Intent} that must be declared as handled by the service. To be supported, the + * service must also require the + * {@link android.Manifest.permission#BIND_AMBIENT_CONTEXT_DETECTION_SERVICE} + * permission so that other applications can not abuse it. + */ + 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) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return new IAmbientContextDetectionService.Stub() { + /** {@inheritDoc} */ + @Override + public void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback callback) { + Objects.requireNonNull(request); + Objects.requireNonNull(callback); + Consumer consumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY, + response); + callback.sendResult(bundle); + }; + AmbientContextDetectionService.this.onStartDetection( + request, packageName, consumer); + Slog.d(TAG, "startDetection " + request); + } + + /** {@inheritDoc} */ + @Override + public void stopDetection(String packageName) { + Objects.requireNonNull(packageName); + AmbientContextDetectionService.this.onStopDetection(packageName); + } + }; + } + 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 + * 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. + * + * @param request The request with events to detect, optional detection window and other + * options. + * @param packageName the requesting app's package name + * @param consumer the consumer for the detected event + */ + public abstract void onStartDetection( + @NonNull AmbientContextEventRequest request, + @NonNull String packageName, + @NonNull Consumer consumer); + + /** + * Stops detection of the events. Events that are not being detected will be ignored. + * + * @param packageName stops detection for the given package. + */ + public abstract void onStopDetection(@NonNull String packageName); +} diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl new file mode 100644 index 0000000000000..1c6e25efeabee --- /dev/null +++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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.app.ambientcontext.AmbientContextEventRequest; +import android.os.RemoteCallback; + +/** + * Interface for a concrete implementation to provide AmbientContextEvents to the framework. + * + * @hide + */ +oneway interface IAmbientContextDetectionService { + void startDetection(in AmbientContextEventRequest request, in String packageName, + in RemoteCallback callback); + void stopDetection(in String packageName); +} \ No newline at end of file diff --git a/core/java/android/service/ambientcontext/OWNERS b/core/java/android/service/ambientcontext/OWNERS new file mode 100644 index 0000000000000..a863297b84e86 --- /dev/null +++ b/core/java/android/service/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0fd46295c8e80..5550d1fcba6ec 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6151,6 +6151,19 @@ + + + + + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e9ba92fe95892..dfeeb6c286690 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4077,6 +4077,12 @@ --> + + +