Add tests for VoiceInteractionService role change

Bug: 170742278
Test: atest VoiceInteractionRoleTest

Change-Id: Ica5f3c533c662017ed39703ff37f8ae65c749084
diff --git a/tests/tests/voiceinteraction/AndroidTest.xml b/tests/tests/voiceinteraction/AndroidTest.xml
index 68501e2..69ae2a9 100644
--- a/tests/tests/voiceinteraction/AndroidTest.xml
+++ b/tests/tests/voiceinteraction/AndroidTest.xml
@@ -27,6 +27,7 @@
         <option name="force-install-mode" value="FULL"/>
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="CtsVoiceInteractionService.apk"/>
+        <option name="test-file-name" value="CtsNoRecognitionVoiceInteractionService.apk"/>
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/voiceinteraction/service/Android.bp b/tests/tests/voiceinteraction/service/Android.bp
index 4aee259..f90a5b4 100644
--- a/tests/tests/voiceinteraction/service/Android.bp
+++ b/tests/tests/voiceinteraction/service/Android.bp
@@ -32,3 +32,18 @@
     ],
     sdk_version: "test_current",
 }
+
+// A test service that doesn't define RecognitionService
+android_test_helper_app {
+    name: "CtsNoRecognitionVoiceInteractionService",
+    defaults: ["cts_support_defaults"],
+    srcs: ["noRecognitionVoiceInteractionService/**/*.java"],
+    resource_dirs: ["noRecognitionVoiceInteractionService/res"],
+    manifest: "noRecognitionVoiceInteractionService/AndroidManifest.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/service/AndroidManifest.xml b/tests/tests/voiceinteraction/service/AndroidManifest.xml
index e47d30b..087a833 100644
--- a/tests/tests/voiceinteraction/service/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/service/AndroidManifest.xml
@@ -18,8 +18,18 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.voiceinteraction.service">
 
-    <application>
+    <application android:label="Voice service">
       <uses-library android:name="android.test.runner" />
+      <service android:name="android.voiceinteraction.service.NoOpVoiceInteractionService"
+              android:label="NoOpVoiceInteractionService has recognition"
+              android:permission="android.permission.BIND_VOICE_INTERACTION"
+              android:exported="true">
+          <meta-data android:name="android.voice_interaction"
+                     android:resource="@xml/has_recognition_interaction_service" />
+          <intent-filter>
+              <action android:name="android.service.voice.VoiceInteractionService" />
+          </intent-filter>
+      </service>
       <service android:name=".MainInteractionService"
               android:label="CTS test voice interaction service"
               android:permission="android.permission.BIND_VOICE_INTERACTION"
diff --git a/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/AndroidManifest.xml b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/AndroidManifest.xml
new file mode 100644
index 0000000..44cd725
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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.voiceinteraction.norecognition">
+
+    <application android:label="no recognition service">
+      <uses-library android:name="android.test.runner"/>
+        <service android:name="android.voiceinteraction.norecognition.NoOpVoiceInteractionService"
+                 android:label="NoOpVoiceInteractionService no recognition"
+                 android:permission="android.permission.BIND_VOICE_INTERACTION"
+                 android:process=":interactor"
+                 android:exported="true">
+            <meta-data android:name="android.voice_interaction"
+                       android:resource="@xml/interaction_no_recognition_service"/>
+            <intent-filter>
+                <action android:name="android.service.voice.VoiceInteractionService"/>
+            </intent-filter>
+        </service>
+        <activity android:name="android.voiceinteraction.norecognition.NoOpActivity"
+                  android:theme="@android:style/Theme.NoDisplay"
+                  android:excludeFromRecents="true"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.ASSIST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/res/xml/interaction_no_recognition_service.xml b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/res/xml/interaction_no_recognition_service.xml
new file mode 100644
index 0000000..260062a
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/res/xml/interaction_no_recognition_service.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="android.voiceinteraction.service.MainInteractionSessionService"
+    android:supportsAssist="true"
+    android:supportsLocalInteraction="true"/>
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/src/android/voiceinteraction/norecognition/NoOpActivity.java b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/src/android/voiceinteraction/norecognition/NoOpActivity.java
new file mode 100644
index 0000000..0790c16
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/src/android/voiceinteraction/norecognition/NoOpActivity.java
@@ -0,0 +1,25 @@
+/*
+ * 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.voiceinteraction.norecognition;
+
+import android.app.Activity;
+
+/**
+ * A Activity that received android.intent.action.ASSIST but do nothing for testing.
+ */
+public final class NoOpActivity extends Activity {
+}
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/src/android/voiceinteraction/norecognition/NoOpVoiceInteractionService.java b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/src/android/voiceinteraction/norecognition/NoOpVoiceInteractionService.java
new file mode 100644
index 0000000..93ac526
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/noRecognitionVoiceInteractionService/src/android/voiceinteraction/norecognition/NoOpVoiceInteractionService.java
@@ -0,0 +1,26 @@
+/*
+ * 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.voiceinteraction.norecognition;
+
+import android.service.voice.VoiceInteractionService;
+
+/**
+ * A {@link VoiceInteractionService} without implementation that is used for role changing testing.
+ */
+public class NoOpVoiceInteractionService extends VoiceInteractionService {
+    // do nothing
+}
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/service/res/xml/has_recognition_interaction_service.xml b/tests/tests/voiceinteraction/service/res/xml/has_recognition_interaction_service.xml
new file mode 100644
index 0000000..5957aeb
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/res/xml/has_recognition_interaction_service.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="android.voiceinteraction.service.MainInteractionSessionService"
+    android:recognitionService="android.voiceinteraction.service.MainRecognitionService"
+    android:settingsActivity="android.voiceinteraction.service.SettingsActivity"
+    android:supportsAssist="true"/>
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/NoOpVoiceInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/NoOpVoiceInteractionService.java
new file mode 100644
index 0000000..8e23102
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/NoOpVoiceInteractionService.java
@@ -0,0 +1,26 @@
+/*
+ * 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.voiceinteraction.common;
+
+import android.service.voice.VoiceInteractionService;
+
+/**
+ * A {@link VoiceInteractionService} without implementation that is used for role changing testing.
+ */
+public class NoOpVoiceInteractionService extends VoiceInteractionService {
+    // do nothing
+}
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionRoleTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionRoleTest.java
new file mode 100644
index 0000000..192e97f
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionRoleTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.voiceinteraction.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for successfully changing ROLE_ASSISTANT. The test focuses on changing ROLE_ASSISTANT role,
+ * the target voice interaction services do nothing during the test.
+ */
+@AppModeFull(reason = "No need for testing role for instant app")
+@RunWith(AndroidJUnit4.class)
+public class VoiceInteractionRoleTest {
+
+    private static final String TAG = "VoiceInteractionRoleTest";
+
+    private static final long TIMEOUT_MILLIS = 15 * 1000;
+    private static final String VOICE_INTERACTION_HAS_RECOGNITION_SERVICE =
+            "android.voiceinteraction.service";
+    private static final String VOICE_INTERACTION_NO_RECOGNITION_SERVICE =
+            "android.voiceinteraction.norecognition";
+
+    private static Context sContext;
+    private static RoleManager sRoleManager;
+
+    List<String> mOriginalRoleHolders;
+
+    @BeforeClass
+    public static void oneTimeSetup() {
+        sContext = ApplicationProvider.getApplicationContext();
+        sRoleManager = sContext.getSystemService(RoleManager.class);
+    }
+
+    @Before
+    public void setup() throws Exception {
+        mOriginalRoleHolders = getAssistRoleHolders();
+    }
+
+    @After
+    public void cleanup() throws Exception {
+        if (mOriginalRoleHolders != null && mOriginalRoleHolders.size() > 0) {
+            // Restore to original, assistant is singleton role
+            addAssistRoleHolder(mOriginalRoleHolders.get(0));
+        }
+    }
+
+    @Test
+    public void testAssistRole_hasRecognitionService() throws Exception {
+        roleTestingForPackage(VOICE_INTERACTION_HAS_RECOGNITION_SERVICE, /* hasRecognition= */
+                true);
+    }
+
+    @Test
+    public void testAssistRole_noRecognitionService() throws Exception {
+        roleTestingForPackage(VOICE_INTERACTION_NO_RECOGNITION_SERVICE, /* hasRecognition= */
+                false);
+    }
+
+    // TODO: Use helpers and move the assertion in Test instead of move together
+    private void roleTestingForPackage(String packageName, boolean hasRecognition)
+            throws Exception {
+        assertThat(getAssistRoleHolders()).doesNotContain(packageName);
+
+        addAssistRoleHolder(packageName);
+        if (mOriginalRoleHolders != null && mOriginalRoleHolders.size() > 0) {
+            String originalHolder = mOriginalRoleHolders.get(0);
+            removeAssistRoleHolder(originalHolder);
+            assertThat(getAssistRoleHolders()).doesNotContain(originalHolder);
+        }
+        assertThat(getAssistRoleHolders()).containsExactly(packageName);
+
+        final String curVoiceInteractionComponentName = Settings.Secure.getString(
+                sContext.getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE);
+        String curVoiceInteractionPackageName = "";
+        if (!TextUtils.isEmpty(curVoiceInteractionComponentName)) {
+            curVoiceInteractionPackageName =
+                    ComponentName.unflattenFromString(
+                            curVoiceInteractionComponentName).getPackageName();
+        }
+        assertThat(curVoiceInteractionPackageName).isEqualTo(hasRecognition ? packageName : "");
+
+        removeAssistRoleHolder(packageName);
+        assertThat(getAssistRoleHolders()).doesNotContain(packageName);
+    }
+
+    private List<String> getAssistRoleHolders() throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> sRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT));
+    }
+
+    private void addAssistRoleHolder(String packageName)
+            throws Exception {
+        Log.i(TAG, "addAssistRoleHolder for " + packageName);
+        final CallbackFuture future = new CallbackFuture("addAssistRoleHolder");
+        runWithShellPermissionIdentity(() -> {
+            sRoleManager.addRoleHolderAsUser(RoleManager.ROLE_ASSISTANT, packageName,
+                    RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, Process.myUserHandle(),
+                    sContext.getMainExecutor(), future);
+        });
+        assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+    }
+
+    private void removeAssistRoleHolder(String packageName)
+            throws Exception {
+        Log.i(TAG, "removeAssistRoleHolder for " + packageName);
+        final CallbackFuture future = new CallbackFuture("removeAssistRoleHolder");
+        runWithShellPermissionIdentity(
+                () -> sRoleManager.removeRoleHolderAsUser(RoleManager.ROLE_ASSISTANT, packageName,
+                        0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+        assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+    }
+
+    private static class CallbackFuture extends CompletableFuture<Boolean>
+            implements Consumer<Boolean> {
+        String mMethodName;
+
+        CallbackFuture(String methodName) {
+            mMethodName = methodName;
+        }
+
+        @Override
+        public void accept(Boolean successful) {
+            Log.i(TAG, mMethodName + " result " + successful);
+            complete(successful);
+        }
+    }
+}