AmbientContextManager API changes to support quick-tap detection

Test: "atest CtsAmbientContextServiceTestCases", "atest FrameworksServicesTests:AmbientContextManagerServiceTest"
BUG: 242480433
Ignore-AOSP-First: to prevent new feature leak.

Change-Id: If445894f0d68fbb336156d2817e0dea884182a10
diff --git a/core/java/android/app/ambientcontext/AmbientContextCallback.java b/core/java/android/app/ambientcontext/AmbientContextCallback.java
new file mode 100644
index 0000000..9133d7f
--- /dev/null
+++ b/core/java/android/app/ambientcontext/AmbientContextCallback.java
@@ -0,0 +1,47 @@
+/*
+ * 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 java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Callback for listening to Ambient Context events and status changes. See {@link
+ * AmbientContextManager#registerObserver(AmbientContextEventRequest, AmbientContextCallback,
+ * Executor)}
+ *
+ * @hide
+ */
+public interface AmbientContextCallback {
+    /**
+     * Called when AmbientContextManager service detects events.
+     *
+     * @param events a list of detected events.
+     */
+    void onEvents(@NonNull List<AmbientContextEvent> events);
+
+    /**
+     * Called with a statusCode when
+     * {@link AmbientContextManager#registerObserver(AmbientContextEventRequest,
+     * Executor, AmbientContextCallback)} completes, to indicate if the registration is successful
+     *
+     * @param statusCode the status of the service.
+     */
+    void onRegistrationComplete(@NonNull @AmbientContextManager.StatusCode int statusCode);
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index 11e695ad..af48fde 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -59,11 +59,21 @@
      */
     public static final int EVENT_SNORE = 2;
 
+    /**
+     * The integer indicating a double-tap event was detected.
+     * For detecting this event type, there's no specific consent activity to request access, but
+     * the consent is implied through the double tap toggle in the Settings app.
+     *
+     * @hide
+     */
+    public static final int EVENT_BACK_DOUBLE_TAP = 3;
+
     /** @hide */
     @IntDef(prefix = { "EVENT_" }, value = {
             EVENT_UNKNOWN,
             EVENT_COUGH,
             EVENT_SNORE,
+            EVENT_BACK_DOUBLE_TAP,
     }) public @interface EventCode {}
 
     /** The integer indicating an unknown level. */
@@ -150,7 +160,8 @@
     @IntDef(prefix = "EVENT_", value = {
         EVENT_UNKNOWN,
         EVENT_COUGH,
-        EVENT_SNORE
+        EVENT_SNORE,
+        EVENT_BACK_DOUBLE_TAP
     })
     @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
@@ -166,6 +177,8 @@
                     return "EVENT_COUGH";
             case EVENT_SNORE:
                     return "EVENT_SNORE";
+            case EVENT_BACK_DOUBLE_TAP:
+                    return "EVENT_BACK_DOUBLE_TAP";
             default: return Integer.toHexString(value);
         }
     }
@@ -478,10 +491,10 @@
     }
 
     @DataClass.Generated(
-            time = 1642040319323L,
+            time = 1659950304931L,
             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)")
