Adding extender builder to the RemoteInput.Builder that supports Wear-specific extras

This is migration of RemoteInputConstants from WSL.

Bug: 181296581
Test: WearableRemoteInputExtenderTest
Relnote: "Added WearableRemoteInputExtender class that can be used for adding Wear-specific extras to the android.app.RemoteInput."

Change-Id: I0190310bcd43c98793bda31a7ef055e1abad815e
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 4da9f9c..ef97193 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -268,6 +268,7 @@
     samples(project(":wear:compose:compose-material-samples"))
     docs(project(":wear:wear-input"))
     docs(project(":wear:wear-input-testing"))
+    samples(project(":wear:wear-input-samples"))
     docs(project(":wear:wear-ongoing"))
     docs(project(":wear:wear-phone-interactions"))
     docs(project(":wear:wear-remote-interactions"))
diff --git a/settings.gradle b/settings.gradle
index 12807502..125fcf2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -652,6 +652,7 @@
 includeProject(":wear:compose:integration-tests:macrobenchmark", "wear/compose/integration-tests/macrobenchmark", [BuildType.COMPOSE])
 includeProject(":wear:compose:integration-tests:macrobenchmark-target", "wear/compose/integration-tests/macrobenchmark-target", [BuildType.COMPOSE])
 includeProject(":wear:wear-input", "wear/wear-input", [BuildType.MAIN, BuildType.WEAR])
+includeProject(":wear:wear-input-samples", "wear/wear-input/samples", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-input-testing", "wear/wear-input-testing", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-ongoing", "wear/wear-ongoing", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-phone-interactions", "wear/wear-phone-interactions", [BuildType.MAIN, BuildType.WEAR])
diff --git a/wear/wear-input/api/current.txt b/wear/wear-input/api/current.txt
index 5909312..bc0107b 100644
--- a/wear/wear-input/api/current.txt
+++ b/wear/wear-input/api/current.txt
@@ -63,5 +63,16 @@
     method public android.os.Bundle getButtonInfo(android.content.Context, int);
   }
 
+  public final class WearableRemoteInputExtender {
+    ctor public WearableRemoteInputExtender(android.app.RemoteInput.Builder remoteInput);
+    method public androidx.wear.input.WearableRemoteInputExtender disallowEmoji();
+    method public android.app.RemoteInput.Builder get();
+    method public androidx.wear.input.WearableRemoteInputExtender setInputActionType(int imeActionType);
+  }
+
+  public final class WearableRemoteInputExtenderKt {
+    method public static android.app.RemoteInput.Builder wearableExtender(android.app.RemoteInput.Builder, kotlin.jvm.functions.Function1<? super androidx.wear.input.WearableRemoteInputExtender,kotlin.Unit> block);
+  }
+
 }
 
diff --git a/wear/wear-input/api/public_plus_experimental_current.txt b/wear/wear-input/api/public_plus_experimental_current.txt
index 5909312..bc0107b 100644
--- a/wear/wear-input/api/public_plus_experimental_current.txt
+++ b/wear/wear-input/api/public_plus_experimental_current.txt
@@ -63,5 +63,16 @@
     method public android.os.Bundle getButtonInfo(android.content.Context, int);
   }
 
+  public final class WearableRemoteInputExtender {
+    ctor public WearableRemoteInputExtender(android.app.RemoteInput.Builder remoteInput);
+    method public androidx.wear.input.WearableRemoteInputExtender disallowEmoji();
+    method public android.app.RemoteInput.Builder get();
+    method public androidx.wear.input.WearableRemoteInputExtender setInputActionType(int imeActionType);
+  }
+
+  public final class WearableRemoteInputExtenderKt {
+    method public static android.app.RemoteInput.Builder wearableExtender(android.app.RemoteInput.Builder, kotlin.jvm.functions.Function1<? super androidx.wear.input.WearableRemoteInputExtender,kotlin.Unit> block);
+  }
+
 }
 
diff --git a/wear/wear-input/api/restricted_current.txt b/wear/wear-input/api/restricted_current.txt
index 5909312..bc0107b 100644
--- a/wear/wear-input/api/restricted_current.txt
+++ b/wear/wear-input/api/restricted_current.txt
@@ -63,5 +63,16 @@
     method public android.os.Bundle getButtonInfo(android.content.Context, int);
   }
 
+  public final class WearableRemoteInputExtender {
+    ctor public WearableRemoteInputExtender(android.app.RemoteInput.Builder remoteInput);
+    method public androidx.wear.input.WearableRemoteInputExtender disallowEmoji();
+    method public android.app.RemoteInput.Builder get();
+    method public androidx.wear.input.WearableRemoteInputExtender setInputActionType(int imeActionType);
+  }
+
+  public final class WearableRemoteInputExtenderKt {
+    method public static android.app.RemoteInput.Builder wearableExtender(android.app.RemoteInput.Builder, kotlin.jvm.functions.Function1<? super androidx.wear.input.WearableRemoteInputExtender,kotlin.Unit> block);
+  }
+
 }
 
diff --git a/wear/wear-input/build.gradle b/wear/wear-input/build.gradle
index b205ac4..aa5dc2e 100644
--- a/wear/wear-input/build.gradle
+++ b/wear/wear-input/build.gradle
@@ -37,6 +37,7 @@
     testImplementation(project(":wear:wear-input-testing"))
 
     compileOnly(fileTree(dir: "../wear_stubs", include: ["com.google.android.wearable-stubs.jar"]))
+    compileOnly(project(":annotation:annotation-sampled"))
 }
 
 android {
diff --git a/wear/wear-input/samples/build.gradle b/wear/wear-input/samples/build.gradle
new file mode 100644
index 0000000..d3518ce
--- /dev/null
+++ b/wear/wear-input/samples/build.gradle
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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.
+ */
+
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+import androidx.build.LibraryVersions
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+dependencies {
+    api("androidx.annotation:annotation:1.1.0")
+    api(libs.kotlinStdlib)
+    api(project(":wear:wear-input"))
+
+    compileOnly(project(":annotation:annotation-sampled"))
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 25
+    }
+}
+
+androidx {
+    name = "Android Wear Input Samples"
+    type = LibraryType.SAMPLES
+    mavenGroup = LibraryGroups.WEAR
+    mavenVersion = LibraryVersions.WEAR_INPUT
+    inceptionYear = "2021"
+    description = "Contains the sample code for the Android Wear Input Classes"
+}
diff --git a/wear/wear-input/samples/src/main/AndroidManifest.xml b/wear/wear-input/samples/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..474f309
--- /dev/null
+++ b/wear/wear-input/samples/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="androidx.wear.input.samples">
+</manifest>
\ No newline at end of file
diff --git a/wear/wear-input/samples/src/main/java/androidx/wear/input/samples/WearableRemoteInputExtenderSample.kt b/wear/wear-input/samples/src/main/java/androidx/wear/input/samples/WearableRemoteInputExtenderSample.kt
new file mode 100644
index 0000000..4dcf433
--- /dev/null
+++ b/wear/wear-input/samples/src/main/java/androidx/wear/input/samples/WearableRemoteInputExtenderSample.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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 androidx.wear.input.samples
+
+import android.app.RemoteInput
+import android.view.inputmethod.EditorInfo
+import androidx.annotation.Sampled
+import androidx.wear.input.wearableExtender
+
+@Sampled
+internal fun extenderSample() {
+    RemoteInput.Builder("resultKey")
+        .setAllowFreeFormInput(true)
+        .wearableExtender {
+            disallowEmoji()
+            setInputActionType(EditorInfo.IME_ACTION_GO)
+        }.build()
+}
\ No newline at end of file
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/WearableRemoteInputExtender.kt b/wear/wear-input/src/main/java/androidx/wear/input/WearableRemoteInputExtender.kt
new file mode 100644
index 0000000..10b8a43
--- /dev/null
+++ b/wear/wear-input/src/main/java/androidx/wear/input/WearableRemoteInputExtender.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 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 androidx.wear.input
+
+import android.app.RemoteInput
+import android.os.Bundle
+import android.view.inputmethod.EditorInfo
+import androidx.annotation.VisibleForTesting
+
+/**
+ * Extender for Wear-specific extras for a [RemoteInput] instance.
+ *
+ * For example, to create a RemoteInput that will allow free form input (e.g. voice input on Wear),
+ * but not show the Draw Emoji option:
+ *
+ * @sample androidx.wear.input.samples.extenderSample
+ */
+public class WearableRemoteInputExtender(private var remoteInput: RemoteInput.Builder) {
+    private var extras = Bundle()
+
+    /**
+     * Adding extra to a [RemoteInput] that causes emoji-only options (e.g. the Draw Emoji option)
+     * to not be shown.
+     *
+     * If it is set, the Draw Emoji option will not be shown. If it is not set, the Draw Emoji
+     * option will be shown as long as the [RemoteInput] allows free form input.
+     */
+    public fun disallowEmoji(): WearableRemoteInputExtender = apply {
+        extras.putBoolean(EXTRA_DISALLOW_EMOJI, true)
+    }
+
+    /**
+     * Adding specified input action type to a [RemoteInput] to modify the action type of the
+     * RemoteInput session (e.g. "send" or "search"). The default action type is "send."
+     *
+     * @param imeActionType Action type to be set on RemoteInput session. Should be one of the
+     * following values: [EditorInfo.IME_ACTION_SEND], [EditorInfo.IME_ACTION_SEARCH],
+     * [EditorInfo.IME_ACTION_DONE], [EditorInfo.IME_ACTION_GO]. If not, send action will be set.
+     */
+    public fun setInputActionType(imeActionType: Int): WearableRemoteInputExtender = apply {
+        extras.putInt(
+            EXTRA_INPUT_ACTION_TYPE, getInputActionTypeForImeActionType(imeActionType)
+        )
+    }
+
+    /**
+     * Returns the [RemoteInput.Builder] with set options.
+     */
+    public fun get(): RemoteInput.Builder = remoteInput.addExtras(extras)
+
+    @VisibleForTesting
+    internal companion object {
+        /**
+         * Key for a boolean extra that can be added to a [RemoteInput] to cause emoji-only
+         * options (e.g. the Draw Emoji option) to not be shown.
+         *
+         * If this extra has value true, the Draw Emoji option will not be shown. If this extra
+         * is not present or has any other value, the Draw Emoji option will be shown as long as
+         * the [RemoteInput] allows free form input.
+         */
+        @VisibleForTesting
+        internal const val EXTRA_DISALLOW_EMOJI =
+            "android.support.wearable.input.extra.DISALLOW_EMOJI"
+
+        /**
+         * Key for an integer extra that can be added to a [RemoteInput] to modify the action
+         * type of the RemoteInput session (e.g. "send" or "search"). The default action type is
+         * "send."
+         */
+        @VisibleForTesting
+        internal const val EXTRA_INPUT_ACTION_TYPE =
+            "android.support.wearable.input.extra.INPUT_ACTION_TYPE"
+
+        /** Send action type. */
+        @VisibleForTesting
+        internal const val INPUT_ACTION_TYPE_SEND = 0
+
+        /** Search action type. */
+        @VisibleForTesting
+        internal const val INPUT_ACTION_TYPE_SEARCH = 1
+
+        /** Done action type. */
+        @VisibleForTesting
+        internal const val INPUT_ACTION_TYPE_DONE = 2
+
+        /** Go action type. */
+        @VisibleForTesting
+        internal const val INPUT_ACTION_TYPE_GO = 3
+
+        internal fun getInputActionTypeForImeActionType(imeActionType: Int) = when (imeActionType) {
+            EditorInfo.IME_ACTION_SEND -> INPUT_ACTION_TYPE_SEND
+            EditorInfo.IME_ACTION_SEARCH -> INPUT_ACTION_TYPE_SEARCH
+            EditorInfo.IME_ACTION_DONE -> INPUT_ACTION_TYPE_DONE
+            EditorInfo.IME_ACTION_GO -> INPUT_ACTION_TYPE_GO
+            // If any other action is passed which is not support on Wear OS, use the default
+            // send action.
+            else -> INPUT_ACTION_TYPE_SEND
+        }
+    }
+}
+
+public fun RemoteInput.Builder.wearableExtender(block: WearableRemoteInputExtender.() -> Unit):
+    RemoteInput.Builder =
+        WearableRemoteInputExtender(this).apply { block() }.get()
\ No newline at end of file
diff --git a/wear/wear-input/src/test/java/androidx/wear/input/WearableRemoteInputExtenderTest.kt b/wear/wear-input/src/test/java/androidx/wear/input/WearableRemoteInputExtenderTest.kt
new file mode 100644
index 0000000..ccc0560
--- /dev/null
+++ b/wear/wear-input/src/test/java/androidx/wear/input/WearableRemoteInputExtenderTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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 androidx.wear.input
+
+import android.app.RemoteInput
+import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
+import android.view.inputmethod.EditorInfo.IME_ACTION_GO
+import android.view.inputmethod.EditorInfo.IME_ACTION_NEXT
+import android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH
+import android.view.inputmethod.EditorInfo.IME_ACTION_SEND
+import androidx.wear.input.WearableRemoteInputExtender.Companion.INPUT_ACTION_TYPE_DONE
+import androidx.wear.input.WearableRemoteInputExtender.Companion.INPUT_ACTION_TYPE_GO
+import androidx.wear.input.WearableRemoteInputExtender.Companion.INPUT_ACTION_TYPE_SEARCH
+import androidx.wear.input.WearableRemoteInputExtender.Companion.INPUT_ACTION_TYPE_SEND
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(WearInputTestRunner::class)
+class WearableRemoteInputExtenderTest {
+    @Test
+    fun testDisallowEmoji_setTrue() {
+        val remoteInput = RemoteInput.Builder("resultKey")
+            .wearableExtender {
+                disallowEmoji()
+            }.build()
+
+        assertTrue(
+            remoteInput.extras.getBoolean(WearableRemoteInputExtender.EXTRA_DISALLOW_EMOJI)
+        )
+        // Test that input action type is not set.
+        assertEquals(
+            -1, remoteInput.extras.getInt(WearableRemoteInputExtender.EXTRA_INPUT_ACTION_TYPE, -1)
+        )
+    }
+
+    @Test
+    fun testDisallowEmoji_notSet() {
+        val remoteInput: RemoteInput = RemoteInput.Builder("resultKey")
+            .wearableExtender {
+                // empty
+            }.build()
+
+        assertFalse(
+            remoteInput.extras.getBoolean(WearableRemoteInputExtender.EXTRA_DISALLOW_EMOJI)
+        )
+        // Test that input action type is not set.
+        assertEquals(
+            -1, remoteInput.extras.getInt(WearableRemoteInputExtender.EXTRA_INPUT_ACTION_TYPE, -1)
+        )
+    }
+
+    @Test
+    fun testSetInputActionType() {
+        for ((ime, iat) in imeToActionTypeMap) {
+            val remoteInput = RemoteInput.Builder("resultKey")
+                .wearableExtender {
+                    setInputActionType(ime)
+                }.build()
+
+            assertEquals(
+                iat,
+                remoteInput.extras.getInt(WearableRemoteInputExtender.EXTRA_INPUT_ACTION_TYPE),
+            )
+
+            // Test that disallowing emoji is not set.
+            assertFalse(
+                remoteInput.extras.getBoolean(WearableRemoteInputExtender.EXTRA_DISALLOW_EMOJI)
+            )
+        }
+    }
+
+    @Test
+    fun testDisallowEmoji_SetInputActionType() {
+        val remoteInput = RemoteInput.Builder("resultKey")
+            .wearableExtender {
+                disallowEmoji()
+                setInputActionType(IME_ACTION_GO)
+            }.build()
+
+        assertTrue(
+            remoteInput.extras.getBoolean(WearableRemoteInputExtender.EXTRA_DISALLOW_EMOJI)
+        )
+        assertEquals(
+            INPUT_ACTION_TYPE_GO,
+            remoteInput.extras.getInt(WearableRemoteInputExtender.EXTRA_INPUT_ACTION_TYPE)
+        )
+    }
+
+    companion object {
+        val imeToActionTypeMap = hashMapOf(
+            IME_ACTION_SEND to INPUT_ACTION_TYPE_SEND,
+            IME_ACTION_SEARCH to INPUT_ACTION_TYPE_SEARCH,
+            IME_ACTION_DONE to INPUT_ACTION_TYPE_DONE,
+            IME_ACTION_GO to INPUT_ACTION_TYPE_GO,
+            IME_ACTION_NEXT to INPUT_ACTION_TYPE_SEND // other value
+        )
+    }
+}
\ No newline at end of file