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