+            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)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
index dd1dd0c..e45dfdf 100644
--- a/core/java/android/app/ambientcontext/AmbientContextManager.java
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -282,7 +282,7 @@
         Preconditions.checkArgument(!resultPendingIntent.isImmutable());
         try {
             RemoteCallback callback = new RemoteCallback(result -> {
-                int statusCode =  result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     executor.execute(() -> statusConsumer.accept(statusCode));
@@ -297,6 +297,72 @@
     }
 
     /**
+     * Allows app to register as a {@link AmbientContextEvent} observer. Same as {@link
+     * #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
+     * but use {@link AmbientContextCallback} instead of {@link PendingIntent} as a callback on
+     * detected events.
+     * Registering another observer from the same package that has already been
+     * registered will override the previous observer. If the same app previously calls
+     * {@link #registerObserver(AmbientContextEventRequest, AmbientContextCallback, Executor)},
+     * and now calls
+     * {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
+     * the previous observer will be replaced with the new observer with the PendingIntent callback.
+     * Or vice versa.
+     *
+     * When the registration completes, a status will be returned to client through
+     * {@link AmbientContextCallback#onRegistrationComplete(int)}.
+     * If the AmbientContextManager service is not enabled yet, or the underlying detection service
+     * is not running yet, {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} will be
+     * returned, and the detection won't be really started.
+     * If the underlying detection service feature is not enabled, or the requested event type is
+     * not enabled yet, {@link AmbientContextManager#STATUS_NOT_SUPPORTED} will be returned, and the
+     * detection won't be really started.
+     * If there is no user consent,  {@link AmbientContextManager#STATUS_ACCESS_DENIED} will be
+     * returned, and the detection won't be really started.
+     * Otherwise, it will try to start the detection. And if it starts successfully, it will return
+     * {@link AmbientContextManager#STATUS_SUCCESS}. If it fails to start the detection, then
+     * it will return {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE}
+     * After registerObserver succeeds and when the service detects an event, the service will
+     * trigger {@link AmbientContextCallback#onEvents(List)}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
+    public void registerObserver(
+            @NonNull AmbientContextEventRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AmbientContextCallback ambientContextCallback) {
+        try {
+            IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
+                @Override
+                public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> ambientContextCallback.onEvents(events));
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+
+                @Override
+                public void onRegistrationComplete(int statusCode) throws RemoteException {
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(
+                                () -> ambientContextCallback.onRegistrationComplete(statusCode));
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+            };
+
+            mService.registerObserverWithCallback(request, mContext.getPackageName(), observer);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an
      * observer that was already unregistered or never registered will have no effect.
      */
diff --git a/core/java/android/app/ambientcontext/IAmbientContextManager.aidl b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
index 0d9ecfd..8f06e76 100644
--- a/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
+++ b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.IAmbientContextObserver;
 import android.os.RemoteCallback;
 
 /**
@@ -29,6 +30,11 @@
     void registerObserver(in AmbientContextEventRequest request,
         in PendingIntent resultPendingIntent,
         in RemoteCallback statusCallback);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)")
+    void registerObserverWithCallback(in AmbientContextEventRequest request,
+        String packageName,
+        in IAmbientContextObserver observer);
     void unregisterObserver(in String callingPackage);
     void queryServiceStatus(in int[] eventTypes, in String callingPackage,
         in RemoteCallback statusCallback);
diff --git a/core/java/android/app/ambientcontext/IAmbientContextObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextObserver.aidl
new file mode 100644
index 0000000..529e2fe
--- /dev/null
+++ b/core/java/android/app/ambientcontext/IAmbientContextObserver.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.ambientcontext.AmbientContextEvent;
+
+/**
+ * Callback interface of AmbientContextManager.
+ *
+ * @hide
+ */
+oneway interface IAmbientContextObserver {
+  void onEvents(in List<AmbientContextEvent> events);
+  void onRegistrationComplete(in int statusCode);
+}
+
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
index 028288f..dcadd5f 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
@@ -29,6 +29,7 @@
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
 import android.app.ambientcontext.AmbientContextManager;
+import android.app.ambientcontext.IAmbientContextObserver;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -53,6 +54,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Per-user manager service for {@link AmbientContextEvent}s.
@@ -165,20 +168,17 @@
      * package. A new registration from the same package will overwrite the previous registration.
      */
     public void onRegisterObserver(AmbientContextEventRequest request,
-            PendingIntent pendingIntent, RemoteCallback clientStatusCallback) {
+            String packageName, IAmbientContextObserver observer) {
         synchronized (mLock) {
             if (!setUpServiceIfNeeded()) {
                 Slog.w(TAG, "Detection service is not available at this moment.");
-                sendStatusCallback(
-                        clientStatusCallback,
-                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
 
             // Register package and add to existing ClientRequests cache
-            startDetection(request, pendingIntent.getCreatorPackage(),
-                    createDetectionResultRemoteCallback(), clientStatusCallback);
-            mMaster.newClientAdded(mUserId, request, pendingIntent, clientStatusCallback);
+            startDetection(request, packageName, observer);
+            mMaster.newClientAdded(mUserId, request, packageName, observer);
         }
     }
 
@@ -186,49 +186,46 @@
      * Returns a RemoteCallback that handles the status from the detection service, and
      * sends results to the client callback.
      */
-    private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) {
+    private RemoteCallback getServerStatusCallback(Consumer<Integer> statusConsumer) {
         return new RemoteCallback(result -> {
             AmbientContextDetectionServiceStatus serviceStatus =
                     (AmbientContextDetectionServiceStatus) result.get(
                             AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
             final long token = Binder.clearCallingIdentity();
             try {
-                String packageName = serviceStatus.getPackageName();
-                Bundle bundle = new Bundle();
-                bundle.putInt(
-                        AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
-                        serviceStatus.getStatusCode());
-                clientStatusCallback.sendResult(bundle);
                 int statusCode = serviceStatus.getStatusCode();
+                statusConsumer.accept(statusCode);
                 Slog.i(TAG, "Got detection status of " + statusCode
-                        + " for " + packageName);
+                        + " for " + serviceStatus.getPackageName());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         });
     }
 
-    @VisibleForTesting
     void startDetection(AmbientContextEventRequest request, String callingPackage,
-            RemoteCallback detectionResultCallback, RemoteCallback clientStatusCallback) {
+            IAmbientContextObserver observer) {
         Slog.d(TAG, "Requested detection of " + request.getEventTypes());
         synchronized (mLock) {
             if (setUpServiceIfNeeded()) {
                 ensureRemoteServiceInitiated();
-                mRemoteService.startDetection(request, callingPackage, detectionResultCallback,
-                        getServerStatusCallback(clientStatusCallback));
+                mRemoteService.startDetection(request, callingPackage,
+                        createDetectionResultRemoteCallback(),
+                        getServerStatusCallback(
+                                statusCode -> completeRegistration(observer, statusCode)));
             } else {
                 Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
-                sendStatusToCallback(clientStatusCallback,
+                completeRegistration(observer,
                         AmbientContextManager.STATUS_NOT_SUPPORTED);
             }
         }
     }
 
     /**
-     * Sends an intent with a status code and empty events.
+     * Sends the result response with the specified status to the callback.
      */
-    void sendStatusCallback(RemoteCallback statusCallback, int statusCode) {
+    static void sendStatusCallback(RemoteCallback statusCallback,
+            @AmbientContextManager.StatusCode int statusCode) {
         Bundle bundle = new Bundle();
         bundle.putInt(
                 AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
@@ -236,6 +233,15 @@
         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());
+        }
+    }
+
     /**
      * Unregisters the client from all previously registered events by removing from the
      * mExistingRequests map, and unregister events from the service if those events are not
@@ -255,7 +261,7 @@
         synchronized (mLock) {
             if (!setUpServiceIfNeeded()) {
                 Slog.w(TAG, "Detection service is not available at this moment.");
-                sendStatusToCallback(statusCallback,
+                sendStatusCallback(statusCallback,
                         AmbientContextManager.STATUS_NOT_SUPPORTED);
                 return;
             }
@@ -263,7 +269,8 @@
             mRemoteService.queryServiceStatus(
                     eventTypes,
                     callingPackage,
-                    getServerStatusCallback(statusCallback));
+                    getServerStatusCallback(
+                            statusCode -> sendStatusCallback(statusCallback, statusCode)));
         }
     }
 
@@ -350,18 +357,6 @@
         return ComponentName.unflattenFromString(consentComponent);
     }
 
-    /**
-     * Sends the result response with the specified status to the callback.
-     */
-    void sendStatusToCallback(RemoteCallback callback,
-                    @AmbientContextManager.StatusCode int status) {
-        Bundle bundle = new Bundle();
-        bundle.putInt(
-                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
-                status);
-        callback.sendResult(bundle);
-    }
-
     @VisibleForTesting
     void stopDetection(String packageName) {
         Slog.d(TAG, "Stop detection for " + packageName);
@@ -377,13 +372,13 @@
      * Sends out the Intent to the client after the event is detected.
      *
      * @param pendingIntent Client's PendingIntent for callback
-     * @param result result from the detection service
+     * @param events detected events from the detection service
      */
-    private void sendDetectionResultIntent(PendingIntent pendingIntent,
-            AmbientContextDetectionResult result) {
+    void sendDetectionResultIntent(PendingIntent pendingIntent,
+            List<AmbientContextEvent> events) {
         Intent intent = new Intent();
         intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
-                new ArrayList(result.getEvents()));
+                new ArrayList(events));
         // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
         // the PendingIntent as a backdoor to do this.
         BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -392,7 +387,7 @@
             pendingIntent.send(getContext(), 0, intent, null, null, null,
                     options.toBundle());
             Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
-                    + result);
+                    + events);
         } catch (PendingIntent.CanceledException e) {
             Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
         }
@@ -405,16 +400,19 @@
                     (AmbientContextDetectionResult) result.get(
                             AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
             String packageName = detectionResult.getPackageName();
-            PendingIntent pendingIntent = mMaster.getPendingIntent(mUserId, packageName);
-            if (pendingIntent == null) {
+            IAmbientContextObserver observer = mMaster.getClientRequestObserver(
+                    mUserId, packageName);
+            if (observer == null) {
                 return;
             }
 
             final long token = Binder.clearCallingIdentity();
             try {
-                sendDetectionResultIntent(pendingIntent, detectionResult);
+                observer.onEvents(detectionResult.getEvents());
                 Slog.i(TAG, "Got detection result of " + detectionResult.getEvents()
                         + " for " + packageName);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call IAmbientContextObserver.onEvents: " + e.getMessage());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index 4206262..e205e84 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -27,10 +27,12 @@
 import android.app.ambientcontext.AmbientContextEventRequest;
 import android.app.ambientcontext.AmbientContextManager;
 import android.app.ambientcontext.IAmbientContextManager;
+import android.app.ambientcontext.IAmbientContextObserver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.os.RemoteCallback;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
@@ -48,6 +50,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -67,31 +70,27 @@
     static class ClientRequest {
         private final int mUserId;
         private final AmbientContextEventRequest mRequest;
-        private final PendingIntent mPendingIntent;
-        private final RemoteCallback mClientStatusCallback;
+        private final String mPackageName;
+        private final IAmbientContextObserver mObserver;
 
         ClientRequest(int userId, AmbientContextEventRequest request,
-                PendingIntent pendingIntent, RemoteCallback clientStatusCallback) {
+                String packageName, IAmbientContextObserver observer) {
             this.mUserId = userId;
             this.mRequest = request;
-            this.mPendingIntent = pendingIntent;
-            this.mClientStatusCallback = clientStatusCallback;
+            this.mPackageName = packageName;
+            this.mObserver = observer;
         }
 
         String getPackageName() {
-            return mPendingIntent.getCreatorPackage();
+            return mPackageName;
         }
 
         AmbientContextEventRequest getRequest() {
             return mRequest;
         }
 
-        PendingIntent getPendingIntent() {
-            return mPendingIntent;
-        }
-
-        RemoteCallback getClientStatusCallback() {
-            return mClientStatusCallback;
+        IAmbientContextObserver getObserver() {
+            return mObserver;
         }
 
         boolean hasUserId(int userId) {
@@ -139,16 +138,16 @@
     }
 
     void newClientAdded(int userId, AmbientContextEventRequest request,
-            PendingIntent pendingIntent, RemoteCallback clientStatusCallback) {
-        Slog.d(TAG, "New client added: " + pendingIntent.getCreatorPackage());
+            String callingPackage, IAmbientContextObserver observer) {
+        Slog.d(TAG, "New client added: " + callingPackage);
 
         // Remove any existing ClientRequest for this user and package.
         mExistingClientRequests.removeAll(
-                findExistingRequests(userId, pendingIntent.getCreatorPackage()));
+                findExistingRequests(userId, callingPackage));
 
         // Add to existing ClientRequests
         mExistingClientRequests.add(
-                new ClientRequest(userId, request, pendingIntent, clientStatusCallback));
+                new ClientRequest(userId, request, callingPackage, observer));
     }
 
     void clientRemoved(int userId, String packageName) {
@@ -167,10 +166,10 @@
     }
 
     @Nullable
-    PendingIntent getPendingIntent(int userId, String packageName) {
+    IAmbientContextObserver getClientRequestObserver(int userId, String packageName) {
         for (ClientRequest clientRequest : mExistingClientRequests) {
             if (clientRequest.hasUserIdAndPackageName(userId, packageName)) {
-                return clientRequest.getPendingIntent();
+                return clientRequest.getObserver();
             }
         }
         return null;
@@ -236,15 +235,13 @@
      * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
      */
     void startDetection(@UserIdInt int userId, AmbientContextEventRequest request,
-            String packageName, RemoteCallback detectionResultCallback,
-            RemoteCallback statusCallback) {
+            String packageName, IAmbientContextObserver observer) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
         synchronized (mLock) {
             final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
             if (service != null) {
-                service.startDetection(request, packageName, detectionResultCallback,
-                        statusCallback);
+                service.startDetection(request, packageName, observer);
             } else {
                 Slog.i(TAG, "service not available for user_id: " + userId);
             }
@@ -297,8 +294,7 @@
                     Slog.d(TAG, "Restoring detection for " + clientRequest.getPackageName());
                     service.startDetection(clientRequest.getRequest(),
                             clientRequest.getPackageName(),
-                            service.createDetectionResultRemoteCallback(),
-                            clientRequest.getClientStatusCallback());
+                            clientRequest.getObserver());
                 }
             }
         }
@@ -328,16 +324,45 @@
             Objects.requireNonNull(request);
             Objects.requireNonNull(resultPendingIntent);
             Objects.requireNonNull(statusCallback);
+            // Wrap the PendingIntent and statusCallback in a IAmbientContextObserver to make the
+            // code unified
+            IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
+                @Override
+                public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
+                    mService.sendDetectionResultIntent(resultPendingIntent, events);
+                }
+
+                @Override
+                public void onRegistrationComplete(int statusCode) throws RemoteException {
+                    AmbientContextManagerPerUserService.sendStatusCallback(statusCallback,
+                            statusCode);
+                }
+            };
+            registerObserverWithCallback(request, resultPendingIntent.getCreatorPackage(),
+                    observer);
+        }
+
+        /**
+         * Register an observer for Ambient Context events.
+         */
+        @Override
+        public void registerObserverWithCallback(AmbientContextEventRequest request,
+                String packageName,
+                IAmbientContextObserver observer) {
+            Slog.i(TAG, "AmbientContextManagerService registerObserverWithCallback.");
+            Objects.requireNonNull(request);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(observer);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
-            assertCalledByPackageOwner(resultPendingIntent.getCreatorPackage());
+            assertCalledByPackageOwner(packageName);
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Service not available.");
-                mService.sendStatusCallback(statusCallback,
+                AmbientContextManagerPerUserService.completeRegistration(observer,
                         AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onRegisterObserver(request, resultPendingIntent, statusCallback);
+            mService.onRegisterObserver(request, packageName, observer);
         }
 
         @Override
@@ -359,7 +384,7 @@
             assertCalledByPackageOwner(callingPackage);
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Detection service not available.");
-                mService.sendStatusToCallback(statusCallback,
+                AmbientContextManagerPerUserService.sendStatusCallback(statusCallback,
                         AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
index ec6c2f0..a3ffcde8 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
@@ -22,13 +22,15 @@
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
 import android.app.ambientcontext.AmbientContextManager;
+import android.app.ambientcontext.IAmbientContextObserver;
 import android.content.ComponentName;
 import android.os.Binder;
 import android.os.RemoteCallback;
+import android.os.RemoteException;
 import android.os.ShellCommand;
-import android.service.ambientcontext.AmbientContextDetectionResult;
 
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * Shell command for {@link AmbientContextManagerService}.
@@ -39,6 +41,7 @@
             new AmbientContextEventRequest.Builder()
                     .addEventType(AmbientContextEvent.EVENT_COUGH)
                     .addEventType(AmbientContextEvent.EVENT_SNORE)
+                    .addEventType(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP)
                     .build();
 
     @NonNull
@@ -50,11 +53,11 @@
 
     /** Callbacks for AmbientContextEventService results used internally for testing. */
     static class TestableCallbackInternal {
-        private AmbientContextDetectionResult mLastResult;
+        private List<AmbientContextEvent> mLastEvents;
         private int mLastStatus;
 
-        public AmbientContextDetectionResult getLastResult() {
-            return mLastResult;
+        public List<AmbientContextEvent> getLastEvents() {
+            return mLastEvents;
         }
 
         public int getLastStatus() {
@@ -62,19 +65,19 @@
         }
 
         @NonNull
-        private RemoteCallback createRemoteDetectionResultCallback() {
-            return new RemoteCallback(result -> {
-                AmbientContextDetectionResult detectionResult =
-                        (AmbientContextDetectionResult) result.get(
-                                AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    mLastResult = detectionResult;
-                    out.println("Detection result available: " + detectionResult);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+        private IAmbientContextObserver createAmbientContextObserver() {
+            return new IAmbientContextObserver.Stub() {
+                @Override
+                public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
+                    mLastEvents = events;
+                    out.println("Detection events available: " + events);
                 }
-            });
+
+                @Override
+                public void onRegistrationComplete(int statusCode) throws RemoteException {
+                    mLastStatus = statusCode;
+                }
+            };
         }
 
         @NonNull
@@ -123,8 +126,7 @@
         final String packageName = getNextArgRequired();
         mService.startDetection(
                 userId, REQUEST, packageName,
-                sTestableCallbackInternal.createRemoteDetectionResultCallback(),
-                sTestableCallbackInternal.createRemoteStatusCallback());
+                sTestableCallbackInternal.createAmbientContextObserver());
         return 0;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java
index 6bb494d..c75abf8 100644
--- a/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java
@@ -21,8 +21,8 @@
 import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.IAmbientContextObserver;
 import android.content.Intent;
-import android.os.RemoteCallback;
 import android.os.UserHandle;
 
 import androidx.test.InstrumentationRegistry;
@@ -30,6 +30,8 @@
 
 import org.junit.Test;
 
+import java.util.List;
+
 /**
  * Unit test for {@link AmbientContextManagerService}.
  * atest FrameworksServicesTests:AmbientContextManagerServiceTest
@@ -48,12 +50,22 @@
         PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 InstrumentationRegistry.getTargetContext(), 0,
                 intent, PendingIntent.FLAG_IMMUTABLE);
+        IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
+            @Override
+            public void onEvents(List<AmbientContextEvent> events) {
+            }
+
+            @Override
+            public void onRegistrationComplete(int statusCode) {
+            }
+        };
         AmbientContextManagerService.ClientRequest clientRequest =
                 new AmbientContextManagerService.ClientRequest(USER_ID, request,
-                        pendingIntent, new RemoteCallback(result -> {}));
+                        pendingIntent.getCreatorPackage(), observer);
 
         assertThat(clientRequest.getRequest()).isEqualTo(request);
         assertThat(clientRequest.getPackageName()).isEqualTo(SYSTEM_PACKAGE_NAME);
+        assertThat(clientRequest.getObserver()).isEqualTo(observer);
         assertThat(clientRequest.hasUserId(USER_ID)).isTrue();
         assertThat(clientRequest.hasUserId(-1)).isFalse();
         assertThat(clientRequest.hasUserIdAndPackageName(USER_ID, SYSTEM_PACKAGE_NAME)).isTrue();