diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 081560a5e95ee..ac5c54d7f180d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1398,12 +1398,15 @@ package android.app.ambientcontext { method @NonNull public java.time.Instant getEndTime(); method public int getEventType(); method @NonNull public java.time.Instant getStartTime(); + method @NonNull public android.os.PersistableBundle getVendorData(); 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_BACK_DOUBLE_TAP = 3; // 0x3 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 EVENT_VENDOR_WEARABLE_START = 100000; // 0x186a0 + field public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name"; 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 @@ -1420,6 +1423,7 @@ package android.app.ambientcontext { 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); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setVendorData(@NonNull android.os.PersistableBundle); } public final class AmbientContextEventRequest implements android.os.Parcelable { @@ -12532,6 +12536,9 @@ package android.service.wearable { method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer); method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer); + method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set, @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.wearable.WearableSensingService"; } diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index 865e1fbaf74ed..a6595feed1f73 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcelable; +import android.os.PersistableBundle; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -66,12 +67,25 @@ public final class AmbientContextEvent implements Parcelable { */ public static final int EVENT_BACK_DOUBLE_TAP = 3; + /** + * Integer indicating the start of wearable vendor defined events that can be detected. + * These depend on the vendor implementation. + */ + public static final int EVENT_VENDOR_WEARABLE_START = 100000; + + /** + * Name for the mVendorData object for this AmbientContextEvent. The mVendorData must be present + * in the object, or it will be rejected. + */ + public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name"; + /** @hide */ @IntDef(prefix = { "EVENT_" }, value = { EVENT_UNKNOWN, EVENT_COUGH, EVENT_SNORE, EVENT_BACK_DOUBLE_TAP, + EVENT_VENDOR_WEARABLE_START, }) public @interface EventCode {} /** The integer indicating an unknown level. */ @@ -139,6 +153,19 @@ public final class AmbientContextEvent implements Parcelable { return LEVEL_UNKNOWN; } + /** + * Vendor defined specific values for vendor event types. + * + *

The use of this vendor data is discouraged. For data defined in the range above + * {@code EVENT_VENDOR_WEARABLE_START} this bundle must include the + * {@link KEY_VENDOR_WEARABLE_EVENT_NAME} field or it will be rejected. In addition, to increase + * transparency of this data contents of this bundle will be logged to logcat.

+ */ + private final @NonNull PersistableBundle mVendorData; + private static PersistableBundle defaultVendorData() { + return new PersistableBundle(); + } + // Code below generated by codegen v1.0.23. @@ -159,7 +186,8 @@ public final class AmbientContextEvent implements Parcelable { EVENT_UNKNOWN, EVENT_COUGH, EVENT_SNORE, - EVENT_BACK_DOUBLE_TAP + EVENT_BACK_DOUBLE_TAP, + EVENT_VENDOR_WEARABLE_START }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -177,6 +205,8 @@ public final class AmbientContextEvent implements Parcelable { return "EVENT_SNORE"; case EVENT_BACK_DOUBLE_TAP: return "EVENT_BACK_DOUBLE_TAP"; + case EVENT_VENDOR_WEARABLE_START: + return "EVENT_VENDOR_WEARABLE_START"; default: return Integer.toHexString(value); } } @@ -220,7 +250,8 @@ public final class AmbientContextEvent implements Parcelable { @NonNull Instant startTime, @NonNull Instant endTime, @LevelValue int confidenceLevel, - @LevelValue int densityLevel) { + @LevelValue int densityLevel, + @NonNull PersistableBundle vendorData) { this.mEventType = eventType; com.android.internal.util.AnnotationValidations.validate( EventCode.class, null, mEventType); @@ -236,6 +267,9 @@ public final class AmbientContextEvent implements Parcelable { this.mDensityLevel = densityLevel; com.android.internal.util.AnnotationValidations.validate( LevelValue.class, null, mDensityLevel); + this.mVendorData = vendorData; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVendorData); // onConstructed(); // You can define this method to get a callback } @@ -279,6 +313,19 @@ public final class AmbientContextEvent implements Parcelable { return mDensityLevel; } + /** + * Vendor defined specific values for vendor event types. + * + *

The use of this vendor data is discouraged. For data defined in the range above + * {@code EVENT_VENDOR_WEARABLE_START} this bundle must include the + * {@link KEY_VENDOR_WEARABLE_EVENT_NAME} field or it will be rejected. In addition, to increase + * transparency of this data contents of this bundle will be logged to logcat.

+ */ + @DataClass.Generated.Member + public @NonNull PersistableBundle getVendorData() { + return mVendorData; + } + @Override @DataClass.Generated.Member public String toString() { @@ -290,7 +337,8 @@ public final class AmbientContextEvent implements Parcelable { "startTime = " + mStartTime + ", " + "endTime = " + mEndTime + ", " + "confidenceLevel = " + mConfidenceLevel + ", " + - "densityLevel = " + mDensityLevel + + "densityLevel = " + mDensityLevel + ", " + + "vendorData = " + mVendorData + " }"; } @@ -327,6 +375,7 @@ public final class AmbientContextEvent implements Parcelable { sParcellingForEndTime.parcel(mEndTime, dest, flags); dest.writeInt(mConfidenceLevel); dest.writeInt(mDensityLevel); + dest.writeTypedObject(mVendorData, flags); } @Override @@ -345,6 +394,7 @@ public final class AmbientContextEvent implements Parcelable { Instant endTime = sParcellingForEndTime.unparcel(in); int confidenceLevel = in.readInt(); int densityLevel = in.readInt(); + PersistableBundle vendorData = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR); this.mEventType = eventType; com.android.internal.util.AnnotationValidations.validate( @@ -361,6 +411,9 @@ public final class AmbientContextEvent implements Parcelable { this.mDensityLevel = densityLevel; com.android.internal.util.AnnotationValidations.validate( LevelValue.class, null, mDensityLevel); + this.mVendorData = vendorData; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVendorData); // onConstructed(); // You can define this method to get a callback } @@ -391,6 +444,7 @@ public final class AmbientContextEvent implements Parcelable { private @NonNull Instant mEndTime; private @LevelValue int mConfidenceLevel; private @LevelValue int mDensityLevel; + private @NonNull PersistableBundle mVendorData; private long mBuilderFieldsSet = 0L; @@ -451,10 +505,26 @@ public final class AmbientContextEvent implements Parcelable { return this; } + /** + * Vendor defined specific values for vendor event types. + * + *

The use of this vendor data is discouraged. For data defined in the range above + * {@code EVENT_VENDOR_WEARABLE_START} this bundle must include the + * {@link KEY_VENDOR_WEARABLE_EVENT_NAME} field or it will be rejected. In addition, to increase + * transparency of this data contents of this bundle will be logged to logcat.

+ */ + @DataClass.Generated.Member + public @NonNull Builder setVendorData(@NonNull PersistableBundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mVendorData = 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 + mBuilderFieldsSet |= 0x40; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mEventType = defaultEventType(); @@ -471,17 +541,21 @@ public final class AmbientContextEvent implements Parcelable { if ((mBuilderFieldsSet & 0x10) == 0) { mDensityLevel = defaultDensityLevel(); } + if ((mBuilderFieldsSet & 0x20) == 0) { + mVendorData = defaultVendorData(); + } AmbientContextEvent o = new AmbientContextEvent( mEventType, mStartTime, mEndTime, mConfidenceLevel, - mDensityLevel); + mDensityLevel, + mVendorData); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x20) != 0) { + if ((mBuilderFieldsSet & 0x40) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -489,10 +563,10 @@ public final class AmbientContextEvent implements Parcelable { } @DataClass.Generated( - time = 1659950304931L, + time = 1671217108067L, 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 EVENT_BACK_DOUBLE_TAP\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)") + inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\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 final @android.annotation.NonNull android.os.PersistableBundle mVendorData\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()\nprivate static android.os.PersistableBundle defaultVendorData()\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() {} diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index 9cb1a204b312c..bf383f1165c43 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -117,7 +117,8 @@ public final class AmbientContextManager { */ @NonNull public static List getEventsFromIntent(@NonNull Intent intent) { if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) { - return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS, android.app.ambientcontext.AmbientContextEvent.class); + return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS, + android.app.ambientcontext.AmbientContextEvent.class); } else { return new ArrayList<>(); } @@ -143,6 +144,9 @@ public final class AmbientContextManager { * 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. + * If the AmbientContextRequest contains a mixed set of events containing values both greater + * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request + * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. *

* * Example: @@ -197,6 +201,9 @@ public final class AmbientContextManager { /** * Requests the consent data host to open an activity that allows users to modify consent. + * If the eventTypes contains a mixed set of events containing values both greater than and less + * than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request will be rejected + * with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. * * @param eventTypes The set of event codes to be consented. */ @@ -226,6 +233,9 @@ public final class AmbientContextManager { * 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. + * If the AmbientContextRequest contains a mixed set of events containing values both greater + * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request + * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. *

* * Example: @@ -308,6 +318,9 @@ public final class AmbientContextManager { * {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)}, * the previous observer will be replaced with the new observer with the PendingIntent callback. * Or vice versa. + * If the AmbientContextRequest contains a mixed set of events containing values both greater + * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request + * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}. * * When the registration completes, a status will be returned to client through * {@link AmbientContextCallback#onRegistrationComplete(int)}. diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl index ba7117440e9ab..44a13c4fb9e53 100644 --- a/core/java/android/service/wearable/IWearableSensingService.aidl +++ b/core/java/android/service/wearable/IWearableSensingService.aidl @@ -16,6 +16,7 @@ package android.service.wearable; +import android.app.ambientcontext.AmbientContextEventRequest; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.SharedMemory; @@ -29,4 +30,8 @@ import android.os.SharedMemory; oneway interface IWearableSensingService { void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback); void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback); + void startDetection(in AmbientContextEventRequest request, in String packageName, + 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/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java index a1c7658fbf65c..8f49bcba11b40 100644 --- a/core/java/android/service/wearable/WearableSensingService.java +++ b/core/java/android/service/wearable/WearableSensingService.java @@ -22,6 +22,7 @@ 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.wearable.WearableSensingManager; import android.content.Intent; import android.os.Bundle; @@ -30,9 +31,14 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.SharedMemory; +import android.service.ambientcontext.AmbientContextDetectionResult; +import android.service.ambientcontext.AmbientContextDetectionServiceStatus; import android.util.Slog; +import java.util.Arrays; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; /** @@ -116,6 +122,60 @@ public abstract class WearableSensingService extends Service { }; WearableSensingService.this.onDataProvided(data, sharedMemory, consumer); } + + /** {@inheritDoc} */ + @Override + public void startDetection(@NonNull AmbientContextEventRequest request, + String packageName, RemoteCallback detectionResultCallback, + RemoteCallback statusCallback) { + Objects.requireNonNull(request); + Objects.requireNonNull(packageName); + Objects.requireNonNull(detectionResultCallback); + Objects.requireNonNull(statusCallback); + Consumer detectionResultConsumer = result -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + 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); + }; + WearableSensingService.this.onStartDetection( + request, packageName, statusConsumer, detectionResultConsumer); + Slog.d(TAG, "startDetection " + request); + } + + /** {@inheritDoc} */ + @Override + public void stopDetection(String packageName) { + Objects.requireNonNull(packageName); + WearableSensingService.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); + }; + Integer[] events = intArrayToIntegerArray(eventTypes); + WearableSensingService.this.onQueryServiceStatus( + new HashSet<>(Arrays.asList(events)), packageName, consumer); + } + }; } Slog.w(TAG, "Incorrect service interface, returning null."); @@ -155,4 +215,61 @@ public abstract class WearableSensingService extends Service { @NonNull PersistableBundle data, @Nullable SharedMemory sharedMemory, @NonNull Consumer statusConsumer); + + /** + * Called when a client app requests starting detection of the events in the request. The + * implementation should keep track of whether the user has explicitly consented to detecting + * the events using on-going ambient sensor (e.g. microphone), and agreed to share the + * detection results with this client app. If the user has not consented, the detection + * should not start, and the statusConsumer should get a response with STATUS_ACCESS_DENIED. + * If the user has made the consent and the underlying services are available, the + * implementation should start detection and provide detected events to the + * detectionResultConsumer. If the type of event needs immediate attention, the implementation + * should send result as soon as detected. Otherwise, the implementation can batch response. + * The ongoing detection will keep running, until onStopDetection is called. If there were + * previously requested detections from the same package, regardless of the type of events in + * the request, the previous request will be replaced with the new request and pending events + * are discarded. + * + * @param request The request with events to detect. + * @param packageName the requesting app's package name + * @param statusConsumer the consumer for the service status. + * @param detectionResultConsumer the consumer for the detected event + */ + @BinderThread + public abstract void onStartDetection(@NonNull AmbientContextEventRequest request, + @NonNull String packageName, + @NonNull Consumer statusConsumer, + @NonNull Consumer detectionResultConsumer); + + /** + * 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); + + /** + * 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 Set eventTypes, + @NonNull String packageName, + @NonNull Consumer consumer); + + @NonNull + private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) { + Integer[] intArray = new Integer[integerSet.length]; + int i = 0; + for (Integer type : integerSet) { + intArray[i++] = type; + } + return intArray; + } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 094f058fd59cf..307707f6e1520 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4154,12 +4154,34 @@ --> + + + @string/config_defaultAmbientContextDetectionService + @string/config_defaultWearableSensingService + + + + + + + + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 094989fdc9eb6..923ef3245c218 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3704,6 +3704,10 @@ + + + + diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java index dcadd5ff518c2..a8066c18abc83 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java @@ -16,9 +16,7 @@ package com.android.server.ambientcontext; -import android.Manifest; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -58,187 +56,105 @@ import java.util.List; import java.util.function.Consumer; /** - * Per-user manager service for {@link AmbientContextEvent}s. + * Base per-user manager service for {@link AmbientContextEvent}s. */ -final class AmbientContextManagerPerUserService extends +abstract class AmbientContextManagerPerUserService extends AbstractPerUserSystemService { - private static final String TAG = AmbientContextManagerPerUserService.class.getSimpleName(); + private static final String TAG = + AmbientContextManagerPerUserService.class.getSimpleName(); - @Nullable - @VisibleForTesting - RemoteAmbientContextDetectionService mRemoteService; - - private ComponentName mComponentName; + /** + * The type of service. + */ + enum ServiceType { + DEFAULT, + WEARABLE + } AmbientContextManagerPerUserService( @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) { super(master, lock, userId); } - void destroyLocked() { - Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed."); - if (mRemoteService != null) { - synchronized (mLock) { - mRemoteService.unbind(); - mRemoteService = null; - } - } - } - - @GuardedBy("mLock") - private void ensureRemoteServiceInitiated() { - if (mRemoteService == null) { - mRemoteService = new RemoteAmbientContextDetectionService( - getContext(), mComponentName, getUserId()); - } - } + /** + * Returns the current bound AmbientContextManagerPerUserService component for this user. + */ + abstract ComponentName getComponentName(); /** - * get the currently bound component name. + * Sets the component name for the per user service. */ - @VisibleForTesting - ComponentName getComponentName() { - return mComponentName; - } - + abstract void setComponentName(ComponentName componentName); /** - * Resolves and sets up the service if it had not been done yet. Returns true if the service - * is available. + * Ensures that the remote service is initiated. */ - @GuardedBy("mLock") - @VisibleForTesting - boolean setUpServiceIfNeeded() { - if (mComponentName == null) { - mComponentName = updateServiceInfoLocked(); - } - if (mComponentName == null) { - return false; - } - - ServiceInfo serviceInfo; - try { - serviceInfo = AppGlobals.getPackageManager().getServiceInfo( - mComponentName, 0, mUserId); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException while setting up service"); - return false; - } - return serviceInfo != null; - } - - @Override - protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) - throws PackageManager.NameNotFoundException { - ServiceInfo serviceInfo; - try { - serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, - 0, mUserId); - if (serviceInfo != null) { - final String permission = serviceInfo.permission; - if (!Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE.equals( - permission)) { - throw new SecurityException(String.format( - "Service %s requires %s permission. Found %s permission", - serviceInfo.getComponentName(), - Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE, - serviceInfo.permission)); - } - } - } catch (RemoteException e) { - throw new PackageManager.NameNotFoundException( - "Could not get service for " + serviceComponent); - } - return serviceInfo; - } - - @Override - protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { - synchronized (super.mLock) { - super.dumpLocked(prefix, pw); - } - if (mRemoteService != null) { - mRemoteService.dump("", new IndentingPrintWriter(pw, " ")); - } - } + abstract void ensureRemoteServiceInitiated(); /** - * Handles client registering as an observer. Only one registration is supported per app - * package. A new registration from the same package will overwrite the previous registration. + * Returns the AmbientContextManagerPerUserService {@link ServiceType} for this user. */ - public void onRegisterObserver(AmbientContextEventRequest request, - String packageName, IAmbientContextObserver observer) { + abstract ServiceType getServiceType(); + + /** + * Returns the int config for the consent component for the + * specific AmbientContextManagerPerUserService type + */ + abstract int getConsentComponentConfig(); + + /** + * Returns the int config for the intent extra key for the + * caller's package name while requesting ambient context consent. + */ + abstract int getAmbientContextPackageNameExtraKeyConfig(); + + /** + * Returns the int config for the Intent extra key for the event code int array while + * requesting ambient context consent. + */ + abstract int getAmbientContextEventArrayExtraKeyConfig(); + + /** + * Returns the permission that is required to bind to this service. + */ + abstract String getProtectedBindPermission(); + + /** + * Returns the remote service implementation for this user. + */ + abstract RemoteAmbientDetectionService getRemoteService(); + + /** + * Clears the remote service. + */ + abstract void clearRemoteService(); + + /** + * Called when there's an application with the callingPackage name is requesting for + * the AmbientContextDetection's service status. + * + * @param eventTypes the event types to query for + * @param callingPackage the package query for information + * @param statusCallback the callback to deliver the status on + */ + public void onQueryServiceStatus(int[] eventTypes, String callingPackage, + RemoteCallback statusCallback) { + Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes) + + " for " + callingPackage); synchronized (mLock) { if (!setUpServiceIfNeeded()) { Slog.w(TAG, "Detection service is not available at this moment."); - completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + sendStatusCallback(statusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } - - // Register package and add to existing ClientRequests cache - startDetection(request, packageName, observer); - mMaster.newClientAdded(mUserId, request, packageName, observer); - } - } - - /** - * Returns a RemoteCallback that handles the status from the detection service, and - * sends results to the client callback. - */ - private RemoteCallback getServerStatusCallback(Consumer statusConsumer) { - return new RemoteCallback(result -> { - AmbientContextDetectionServiceStatus serviceStatus = - (AmbientContextDetectionServiceStatus) result.get( - AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY); - final long token = Binder.clearCallingIdentity(); - try { - int statusCode = serviceStatus.getStatusCode(); - statusConsumer.accept(statusCode); - Slog.i(TAG, "Got detection status of " + statusCode - + " for " + serviceStatus.getPackageName()); - } finally { - Binder.restoreCallingIdentity(token); - } - }); - } - - void startDetection(AmbientContextEventRequest request, String callingPackage, - IAmbientContextObserver observer) { - Slog.d(TAG, "Requested detection of " + request.getEventTypes()); - synchronized (mLock) { - if (setUpServiceIfNeeded()) { - ensureRemoteServiceInitiated(); - mRemoteService.startDetection(request, callingPackage, - createDetectionResultRemoteCallback(), - getServerStatusCallback( - statusCode -> completeRegistration(observer, statusCode))); - } else { - Slog.w(TAG, "No valid component found for AmbientContextDetectionService"); - completeRegistration(observer, - AmbientContextManager.STATUS_NOT_SUPPORTED); - } - } - } - - /** - * Sends the result response with the specified status to the callback. - */ - static void sendStatusCallback(RemoteCallback statusCallback, - @AmbientContextManager.StatusCode int statusCode) { - Bundle bundle = new Bundle(); - bundle.putInt( - AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, - statusCode); - statusCallback.sendResult(bundle); - } - - static void completeRegistration(IAmbientContextObserver observer, int statusCode) { - try { - observer.onRegistrationComplete(statusCode); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: " - + e.getMessage()); + ensureRemoteServiceInitiated(); + getRemoteService().queryServiceStatus( + eventTypes, + callingPackage, + getServerStatusCallback( + statusCode -> sendStatusCallback(statusCallback, statusCode))); } } @@ -254,26 +170,9 @@ final class AmbientContextManagerPerUserService extends } } - public void onQueryServiceStatus(int[] eventTypes, String callingPackage, - RemoteCallback statusCallback) { - Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes) - + " for " + callingPackage); - synchronized (mLock) { - if (!setUpServiceIfNeeded()) { - Slog.w(TAG, "Detection service is not available at this moment."); - sendStatusCallback(statusCallback, - AmbientContextManager.STATUS_NOT_SUPPORTED); - return; - } - ensureRemoteServiceInitiated(); - mRemoteService.queryServiceStatus( - eventTypes, - callingPackage, - getServerStatusCallback( - statusCode -> sendStatusCallback(statusCallback, statusCode))); - } - } - + /** + * Starts the consent activity for the calling package and event types. + */ public void onStartConsentActivity(int[] eventTypes, String callingPackage) { Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes) + " for " + callingPackage); @@ -315,9 +214,9 @@ final class AmbientContextManagerPerUserService extends try { Context context = getContext(); String packageNameExtraKey = context.getResources().getString( - com.android.internal.R.string.config_ambientContextPackageNameExtraKey); + getAmbientContextPackageNameExtraKeyConfig()); String eventArrayExtraKey = context.getResources().getString( - com.android.internal.R.string.config_ambientContextEventArrayExtraKey); + getAmbientContextEventArrayExtraKeyConfig()); // Create consent activity intent with the calling package name and requested events intent.setComponent(consentComponent); @@ -344,37 +243,163 @@ final class AmbientContextManagerPerUserService extends } /** - * Returns the consent activity component from config lookup. + * Handles client registering as an observer. Only one registration is supported per app + * package. A new registration from the same package will overwrite the previous registration. */ - private ComponentName getConsentComponent() { - Context context = getContext(); - String consentComponent = context.getResources().getString( - com.android.internal.R.string.config_defaultAmbientContextConsentComponent); - if (TextUtils.isEmpty(consentComponent)) { - return null; + public void onRegisterObserver(AmbientContextEventRequest request, + String packageName, IAmbientContextObserver observer) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + + // Register package and add to existing ClientRequests cache + startDetection(request, packageName, observer); + mMaster.newClientAdded(mUserId, request, packageName, observer); } - Slog.i(TAG, "Consent component name: " + consentComponent); - return ComponentName.unflattenFromString(consentComponent); } + @Override + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws PackageManager.NameNotFoundException { + Slog.d(TAG, "newServiceInfoLocked with component name: " + + serviceComponent.getClassName()); + + if (getComponentName() == null + || !serviceComponent.getClassName().equals(getComponentName().getClassName())) { + Slog.d(TAG, "service name does not match this per user, returning..."); + return null; + } + + ServiceInfo serviceInfo; + try { + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + 0, mUserId); + if (serviceInfo != null) { + final String permission = serviceInfo.permission; + if (!getProtectedBindPermission().equals( + permission)) { + throw new SecurityException(String.format( + "Service %s requires %s permission. Found %s permission", + serviceInfo.getComponentName(), + getProtectedBindPermission(), + serviceInfo.permission)); + } + } + } catch (RemoteException e) { + throw new PackageManager.NameNotFoundException( + "Could not get service for " + serviceComponent); + } + return serviceInfo; + } + + /** + * Dumps the remote service. + */ + protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { + synchronized (super.mLock) { + super.dumpLocked(prefix, pw); + } + RemoteAmbientDetectionService remoteService = getRemoteService(); + if (remoteService != null) { + remoteService.dump("", new IndentingPrintWriter(pw, " ")); + } + } + + /** + * Send request to the remote AmbientContextDetectionService impl to stop detecting the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ @VisibleForTesting - void stopDetection(String packageName) { + protected void stopDetection(String packageName) { Slog.d(TAG, "Stop detection for " + packageName); synchronized (mLock) { - if (mComponentName != null) { + if (getComponentName() != null) { ensureRemoteServiceInitiated(); - mRemoteService.stopDetection(packageName); + RemoteAmbientDetectionService remoteService = getRemoteService(); + remoteService.stopDetection(packageName); } } } + /** + * Destroys this service and unbinds from the remote service. + */ + protected void destroyLocked() { + Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed."); + RemoteAmbientDetectionService remoteService = getRemoteService(); + if (remoteService != null) { + synchronized (mLock) { + remoteService.unbind(); + clearRemoteService(); + } + } + } + + /** + * Send request to the remote AmbientContextDetectionService impl to start detecting the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ + protected void startDetection(AmbientContextEventRequest request, String callingPackage, + IAmbientContextObserver observer) { + Slog.d(TAG, "Requested detection of " + request.getEventTypes()); + synchronized (mLock) { + if (setUpServiceIfNeeded()) { + ensureRemoteServiceInitiated(); + RemoteAmbientDetectionService remoteService = getRemoteService(); + remoteService.startDetection(request, callingPackage, + createDetectionResultRemoteCallback(), + getServerStatusCallback( + statusCode -> completeRegistration(observer, statusCode))); + } else { + Slog.w(TAG, "No valid component found for AmbientContextDetectionService"); + completeRegistration(observer, + AmbientContextManager.STATUS_NOT_SUPPORTED); + } + } + } + + /** + * Notifies the observer the status of the registration. + * + * @param observer the observer to notify + * @param statusCode the status to notify + */ + protected void completeRegistration(IAmbientContextObserver observer, int statusCode) { + try { + observer.onRegistrationComplete(statusCode); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: " + + e.getMessage()); + } + } + + /** + * Sends the status on the {@link RemoteCallback}. + * + * @param statusCallback the callback to send the status on + * @param statusCode the status to send + */ + protected void sendStatusCallback(RemoteCallback statusCallback, + @AmbientContextManager.StatusCode int statusCode) { + Bundle bundle = new Bundle(); + bundle.putInt( + AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, + statusCode); + statusCallback.sendResult(bundle); + } + /** * Sends out the Intent to the client after the event is detected. * * @param pendingIntent Client's PendingIntent for callback * @param events detected events from the detection service */ - void sendDetectionResultIntent(PendingIntent pendingIntent, + protected void sendDetectionResultIntent(PendingIntent pendingIntent, List events) { Intent intent = new Intent(); intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS, @@ -384,8 +409,8 @@ final class AmbientContextManagerPerUserService extends BroadcastOptions options = BroadcastOptions.makeBasic(); options.setPendingIntentBackgroundActivityLaunchAllowed(false); try { - pendingIntent.send(getContext(), 0, intent, null, null, null, - options.toBundle()); + pendingIntent.send(getContext(), 0, intent, null, + null, null, options.toBundle()); Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": " + events); } catch (PendingIntent.CanceledException e) { @@ -394,7 +419,7 @@ final class AmbientContextManagerPerUserService extends } @NonNull - RemoteCallback createDetectionResultRemoteCallback() { + protected RemoteCallback createDetectionResultRemoteCallback() { return new RemoteCallback(result -> { AmbientContextDetectionResult detectionResult = (AmbientContextDetectionResult) result.get( @@ -418,4 +443,80 @@ final class AmbientContextManagerPerUserService extends } }); } + + /** + * Resolves and sets up the service if it had not been done yet. Returns true if the service + * is available. + */ + @GuardedBy("mLock") + @VisibleForTesting + private boolean setUpServiceIfNeeded() { + if (getComponentName() == null) { + ComponentName[] componentNames = updateServiceInfoListLocked(); + if (componentNames == null || componentNames.length != 2) { + Slog.d(TAG, "updateServiceInfoListLocked returned incorrect componentNames"); + return false; + } + + switch (getServiceType()) { + case DEFAULT: + setComponentName(componentNames[0]); + break; + case WEARABLE: + setComponentName(componentNames[1]); + break; + default: + Slog.d(TAG, "updateServiceInfoListLocked returned unknown service types."); + return false; + } + } + + if (getComponentName() == null) { + return false; + } + + ServiceInfo serviceInfo; + try { + serviceInfo = AppGlobals.getPackageManager().getServiceInfo( + getComponentName(), 0, mUserId); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException while setting up service"); + return false; + } + return serviceInfo != null; + } + + /** + * Returns a RemoteCallback that handles the status from the detection service, and + * sends results to the client callback. + */ + private RemoteCallback getServerStatusCallback(Consumer statusConsumer) { + return new RemoteCallback(result -> { + AmbientContextDetectionServiceStatus serviceStatus = + (AmbientContextDetectionServiceStatus) result.get( + AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + int statusCode = serviceStatus.getStatusCode(); + statusConsumer.accept(statusCode); + Slog.i(TAG, "Got detection status of " + statusCode + + " for " + serviceStatus.getPackageName()); + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } + + /** + * Returns the consent activity component from config lookup. + */ + private ComponentName getConsentComponent() { + Context context = getContext(); + String consentComponent = context.getResources().getString(getConsentComponentConfig()); + if (TextUtils.isEmpty(consentComponent)) { + return null; + } + Slog.i(TAG, "Consent component name: " + consentComponent); + return ComponentName.unflattenFromString(consentComponent); + } } diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java index e205e84cea268..a0c7ee65e4147 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java @@ -17,6 +17,7 @@ package com.android.server.ambientcontext; import static android.provider.DeviceConfig.NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE; +import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING; import android.Manifest; import android.annotation.NonNull; @@ -44,12 +45,16 @@ import com.android.internal.R; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.ambientcontext.AmbientContextManagerPerUserService.ServiceType; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; import com.android.server.pm.KnownPackages; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -62,6 +67,12 @@ public class AmbientContextManagerService extends AmbientContextManagerPerUserService> { private static final String TAG = AmbientContextManagerService.class.getSimpleName(); private static final String KEY_SERVICE_ENABLED = "service_enabled"; + private static final Set DEFAULT_EVENT_SET = new HashSet<>(){{ + add(AmbientContextEvent.EVENT_COUGH); + add(AmbientContextEvent.EVENT_SNORE); + add(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP); + } + }; /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; @@ -104,14 +115,16 @@ public class AmbientContextManagerService extends private final Context mContext; boolean mIsServiceEnabled; + boolean mIsWearableServiceEnabled; private Set mExistingClientRequests; public AmbientContextManagerService(Context context) { super(context, new FrameworkResourcesServiceNameResolver( context, - R.string.config_defaultAmbientContextDetectionService), - /*disallowProperty=*/null, + R.array.config_defaultAmbientContextServices, + /*isMultiple=*/ true), + /*disallowProperty=*/null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER); mContext = context; @@ -134,6 +147,9 @@ public class AmbientContextManagerService extends mIsServiceEnabled = DeviceConfig.getBoolean( NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + mIsWearableServiceEnabled = DeviceConfig.getBoolean( + NAMESPACE_WEARABLE_SENSING, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); } } @@ -180,13 +196,63 @@ public class AmbientContextManagerService extends mIsServiceEnabled = DeviceConfig.getBoolean( NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + mIsWearableServiceEnabled = DeviceConfig.getBoolean( + NAMESPACE_WEARABLE_SENSING, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); } } @Override protected AmbientContextManagerPerUserService newServiceLocked(int resolvedUserId, boolean disabled) { - return new AmbientContextManagerPerUserService(this, mLock, resolvedUserId); + // This service uses newServiceListLocked, it is configured in multiple mode. + return null; + } + + @Override // from AbstractMasterSystemService + protected List newServiceListLocked(int resolvedUserId, + boolean disabled, String[] serviceNames) { + if (serviceNames == null || serviceNames.length == 0) { + Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty"); + return new ArrayList<>(); + } + + List serviceList = + new ArrayList<>(serviceNames.length); + if (!isDefaultServiceEnabled(resolvedUserId)) { + Slog.i(TAG, "Not using default services, " + + "services provided for testing should be exactly two services."); + if (serviceNames.length == 2) { + // Expecting two services for testing, first being the default and second wearable. + serviceList.add(new DefaultAmbientContextManagerPerUserService( + this, mLock, resolvedUserId, + AmbientContextManagerPerUserService.ServiceType.DEFAULT, serviceNames[0])); + serviceList.add(new WearableAmbientContextManagerPerUserService( + this, mLock, resolvedUserId, + AmbientContextManagerPerUserService.ServiceType.WEARABLE, + serviceNames[1])); + } else { + Slog.i(TAG, "Incorrect number of services provided for testing."); + } + return serviceList; + } + + for (String serviceName : serviceNames) { + Slog.d(TAG, "newServicesListLocked with service name: " + serviceName); + if (getServiceType(serviceName) + == AmbientContextManagerPerUserService.ServiceType.WEARABLE) { + serviceList.add(new + WearableAmbientContextManagerPerUserService( + this, mLock, resolvedUserId, + AmbientContextManagerPerUserService.ServiceType.WEARABLE, serviceName)); + } else { + serviceList.add(new DefaultAmbientContextManagerPerUserService( + this, mLock, resolvedUserId, + AmbientContextManagerPerUserService.ServiceType.DEFAULT, serviceName)); + } + + } + return serviceList; } @Override @@ -239,7 +305,10 @@ public class AmbientContextManagerService extends mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); synchronized (mLock) { - final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + userId, + request.getEventTypes()); if (service != null) { service.startDetection(request, packageName, observer); } else { @@ -257,11 +326,19 @@ public class AmbientContextManagerService extends mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); synchronized (mLock) { - final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); - if (service != null) { - service.stopDetection(packageName); - } else { - Slog.i(TAG, "service not available for user_id: " + userId); + for (ClientRequest cr : mExistingClientRequests) { + Slog.i(TAG, "Looping through clients"); + if (cr.hasUserIdAndPackageName(userId, packageName)) { + Slog.i(TAG, "we have an existing client"); + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + userId, cr.getRequest().getEventTypes()); + if (service != null) { + service.stopDetection(packageName); + } else { + Slog.i(TAG, "service not available for user_id: " + userId); + } + } } } } @@ -276,7 +353,9 @@ public class AmbientContextManagerService extends mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); synchronized (mLock) { - final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + userId, intArrayToIntegerSet(eventTypes)); if (service != null) { service.onQueryServiceStatus(eventTypes, packageName, callback); } else { @@ -287,14 +366,18 @@ public class AmbientContextManagerService extends private void restorePreviouslyEnabledClients(int userId) { synchronized (mLock) { - final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); - for (ClientRequest clientRequest : mExistingClientRequests) { - // Start detection for previously enabled clients - if (clientRequest.hasUserId(userId)) { - Slog.d(TAG, "Restoring detection for " + clientRequest.getPackageName()); - service.startDetection(clientRequest.getRequest(), - clientRequest.getPackageName(), - clientRequest.getObserver()); + final List services = + getServiceListForUserLocked(userId); + for (AmbientContextManagerPerUserService service : services) { + for (ClientRequest clientRequest : mExistingClientRequests) { + // Start detection for previously enabled clients + if (clientRequest.hasUserId(userId)) { + Slog.d(TAG, "Restoring detection for " + + clientRequest.getPackageName()); + service.startDetection(clientRequest.getRequest(), + clientRequest.getPackageName(), + clientRequest.getObserver()); + } } } } @@ -303,9 +386,12 @@ public class AmbientContextManagerService extends /** * Returns the AmbientContextManagerPerUserService component for this user. */ - public ComponentName getComponentName(@UserIdInt int userId) { + public ComponentName getComponentName( + @UserIdInt int userId, + AmbientContextManagerPerUserService.ServiceType serviceType) { synchronized (mLock) { - final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + final AmbientContextManagerPerUserService service = + getServiceForType(userId, serviceType); if (service != null) { return service.getComponentName(); } @@ -313,10 +399,114 @@ public class AmbientContextManagerService extends return null; } - private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub { - final AmbientContextManagerPerUserService mService = getServiceForUserLocked( - UserHandle.getCallingUserId()); + private AmbientContextManagerPerUserService getAmbientContextManagerPerUserServiceForEventTypes( + @UserIdInt int userId, Set eventTypes) { + if (isWearableEventTypesOnly(eventTypes)) { + return getServiceForType(userId, + AmbientContextManagerPerUserService.ServiceType.WEARABLE); + } else { + return getServiceForType(userId, + AmbientContextManagerPerUserService.ServiceType.DEFAULT); + } + } + private Set intArrayToIntegerSet(int[] eventTypes) { + Set types = new HashSet<>(); + for (Integer i : eventTypes) { + types.add(i); + } + return types; + } + + private AmbientContextManagerPerUserService.ServiceType getServiceType(String serviceName) { + final String wearableService = mContext.getResources() + .getString(R.string.config_defaultWearableSensingService); + if (wearableService != null && wearableService.equals(serviceName)) { + return AmbientContextManagerPerUserService.ServiceType.WEARABLE; + } + + return AmbientContextManagerPerUserService.ServiceType.DEFAULT; + } + + private AmbientContextManagerPerUserService getServiceForType(int userId, + AmbientContextManagerPerUserService.ServiceType serviceType) { + Slog.d(TAG, "getServiceForType with userid: " + + userId + " service type: " + serviceType.name()); + synchronized (mLock) { + final List services = + getServiceListForUserLocked(userId); + Slog.d(TAG, "Services that are available: " + + (services == null ? "null services" : services.size() + + " number of services")); + if (services == null) { + return null; + } + + for (AmbientContextManagerPerUserService service : services) { + if (service.getServiceType() == serviceType) { + return service; + } + } + } + return null; + } + + private boolean isWearableEventTypesOnly(Set eventTypes) { + if (eventTypes.isEmpty()) { + Slog.d(TAG, "empty event types."); + return false; + } + for (Integer eventType : eventTypes) { + if (eventType < AmbientContextEvent.EVENT_VENDOR_WEARABLE_START) { + Slog.d(TAG, "Not all events types are wearable events."); + return false; + } + } + Slog.d(TAG, "only wearable events."); + return true; + } + + private boolean isWearableEventTypesOnly(int[] eventTypes) { + Integer[] events = intArrayToIntegerArray(eventTypes); + return isWearableEventTypesOnly(new HashSet<>(Arrays.asList(events))); + } + + private boolean containsMixedEvents(int[] eventTypes) { + if (isWearableEventTypesOnly(eventTypes)) { + return false; + } + // It's not only wearable events so check if it's only default events. + for (Integer event : eventTypes) { + if (!DEFAULT_EVENT_SET.contains(event)) { + // mixed events. + Slog.w(TAG, "Received mixed event types, this is not supported."); + return true; + } + } + // Only default events. + return false; + } + + 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; + } + + @NonNull + private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) { + Integer[] intArray = new Integer[integerSet.length]; + int i = 0; + for (Integer type : integerSet) { + intArray[i++] = type; + } + return intArray; + } + + private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub { @Override public void registerObserver( AmbientContextEventRequest request, PendingIntent resultPendingIntent, @@ -324,17 +514,21 @@ public class AmbientContextManagerService extends Objects.requireNonNull(request); Objects.requireNonNull(resultPendingIntent); Objects.requireNonNull(statusCallback); + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + UserHandle.getCallingUserId(), + request.getEventTypes()); // Wrap the PendingIntent and statusCallback in a IAmbientContextObserver to make the // code unified IAmbientContextObserver observer = new IAmbientContextObserver.Stub() { @Override public void onEvents(List events) throws RemoteException { - mService.sendDetectionResultIntent(resultPendingIntent, events); + service.sendDetectionResultIntent(resultPendingIntent, events); } @Override public void onRegistrationComplete(int statusCode) throws RemoteException { - AmbientContextManagerPerUserService.sendStatusCallback(statusCallback, + service.sendStatusCallback(statusCallback, statusCode); } }; @@ -356,13 +550,37 @@ public class AmbientContextManagerService extends mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); assertCalledByPackageOwner(packageName); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available."); - AmbientContextManagerPerUserService.completeRegistration(observer, + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + UserHandle.getCallingUserId(), + request.getEventTypes()); + + if (service == null) { + Slog.w(TAG, "onRegisterObserver unavailable user_id: " + + UserHandle.getCallingUserId()); + } + + if (service.getServiceType() == ServiceType.DEFAULT && !mIsServiceEnabled) { + Slog.d(TAG, "Service not available."); + service.completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } - mService.onRegisterObserver(request, packageName, observer); + if (service.getServiceType() == ServiceType.WEARABLE && !mIsWearableServiceEnabled) { + Slog.d(TAG, "Wearable Service not available."); + service.completeRegistration(observer, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + if (containsMixedEvents(integerSetToIntArray(request.getEventTypes()))) { + Slog.d(TAG, "AmbientContextEventRequest contains mixed events," + + " this is not supported."); + service.completeRegistration(observer, + AmbientContextManager.STATUS_NOT_SUPPORTED); + return; + } + + service.onRegisterObserver(request, packageName, observer); } @Override @@ -370,7 +588,20 @@ public class AmbientContextManagerService extends mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); assertCalledByPackageOwner(callingPackage); - mService.onUnregisterObserver(callingPackage); + + AmbientContextManagerPerUserService service = null; + for (ClientRequest cr : mExistingClientRequests) { + if (cr.getPackageName().equals(callingPackage)) { + service = getAmbientContextManagerPerUserServiceForEventTypes( + UserHandle.getCallingUserId(), cr.getRequest().getEventTypes()); + if (service != null) { + service.onUnregisterObserver(callingPackage); + } else { + Slog.w(TAG, "onUnregisterObserver unavailable user_id: " + + UserHandle.getCallingUserId()); + } + } + } } @Override @@ -382,14 +613,40 @@ public class AmbientContextManagerService extends mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); assertCalledByPackageOwner(callingPackage); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Detection service not available."); - AmbientContextManagerPerUserService.sendStatusCallback(statusCallback, - AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); - return; + synchronized (mLock) { + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + UserHandle.getCallingUserId(), intArrayToIntegerSet(eventTypes)); + if (service == null) { + Slog.w(TAG, "onQueryServiceStatus unavailable user_id: " + + UserHandle.getCallingUserId()); + } + + if (service.getServiceType() == ServiceType.DEFAULT && !mIsServiceEnabled) { + Slog.d(TAG, "Service not available."); + service.sendStatusCallback(statusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + if (service.getServiceType() == ServiceType.WEARABLE + && !mIsWearableServiceEnabled) { + Slog.d(TAG, "Wearable Service not available."); + service.sendStatusCallback(statusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + + if (containsMixedEvents(eventTypes)) { + Slog.d(TAG, "AmbientContextEventRequest contains mixed events," + + " this is not supported."); + service.sendStatusCallback(statusCallback, + AmbientContextManager.STATUS_NOT_SUPPORTED); + return; + } + + service.onQueryServiceStatus(eventTypes, callingPackage, + statusCallback); } - mService.onQueryServiceStatus(eventTypes, callingPackage, - statusCallback); } @Override @@ -399,7 +656,23 @@ public class AmbientContextManagerService extends assertCalledByPackageOwner(callingPackage); mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); - mService.onStartConsentActivity(eventTypes, callingPackage); + + if (containsMixedEvents(eventTypes)) { + Slog.d(TAG, "AmbientContextEventRequest contains mixed events," + + " this is not supported."); + return; + } + + AmbientContextManagerPerUserService service = + getAmbientContextManagerPerUserServiceForEventTypes( + UserHandle.getCallingUserId(), intArrayToIntegerSet(eventTypes)); + + if (service != null) { + service.onStartConsentActivity(eventTypes, callingPackage); + } else { + Slog.w(TAG, "startConsentActivity unavailable user_id: " + + UserHandle.getCallingUserId()); + } } @Override diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java index a3ffcde802f34..8808854446ae3 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ShellCommand; +import android.util.Slog; import java.io.PrintWriter; import java.util.List; @@ -36,6 +37,7 @@ import java.util.List; * Shell command for {@link AmbientContextManagerService}. */ final class AmbientContextShellCommand extends ShellCommand { + private static final String TAG = AmbientContextShellCommand.class.getSimpleName(); private static final AmbientContextEventRequest REQUEST = new AmbientContextEventRequest.Builder() @@ -44,6 +46,20 @@ final class AmbientContextShellCommand extends ShellCommand { .addEventType(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP) .build(); + private static final int WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING = + AmbientContextEvent.EVENT_VENDOR_WEARABLE_START + 1; + + private static final AmbientContextEventRequest WEARABLE_REQUEST = + new AmbientContextEventRequest.Builder() + .addEventType(WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING) + .build(); + + private static final AmbientContextEventRequest MIXED_REQUEST = + new AmbientContextEventRequest.Builder() + .addEventType(AmbientContextEvent.EVENT_COUGH) + .addEventType(WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING) + .build(); + @NonNull private final AmbientContextManagerService mService; @@ -106,16 +122,26 @@ final class AmbientContextShellCommand extends ShellCommand { switch (cmd) { case "start-detection": return runStartDetection(); + case "start-detection-wearable": + return runWearableStartDetection(); + case "start-detection-mixed": + return runMixedStartDetection(); case "stop-detection": return runStopDetection(); case "get-last-status-code": return getLastStatusCode(); case "query-service-status": return runQueryServiceStatus(); + case "query-wearable-service-status": + return runQueryWearableServiceStatus(); + case "query-mixed-service-status": + return runQueryMixedServiceStatus(); case "get-bound-package": return getBoundPackageName(); case "set-temporary-service": return setTemporaryService(); + case "set-temporary-services": + return setTemporaryServices(); default: return handleDefaultCommands(cmd); } @@ -127,6 +153,30 @@ final class AmbientContextShellCommand extends ShellCommand { mService.startDetection( userId, REQUEST, packageName, sTestableCallbackInternal.createAmbientContextObserver()); + mService.newClientAdded(userId, REQUEST, packageName, + sTestableCallbackInternal.createAmbientContextObserver()); + return 0; + } + + private int runWearableStartDetection() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + mService.startDetection( + userId, WEARABLE_REQUEST, packageName, + sTestableCallbackInternal.createAmbientContextObserver()); + mService.newClientAdded(userId, WEARABLE_REQUEST, packageName, + sTestableCallbackInternal.createAmbientContextObserver()); + return 0; + } + + private int runMixedStartDetection() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + mService.startDetection( + userId, MIXED_REQUEST, packageName, + sTestableCallbackInternal.createAmbientContextObserver()); + mService.newClientAdded(userId, MIXED_REQUEST, packageName, + sTestableCallbackInternal.createAmbientContextObserver()); return 0; } @@ -148,6 +198,26 @@ final class AmbientContextShellCommand extends ShellCommand { return 0; } + private int runQueryWearableServiceStatus() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + int[] types = new int[] {WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING}; + mService.queryServiceStatus(userId, packageName, types, + sTestableCallbackInternal.createRemoteStatusCallback()); + return 0; + } + + private int runQueryMixedServiceStatus() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + int[] types = new int[] { + AmbientContextEvent.EVENT_COUGH, + WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING}; + mService.queryServiceStatus(userId, packageName, types, + sTestableCallbackInternal.createRemoteStatusCallback()); + return 0; + } + private int getLastStatusCode() { final PrintWriter resultPrinter = getOutPrintWriter(); int lastStatus = sTestableCallbackInternal.getLastStatus(); @@ -163,20 +233,33 @@ final class AmbientContextShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(); pw.println(" start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection."); + pw.println(" start-detection-wearable USER_ID PACKAGE_NAME: " + + "Starts AmbientContextEvent detection for wearable."); + pw.println(" start-detection-mixed USER_ID PACKAGE_NAME: " + + " Starts AmbientContextEvent detection for mixed events."); pw.println(" stop-detection USER_ID PACKAGE_NAME: Stops AmbientContextEvent detection."); pw.println(" get-last-status-code: Prints the latest request status code."); pw.println(" query-service-status USER_ID PACKAGE_NAME: Prints the service status code."); + pw.println(" query-wearable-service-status USER_ID PACKAGE_NAME: " + + "Prints the service status code for wearable."); + pw.println(" query-mixed-service-status USER_ID PACKAGE_NAME: " + + "Prints the service status code for mixed events."); pw.println(" get-bound-package USER_ID:" + " Print the bound package that implements the service."); pw.println(" set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]"); pw.println(" Temporarily (for DURATION ms) changes the service implementation."); pw.println(" To reset, call with just the USER_ID argument."); + pw.println(" set-temporary-services USER_ID " + + "[FIRST_PACKAGE_NAME] [SECOND_PACKAGE_NAME] [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implementation."); + pw.println(" To reset, call with just the USER_ID argument."); } private int getBoundPackageName() { final PrintWriter resultPrinter = getOutPrintWriter(); final int userId = Integer.parseInt(getNextArgRequired()); - final ComponentName componentName = mService.getComponentName(userId); + final ComponentName componentName = mService.getComponentName(userId, + AmbientContextManagerPerUserService.ServiceType.DEFAULT); resultPrinter.println(componentName == null ? "" : componentName.getPackageName()); return 0; } @@ -188,6 +271,7 @@ final class AmbientContextShellCommand extends ShellCommand { if (serviceName == null) { mService.resetTemporaryService(userId); out.println("AmbientContextDetectionService temporary reset. "); + mService.setDefaultServiceEnabled(userId, true); return 0; } @@ -197,4 +281,30 @@ final class AmbientContextShellCommand extends ShellCommand { + " for " + duration + "ms"); return 0; } + + private int setTemporaryServices() { + String[] serviceNames = new String[2]; + final PrintWriter out = getOutPrintWriter(); + final int userId = Integer.parseInt(getNextArgRequired()); + mService.setDefaultServiceEnabled(userId, false); + final String firstServiceName = getNextArg(); + final String secondServiceName = getNextArg(); + if (firstServiceName == null || secondServiceName == null) { + mService.resetTemporaryService(userId); + mService.setDefaultServiceEnabled(userId, true); + out.println("AmbientContextDetectionService temporary reset."); + return 0; + } + serviceNames[0] = firstServiceName; + serviceNames[1] = secondServiceName; + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryServices(userId, serviceNames, duration); + Slog.w(TAG, "AmbientContextDetectionService temporarily set to " + serviceNames[0] + + " and " + serviceNames[1] + + " for " + duration + "ms"); + out.println("AmbientContextDetectionService temporarily set to " + serviceNames[0] + + " and " + serviceNames[1] + + " for " + duration + "ms"); + return 0; + } } diff --git a/services/core/java/com/android/server/ambientcontext/DefaultAmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/DefaultAmbientContextManagerPerUserService.java new file mode 100644 index 0000000000000..3fb29a0893ec2 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/DefaultAmbientContextManagerPerUserService.java @@ -0,0 +1,112 @@ +/* + * 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 com.android.server.ambientcontext; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ambientcontext.AmbientContextEvent; +import android.content.ComponentName; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Per-user manager service for {@link AmbientContextEvent}s. + */ +public class DefaultAmbientContextManagerPerUserService extends + AmbientContextManagerPerUserService { + private static final String TAG = + DefaultAmbientContextManagerPerUserService.class.getSimpleName(); + + @Nullable + @VisibleForTesting + DefaultRemoteAmbientContextDetectionService mRemoteService; + + private ComponentName mComponentName; + private final ServiceType mServiceType; + private final String mServiceName; + + DefaultAmbientContextManagerPerUserService( + @NonNull AmbientContextManagerService master, Object lock, + @UserIdInt int userId, ServiceType serviceType, String serviceName) { + super(master, lock, userId); + this.mServiceType = serviceType; + this.mServiceName = serviceName; + this.mComponentName = ComponentName.unflattenFromString(mServiceName); + Slog.d(TAG, "Created DefaultAmbientContextManagerPerUserService" + + "and service type: " + mServiceType.name() + " and service name: " + serviceName); + } + + + @GuardedBy("mLock") + @Override + protected void ensureRemoteServiceInitiated() { + if (mRemoteService == null) { + mRemoteService = new DefaultRemoteAmbientContextDetectionService( + getContext(), mComponentName, getUserId()); + } + } + + @VisibleForTesting + @Override + ComponentName getComponentName() { + return mComponentName; + } + + @Override + protected void setComponentName(ComponentName componentName) { + this.mComponentName = componentName; + } + + @Override + protected RemoteAmbientDetectionService getRemoteService() { + return mRemoteService; + } + + @Override + protected String getProtectedBindPermission() { + return Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE; + } + + @Override + public ServiceType getServiceType() { + return mServiceType; + } + + @Override + protected int getAmbientContextPackageNameExtraKeyConfig() { + return com.android.internal.R.string.config_ambientContextPackageNameExtraKey; + } + + @Override + protected int getAmbientContextEventArrayExtraKeyConfig() { + return com.android.internal.R.string.config_ambientContextEventArrayExtraKey; + } + + @Override + protected int getConsentComponentConfig() { + return com.android.internal.R.string.config_defaultAmbientContextConsentComponent; + } + + @Override + protected void clearRemoteService() { + mRemoteService = null; + } +} diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/DefaultRemoteAmbientContextDetectionService.java similarity index 77% rename from services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java rename to services/core/java/com/android/server/ambientcontext/DefaultRemoteAmbientContextDetectionService.java index 8aec75226958d..ebbc4d1bbf5bd 100644 --- a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java +++ b/services/core/java/com/android/server/ambientcontext/DefaultRemoteAmbientContextDetectionService.java @@ -32,13 +32,16 @@ import android.util.Slog; import com.android.internal.infra.ServiceConnector; -/** Manages the connection to the remote service. */ -final class RemoteAmbientContextDetectionService - extends ServiceConnector.Impl { - private static final String TAG = - RemoteAmbientContextDetectionService.class.getSimpleName(); +import java.io.PrintWriter; - RemoteAmbientContextDetectionService(Context context, ComponentName serviceName, +/** Manages the connection to the remote service. */ +final class DefaultRemoteAmbientContextDetectionService + extends ServiceConnector.Impl + implements RemoteAmbientDetectionService { + private static final String TAG = + DefaultRemoteAmbientContextDetectionService.class.getSimpleName(); + + DefaultRemoteAmbientContextDetectionService(Context context, ComponentName serviceName, int userId) { super(context, new Intent( AmbientContextDetectionService.SERVICE_INTERFACE).setComponent(serviceName), @@ -55,14 +58,7 @@ final class RemoteAmbientContextDetectionService return -1; } - /** - * Asks the implementation to start detection. - * - * @param request The request with events to detect, and optional detection options. - * @param packageName The app package that requested the detection - * @param detectionResultCallback callback for detection results - * @param statusCallback callback for service status - */ + @Override public void startDetection( @NonNull AmbientContextEventRequest request, String packageName, RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { @@ -71,19 +67,13 @@ final class RemoteAmbientContextDetectionService statusCallback)); } - /** - * Asks the implementation to stop detection. - * - * @param packageName stop detection for the given package - */ + @Override public void stopDetection(String packageName) { Slog.i(TAG, "Stop detection for " + packageName); post(service -> service.stopDetection(packageName)); } - /** - * Asks the implementation to return the event status for the package. - */ + @Override public void queryServiceStatus( @AmbientContextEvent.EventCode int[] eventTypes, String packageName, @@ -91,4 +81,14 @@ final class RemoteAmbientContextDetectionService Slog.i(TAG, "Query status for " + packageName); post(service -> service.queryServiceStatus(eventTypes, packageName, callback)); } + + @Override + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + } + + @Override + public void unbind() { + super.unbind(); + } } diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientDetectionService.java new file mode 100644 index 0000000000000..802718dd950a1 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientDetectionService.java @@ -0,0 +1,66 @@ +/* + * 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 com.android.server.ambientcontext; + +import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.os.RemoteCallback; + +import java.io.PrintWriter; + +/** + * Interface for a remote service implementing Ambient Context Detection Service capabilities. + */ +interface RemoteAmbientDetectionService { + /** + * Asks the implementation to start detection. + * + * @param request The request with events to detect, and optional detection options. + * @param packageName The app package that requested the detection + * @param detectionResultCallback callback for detection results + * @param statusCallback callback for service status + */ + void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback detectionResultCallback, RemoteCallback statusCallback); + + /** + * Asks the implementation to stop detection. + * + * @param packageName stop detection for the given package + */ + void stopDetection(String packageName); + + /** + * Asks the implementation to return the event status for the package. + */ + void queryServiceStatus( + @AmbientContextEvent.EventCode int[] eventTypes, + String packageName, + RemoteCallback callback); + + /** + * Dumps the RemoteAmbientDetectionService. + */ + void dump(@NonNull String prefix, @NonNull PrintWriter pw); + + /** + * Unbinds from the remote service. + */ + void unbind(); +} diff --git a/services/core/java/com/android/server/ambientcontext/RemoteWearableSensingService.java b/services/core/java/com/android/server/ambientcontext/RemoteWearableSensingService.java new file mode 100644 index 0000000000000..3c6ff98dfe829 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/RemoteWearableSensingService.java @@ -0,0 +1,94 @@ +/* + * 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 com.android.server.ambientcontext; + +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_INCLUDE_CAPABILITIES; + +import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteCallback; +import android.service.wearable.IWearableSensingService; +import android.service.wearable.WearableSensingService; +import android.util.Slog; + +import com.android.internal.infra.ServiceConnector; + +import java.io.PrintWriter; + +/** Manages the connection to the remote wearable sensing service. */ +final class RemoteWearableSensingService + extends ServiceConnector.Impl + implements RemoteAmbientDetectionService { + private static final String TAG = + RemoteWearableSensingService.class.getSimpleName(); + + RemoteWearableSensingService(Context context, ComponentName serviceName, + int userId) { + super(context, new Intent( + WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName), + BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId, + IWearableSensingService.Stub::asInterface); + + // Bind right away + connect(); + } + + @Override + protected long getAutoDisconnectTimeoutMs() { + // Disable automatic unbinding. + return -1; + } + + @Override + public void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { + Slog.i(TAG, "Start detection for " + request.getEventTypes()); + post(service -> service.startDetection(request, packageName, detectionResultCallback, + statusCallback)); + } + + @Override + public void stopDetection(String packageName) { + Slog.i(TAG, "Stop detection for " + packageName); + post(service -> service.stopDetection(packageName)); + } + + @Override + public void queryServiceStatus( + @AmbientContextEvent.EventCode int[] eventTypes, + String packageName, + RemoteCallback callback) { + Slog.i(TAG, "Query status for " + packageName); + post(service -> service.queryServiceStatus(eventTypes, packageName, callback)); + } + + @Override + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + } + + @Override + public void unbind() { + super.unbind(); + } +} diff --git a/services/core/java/com/android/server/ambientcontext/WearableAmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/WearableAmbientContextManagerPerUserService.java new file mode 100644 index 0000000000000..36abd266b57a3 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/WearableAmbientContextManagerPerUserService.java @@ -0,0 +1,112 @@ +/* + * 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 com.android.server.ambientcontext; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ambientcontext.AmbientContextEvent; +import android.content.ComponentName; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Per-user manager service for {@link AmbientContextEvent}s for the Wearable Sensing. + */ +public class WearableAmbientContextManagerPerUserService extends + AmbientContextManagerPerUserService { + private static final String TAG = + WearableAmbientContextManagerPerUserService.class.getSimpleName(); + + @Nullable + @VisibleForTesting + RemoteWearableSensingService mRemoteService; + + private ComponentName mComponentName; + private final ServiceType mServiceType; + private final String mServiceName; + + WearableAmbientContextManagerPerUserService( + @NonNull AmbientContextManagerService master, Object lock, + @UserIdInt int userId, ServiceType serviceType, String serviceName) { + super(master, lock, userId); + this.mServiceType = serviceType; + this.mServiceName = serviceName; + this.mComponentName = ComponentName.unflattenFromString(mServiceName); + Slog.d(TAG, "Created WearableAmbientContextManagerPerUserService" + + "and service type: " + mServiceType.name() + " and service name: " + serviceName); + } + + @GuardedBy("mLock") + @Override + protected void ensureRemoteServiceInitiated() { + if (mRemoteService == null) { + mRemoteService = new RemoteWearableSensingService( + getContext(), mComponentName, getUserId()); + } + } + + @VisibleForTesting + @Override + ComponentName getComponentName() { + return mComponentName; + } + + @Override + protected void setComponentName(ComponentName componentName) { + this.mComponentName = componentName; + } + + + @Override + protected RemoteAmbientDetectionService getRemoteService() { + return mRemoteService; + } + + @Override + protected String getProtectedBindPermission() { + return Manifest.permission.BIND_WEARABLE_SENSING_SERVICE; + } + + @Override + public ServiceType getServiceType() { + return mServiceType; + } + + @Override + protected int getAmbientContextPackageNameExtraKeyConfig() { + return com.android.internal.R.string.config_wearableAmbientContextPackageNameExtraKey; + } + + @Override + protected int getAmbientContextEventArrayExtraKeyConfig() { + return com.android.internal.R.string.config_wearableAmbientContextEventArrayExtraKey; + } + + @Override + protected int getConsentComponentConfig() { + return com.android.internal.R.string.config_defaultWearableSensingConsentComponent; + } + + @Override + protected void clearRemoteService() { + mRemoteService = null; + } +} diff --git a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java index b8f1db402a555..ddb19f0ebfb85 100644 --- a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java @@ -182,8 +182,12 @@ public abstract class AbstractPerUserSystemService