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