CTS test for AmbientContext API.

Test: atest CtsAmbientContextDetectionServiceDeviceTest
Bug: 192476579
Ignore-AOSP-First: to prevent new feature leak.

Change-Id: Iea4dc3736218916423d703266cd4a1cf0fd3a0c1
diff --git a/tests/ambientcontext/Android.bp b/tests/ambientcontext/Android.bp
new file mode 100644
index 0000000..70f3b48
--- /dev/null
+++ b/tests/ambientcontext/Android.bp
@@ -0,0 +1,52 @@
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+    ],
+}
+
+android_test {
+    name: "CtsAmbientContextDetectionServiceDeviceTestCases",
+    defaults: ["cts_defaults"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+        "compatibility-device-util-axt",
+        "cts-wm-util",
+        "android-common",
+        "android-support-v4",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/ambientcontext/AndroidManifest.xml b/tests/ambientcontext/AndroidManifest.xml
new file mode 100644
index 0000000..51cb791
--- /dev/null
+++ b/tests/ambientcontext/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.ambientaudioevent.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <service
+            android:exported="true"
+            android:name=".CtsTestAmbientSignalProviderService"
+            android:permission="android.permission.BIND_AMBIENT_SIGNAL_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.ambientsignal.AmbientSignalProviderService"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for the Ambient Signal Service APIs."
+        android:targetPackage="android.ambientaudioevent.cts" >
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/ambientcontext/AndroidTest.xml b/tests/ambientcontext/AndroidTest.xml
new file mode 100644
index 0000000..369bebe
--- /dev/null
+++ b/tests/ambientcontext/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<configuration description="Config for CtsAmbientContextDetectionServiceDeviceTestCases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAmbientContextDetectionServiceDeviceTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.ambientaudioevent.cts" />
+    </test>
+</configuration>
diff --git a/tests/ambientcontext/OWNERS b/tests/ambientcontext/OWNERS
new file mode 100644
index 0000000..d085679
--- /dev/null
+++ b/tests/ambientcontext/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 103965
+kxchen@google.com
+enxun@google.com
+tgadh@google.com
\ No newline at end of file
diff --git a/tests/ambientcontext/src/android/ambientcontext/cts/CtsAmbientContextDetectionService.java b/tests/ambientcontext/src/android/ambientcontext/cts/CtsAmbientContextDetectionService.java
new file mode 100644
index 0000000..646d8b8
--- /dev/null
+++ b/tests/ambientcontext/src/android/ambientcontext/cts/CtsAmbientContextDetectionService.java
@@ -0,0 +1,90 @@
+/*
+ * 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.ambientcontext.cts;
+
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.AmbientContextEventResponse;
+import android.service.ambientcontext.AmbientContextDetectionService;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+
+public class CtsAmbientContextDetectionService
+        extends AmbientContextDetectionService {
+    private static final String TAG = "CtsTestAmbientContextEventProviderService";
+
+    private static Consumer<AmbientContextEventResponse> sConsumer;
+    private static CountDownLatch sRespondLatch = new CountDownLatch(1);
+
+    @Override
+    public void onStartDetection(AmbientContextEventRequest request, String packageName,
+            Consumer<AmbientContextEventResponse> consumer) {
+        sConsumer = consumer;
+        sRespondLatch.countDown();
+    }
+
+    @Override
+    public void onStopDetection(String packageName) {
+    }
+
+    public static void reset() {
+        sConsumer = null;
+    }
+
+    public static void respondSuccess(AmbientContextEvent event) {
+        if (sConsumer != null) {
+            AmbientContextEventResponse response = new AmbientContextEventResponse.Builder()
+                    .setStatusCode(AmbientContextEventResponse.STATUS_SUCCESS)
+                    .addEvent(event)
+                    .build();
+            sConsumer.accept(response);
+        }
+        reset();
+    }
+
+    public static void respondFailure(int status) {
+        if (sConsumer != null) {
+            AmbientContextEventResponse response = new AmbientContextEventResponse.Builder()
+                    .setStatusCode(status)
+                    .build();
+            sConsumer.accept(response);
+        }
+        reset();
+    }
+
+    public static boolean hasPendingRequest() {
+        return sConsumer != null;
+    }
+
+    public static void onReceivedResponse() {
+        try {
+            if (!sRespondLatch.await(3000, TimeUnit.MILLISECONDS)) {
+                throw new AssertionError("CtsTestAmbientContextEventProviderService"
+                        + " timed out while expecting a call.");
+            }
+            //reset for next
+            sRespondLatch = new CountDownLatch(1);
+        } catch (InterruptedException e) {
+            Log.e(TAG, e.getMessage());
+            Thread.currentThread().interrupt();
+            throw new AssertionError("Got InterruptedException while waiting for response.");
+        }
+    }
+}
diff --git a/tests/ambientcontext/src/android/ambientcontext/cts/CtsAmbientContextDetectionServiceDeviceTest.java b/tests/ambientcontext/src/android/ambientcontext/cts/CtsAmbientContextDetectionServiceDeviceTest.java
new file mode 100644
index 0000000..09d887e
--- /dev/null
+++ b/tests/ambientcontext/src/android/ambientcontext/cts/CtsAmbientContextDetectionServiceDeviceTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.ambientcontext.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventResponse;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * This suite of test ensures that AmbientContextService behaves correctly when properly
+ * bound to an AmbientContextDetectionService implementation.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(
+        reason = "PM will not recognize CtsTestAmbientContextDetectionService in instantMode.")
+public class CtsAmbientContextDetectionServiceDeviceTest {
+
+    private static final String NAMESPACE_ambient_context = "ambient_context";
+    private static final String KEY_SERVICE_ENABLED = "service_enabled";
+    private static final String FAKE_SERVICE_PACKAGE =
+            CtsAmbientContextDetectionService.class.getPackage().getName();
+    private static final String USER_ID = "0";
+
+    private static final AmbientContextEvent FAKE_EVENT = new AmbientContextEvent.Builder()
+            .setEventType(AmbientContextEvent.EVENT_COUGH)
+            .setConfidenceLevel(AmbientContextEvent.LEVEL_HIGH)
+            .setDensityLevel(AmbientContextEvent.LEVEL_MEDIUM)
+            .build();
+    private static final long TEMPORARY_SERVICE_DURATION = 5000L;
+
+    private final boolean mIsTestable =
+            !TextUtils.isEmpty(getAmbientContextDetectionServiceComponent());
+
+    @Rule
+    public final DeviceConfigStateChangerRule mLookAllTheseRules =
+            new DeviceConfigStateChangerRule(getInstrumentation().getTargetContext(),
+                    NAMESPACE_ambient_context,
+                    KEY_SERVICE_ENABLED,
+                    "true");
+
+    @Before
+    public void setUp() {
+        assumeTrue("VERSION.SDK_INT=" + VERSION.SDK_INT,
+                VERSION.SDK_INT >= VERSION_CODES.TIRAMISU);
+        assumeTrue("Feature not available on this device. Skipping test.", mIsTestable);
+        clearTestableAmbientContextDetectionService();
+        CtsAmbientContextDetectionService.reset();
+        bindToTestService();
+    }
+
+    @After
+    public void tearDown() {
+        clearTestableAmbientContextDetectionService();
+    }
+
+    @Test
+    public void testAmbientContextDetectionService_OnSuccess() {
+        // From manager, call startDetection() on test service
+        assertThat(CtsAmbientContextDetectionService.hasPendingRequest()).isFalse();
+        callStartDetection();
+        assertThat(CtsAmbientContextDetectionService.hasPendingRequest()).isTrue();
+
+        // From test service, respond with onSuccess
+        CtsAmbientContextDetectionService.respondSuccess(FAKE_EVENT);
+
+        // From manager, verify callback was called
+        assertThat(getLastStatusCode()).isEqualTo(AmbientContextEventResponse.STATUS_SUCCESS);
+    }
+
+    @Test
+    public void testAmbientContextDetectionService_OnServiceUnavailable() {
+        // From manager, call startDetection() on test service
+        assertThat(CtsAmbientContextDetectionService.hasPendingRequest()).isFalse();
+        callStartDetection();
+        assertThat(CtsAmbientContextDetectionService.hasPendingRequest()).isTrue();
+
+        // From test service, cancel the request and respond with STATUS_SERVICE_UNAVAILABLE
+        CtsAmbientContextDetectionService.respondFailure(
+                AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
+
+        // From test service, verify that the request was cancelled
+        assertThat(CtsAmbientContextDetectionService.hasPendingRequest()).isFalse();
+
+        // From manager, verify that the callback was called with STATUS_SERVICE_UNAVAILABLE
+        assertThat(getLastStatusCode()).isEqualTo(
+                AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
+    }
+
+    private int getLastStatusCode() {
+        return Integer.parseInt(runShellCommand(
+                "cmd ambient_context get-last-status-code"));
+    }
+
+    private void bindToTestService() {
+        // On Manager, bind to test service
+        assertThat(getAmbientContextDetectionServiceComponent()).isNotEqualTo(FAKE_SERVICE_PACKAGE);
+        setTestableAmbientContextDetectionService(FAKE_SERVICE_PACKAGE);
+        assertThat(getAmbientContextDetectionServiceComponent()).contains(FAKE_SERVICE_PACKAGE);
+    }
+
+    private String getAmbientContextDetectionServiceComponent() {
+        return runShellCommand("cmd ambient_context get-bound-package %s", USER_ID);
+    }
+
+    /**
+     * This call is asynchronous (manager spawns + binds to service and then asynchronously makes a
+     * call).
+     * As such, we need to ensure consistent testing results, by waiting until we receive a response
+     * in our test service w/ CountDownLatch(s).
+     */
+    private void callStartDetection() {
+        runShellCommand("cmd ambient_context start-detection %s %s",
+                USER_ID, FAKE_SERVICE_PACKAGE);
+        CtsAmbientContextDetectionService.onReceivedResponse();
+    }
+
+    private void setTestableAmbientContextDetectionService(String service) {
+        runShellCommand("cmd ambient_context set-temporary-service %s %s %s",
+                USER_ID, service, TEMPORARY_SERVICE_DURATION);
+    }
+
+    private void clearTestableAmbientContextDetectionService() {
+        runShellCommand("cmd ambient_context set-temporary-service %s", USER_ID);
+    }
+}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index a1ab5cc..591d2fe 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3944,6 +3944,17 @@
         android:protectionLevel="signature" />
     <uses-permission android:name="android.permission.BIND_ROTATION_RESOLVER_SERVICE" />
 
+    <!-- @SystemApi Allows an application to access ambient context service.
+         @hide <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"
+        android:protectionLevel="internal|role"/>
+
+    <!-- @SystemApi Required by a AmbientContextEventDetectionService
+         to ensure that only the service with this permission can bind to it.
+         @hide <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
+        android:protectionLevel="signature"/>
+
     <!-- Must be required by a {@link android.net.VpnService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature