Introduce VisualTransform to the InputField

VisualTransform is a filter for changing visual output of the InputField.
This can be used for password field or credit card number input, etc.

Bug: 137785986
Test: ./gradlew test
Change-Id: Id65d6ec96c3b3a9fd9d9d40240e774dcd698ccf2
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 1ecc48eb..4695ff5 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,5 +1,7 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
+    <option name="RIGHT_MARGIN" value="100" />
+    <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
     <JavaCodeStyleSettings>
       <option name="FIELD_NAME_PREFIX" value="m" />
       <option name="STATIC_FIELD_NAME_PREFIX" value="s" />
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 03661d9..79ee123 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,6 +1,5 @@
 <component name="ProjectCodeStyleConfiguration">
   <state>
     <option name="USE_PER_PROJECT_SETTINGS" value="true" />
-    <option name="PREFERRED_PROJECT_CODE_STYLE" value="AndroidStyle" />
   </state>
 </component>
\ No newline at end of file
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -34,7 +34,7 @@
 
   public final class InputFieldKt {
     ctor public InputFieldKt();
-    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
   }
 
   public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
     property public abstract Object? parentData;
   }
 
+  public interface OffsetMap {
+    method public int originalToTransformed(int offset);
+    method public int transformedToOriginal(int offset);
+  }
+
   public final class OpacityKt {
     ctor public OpacityKt();
     method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
     method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+    ctor public PasswordVisualTransformation(char mask);
+    ctor public PasswordVisualTransformation();
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+    method public char getMask();
+  }
+
   public abstract class Placeable {
     ctor public Placeable();
     method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
     method public androidx.ui.core.TextSpanComposition getComposer();
   }
 
+  public final class TransformedText {
+    ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.text.AnnotatedString component1();
+    method public androidx.ui.core.OffsetMap component2();
+    method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.core.OffsetMap getOffsetMap();
+    method public androidx.ui.text.AnnotatedString getTransformedText();
+  }
+
+  public interface VisualTransformation {
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+  }
+
+  public final class VisualTransformationKt {
+    ctor public VisualTransformationKt();
+  }
+
   public final class WrapperKt {
     ctor public WrapperKt();
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -34,7 +34,7 @@
 
   public final class InputFieldKt {
     ctor public InputFieldKt();
-    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
   }
 
   public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
     property public abstract Object? parentData;
   }
 
+  public interface OffsetMap {
+    method public int originalToTransformed(int offset);
+    method public int transformedToOriginal(int offset);
+  }
+
   public final class OpacityKt {
     ctor public OpacityKt();
     method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
     method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+    ctor public PasswordVisualTransformation(char mask);
+    ctor public PasswordVisualTransformation();
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+    method public char getMask();
+  }
+
   public abstract class Placeable {
     ctor public Placeable();
     method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
     method public androidx.ui.core.TextSpanComposition getComposer();
   }
 
+  public final class TransformedText {
+    ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.text.AnnotatedString component1();
+    method public androidx.ui.core.OffsetMap component2();
+    method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.core.OffsetMap getOffsetMap();
+    method public androidx.ui.text.AnnotatedString getTransformedText();
+  }
+
+  public interface VisualTransformation {
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+  }
+
+  public final class VisualTransformationKt {
+    ctor public VisualTransformationKt();
+  }
+
   public final class WrapperKt {
     ctor public WrapperKt();
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -34,7 +34,7 @@
 
   public final class InputFieldKt {
     ctor public InputFieldKt();
-    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
   }
 
   public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
     property public abstract Object? parentData;
   }
 
+  public interface OffsetMap {
+    method public int originalToTransformed(int offset);
+    method public int transformedToOriginal(int offset);
+  }
+
   public final class OpacityKt {
     ctor public OpacityKt();
     method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
     method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+    ctor public PasswordVisualTransformation(char mask);
+    ctor public PasswordVisualTransformation();
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+    method public char getMask();
+  }
+
   public abstract class Placeable {
     ctor public Placeable();
     method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
     method public androidx.ui.core.TextSpanComposition getComposer();
   }
 
+  public final class TransformedText {
+    ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.text.AnnotatedString component1();
+    method public androidx.ui.core.OffsetMap component2();
+    method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.core.OffsetMap getOffsetMap();
+    method public androidx.ui.text.AnnotatedString getTransformedText();
+  }
+
+  public interface VisualTransformation {
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+  }
+
+  public final class VisualTransformationKt {
+    ctor public VisualTransformationKt();
+  }
+
   public final class WrapperKt {
     ctor public WrapperKt();
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index cbd3f86..b53ffe0 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -34,7 +34,7 @@
 
   public final class InputFieldKt {
     ctor public InputFieldKt();
-    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {});
+    method public static void InputField(androidx.ui.input.EditorState value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorState,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
   }
 
   public final class IntrinsicMeasurementsReceiver implements androidx.ui.core.DensityReceiver {
@@ -92,6 +92,11 @@
     property public abstract Object? parentData;
   }
 
+  public interface OffsetMap {
+    method public int originalToTransformed(int offset);
+    method public int transformedToOriginal(int offset);
+  }
+
   public final class OpacityKt {
     ctor public OpacityKt();
     method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -102,6 +107,13 @@
     method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
+    ctor public PasswordVisualTransformation(char mask);
+    ctor public PasswordVisualTransformation();
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+    method public char getMask();
+  }
+
   public abstract class Placeable {
     ctor public Placeable();
     method public abstract androidx.ui.core.IntPx getHeight();
@@ -174,6 +186,23 @@
     method public androidx.ui.core.TextSpanComposition getComposer();
   }
 
+  public final class TransformedText {
+    ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.text.AnnotatedString component1();
+    method public androidx.ui.core.OffsetMap component2();
+    method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
+    method public androidx.ui.core.OffsetMap getOffsetMap();
+    method public androidx.ui.text.AnnotatedString getTransformedText();
+  }
+
+  public interface VisualTransformation {
+    method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
+  }
+
+  public final class VisualTransformationKt {
+    ctor public VisualTransformationKt();
+  }
+
   public final class WrapperKt {
     ctor public WrapperKt();
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
index f834987..b9bb9e9 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/InputField.kt
@@ -31,7 +31,6 @@
 import androidx.ui.input.EditorState
 import androidx.ui.input.ImeAction
 import androidx.ui.input.KeyboardType
-import androidx.ui.text.AnnotatedString
 import androidx.ui.text.TextPainter
 import androidx.ui.text.TextStyle
 
@@ -100,7 +99,12 @@
     onValueChange: (EditorState) -> Unit = {},
 
     /** Called when the InputMethod requested an IME action */
-    onImeActionPerformed: (ImeAction) -> Unit = {} // TODO(nona): Define argument type
+    onImeActionPerformed: (ImeAction) -> Unit = {},
+
+    /**
+     * Optional visual filter for changing visual output of input field.
+     */
+    visualTransformation: VisualTransformation? = null
 ) {
     // Ambients
     val style = +ambient(CurrentTextStyleAmbient)
@@ -111,10 +115,13 @@
     // Memos
     val processor = +memo { EditProcessor() }
     val mergedStyle = style.merge(editorStyle.textStyle)
-    val textPainter = +memo(value, mergedStyle, density, resourceLoader) {
+    val (visualText, offsetMap) = +memo(value, visualTransformation) {
+        InputFieldDelegate.applyVisualFilter(value, visualTransformation)
+    }
+    val textPainter = +memo(visualText, mergedStyle, density, resourceLoader) {
         // TODO(nona): Add parameter for text direction, softwrap, etc.
         TextPainter(
-            text = AnnotatedString(text = value.text),
+            text = visualText,
             style = mergedStyle,
             density = density,
             resourceLoader = resourceLoader
@@ -145,7 +152,8 @@
                         textPainter,
                         coords,
                         textInputService,
-                        hasFocus.value
+                        hasFocus.value,
+                        offsetMap
                     )
                 }
             }
@@ -158,7 +166,14 @@
                 onValueChange)
         },
         onDragAt = { InputFieldDelegate.onDragAt(it) },
-        onRelease = { InputFieldDelegate.onRelease(it, textPainter, processor, onValueChange) }
+        onRelease = {
+            InputFieldDelegate.onRelease(
+                it,
+                textPainter,
+                processor,
+                offsetMap,
+                onValueChange)
+        }
     ) {
         Layout(
             children = @Composable {
@@ -172,13 +187,15 @@
                             textPainter,
                             it,
                             textInputService,
-                            hasFocus.value
+                            hasFocus.value,
+                            offsetMap
                         )
                     }
                 }
                 Draw { canvas, _ -> InputFieldDelegate.draw(
                     canvas,
                     value,
+                    offsetMap,
                     textPainter,
                     hasFocus.value,
                     editorStyle) }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
index a6552d5..907545b 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/InputFieldDelegate.kt
@@ -72,6 +72,7 @@
          *
          * @param canvas The target canvas.
          * @param value The editor state
+         * @param offsetMap The offset map
          * @param textPainter The text painter
          * @param hasFocus true if this widget is focused, otherwise false
          * @param editorStyle The editor style.
@@ -80,26 +81,28 @@
         fun draw(
             canvas: Canvas,
             value: EditorState,
+            offsetMap: OffsetMap,
             textPainter: TextPainter,
             hasFocus: Boolean,
             editorStyle: EditorStyle
         ) {
             value.composition?.let {
                 textPainter.paintBackground(
-                    it.start,
-                    it.end,
+                    offsetMap.originalToTransformed(it.start),
+                    offsetMap.originalToTransformed(it.end),
                     editorStyle.compositionColor,
                     canvas
                 )
             }
             if (value.selection.collapsed) {
                 if (hasFocus) {
-                    textPainter.paintCursor(value.selection.start, canvas)
+                    textPainter.paintCursor(
+                        offsetMap.originalToTransformed(value.selection.start), canvas)
                 }
             } else {
                 textPainter.paintBackground(
-                    value.selection.start,
-                    value.selection.end,
+                    offsetMap.originalToTransformed(value.selection.start),
+                    offsetMap.originalToTransformed(value.selection.end),
                     editorStyle.selectionColor,
                     canvas
                 )
@@ -118,16 +121,17 @@
             textPainter: TextPainter,
             layoutCoordinates: LayoutCoordinates,
             textInputService: TextInputService,
-            hasFocus: Boolean
+            hasFocus: Boolean,
+            offsetMap: OffsetMap
         ) {
             if (!hasFocus) {
                 return
             }
 
             val bbox = if (value.selection.end < value.text.length) {
-                textPainter.getBoundingBox(value.selection.end)
+                textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end))
             } else if (value.selection.end != 0) {
-                textPainter.getBoundingBox(value.selection.end - 1)
+                textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end) - 1)
             } else {
                 Rect(0f, 0f, 1.0f, textPainter.preferredLineHeight)
             }
@@ -186,6 +190,7 @@
          * @param position The event position in widget coordinate.
          * @param textPainter The text painter
          * @param editProcessor The edit processor
+         * @param offsetMap The offset map
          * @param onValueChange The callback called when the new editor state arrives.
          */
         @JvmStatic
@@ -193,9 +198,10 @@
             position: PxPosition,
             textPainter: TextPainter,
             editProcessor: EditProcessor,
+            offsetMap: OffsetMap,
             onValueChange: (EditorState) -> Unit
         ) {
-            val offset = textPainter.getOffsetForPosition(position)
+            val offset = offsetMap.transformedToOriginal(textPainter.getOffsetForPosition(position))
             onEditCommand(listOf(SetSelectionEditOp(offset, offset)), editProcessor, onValueChange)
         }
 
@@ -207,7 +213,7 @@
          * @param editProcessor The edit processor
          * @param keyboardType The keyboard type
          * @param onValueChange The callback called when the new editor state arrives.
-         * @param onEditorActionPerformed The callback called when the editor action arrives.
+         * @param onImeActionPerformed The callback called when the editor action arrives.
          */
         @JvmStatic
         fun onFocus(
@@ -243,5 +249,21 @@
             onEditCommand(listOf(FinishComposingTextEditOp()), editProcessor, onValueChange)
             textInputService?.stopInput()
         }
+
+        /**
+         * Helper function of applying visual transformation method to the EditorState.
+         *
+         * @param value An editor state
+         * @param visualTransformation A visual transformation
+         */
+        @JvmStatic
+        fun applyVisualFilter(
+            value: EditorState,
+            visualTransformation: VisualTransformation?
+        ): TransformedText {
+            val annotatedString = AnnotatedString(value.text)
+            return visualTransformation?.filter(annotatedString)
+                    ?: TransformedText(annotatedString, identityOffsetMap)
+        }
     }
 }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt b/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
new file mode 100644
index 0000000..23ff8a6
--- /dev/null
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019 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.ui.core
+
+import androidx.ui.text.AnnotatedString
+
+/**
+ * The map interface used for bidirectional offset mapping from original to transformed text.
+ */
+interface OffsetMap {
+    /**
+     * Convert offset in original text into the offset in transformed text.
+     *
+     * This function must be a monotonically non-decreasing function. In other words, if a cursor
+     * advances in the original text, the cursor in the transformed text must advance or stay there.
+     *
+     * @param offset offset in original text.
+     * @return offset in transformed text
+     * @see VisualTransformation
+     */
+    fun originalToTransformed(offset: Int): Int
+
+    /**
+     * Convert offset in transformed text into the offset in original text.
+     *
+     * This function must be a monotonically non-decreasing function. In other words, if a cursor
+     * advances in the transformed text, the cusrsor in the original text must advance or stay
+     * there.
+     *
+     * @param offset offset in transformed text
+     * @return offset in original text
+     * @see VisualTransformation
+     */
+    fun transformedToOriginal(offset: Int): Int
+}
+
+/**
+ * The transformed text with offset offset mapping
+ */
+data class TransformedText(
+    /**
+     * The transformed text
+     */
+    val transformedText: AnnotatedString,
+
+    /**
+     * The map used for bidirectional offset mapping from original to transformed text.
+     */
+    val offsetMap: OffsetMap
+)
+
+/**
+ * Interface used for changing visual output of the input field.
+ *
+ * This interface can be used for changing visual output of the text in the input field.
+ * For example, you can mask characters in password filed with asterisk with
+ * PasswordVisualTransformation.
+ */
+interface VisualTransformation {
+    /**
+     * Change the visual output of given text.
+     *
+     * Note that the returned text length can be different length from the given text. The widget
+     * will call the offset translator for converting offsets for various reasons, cursor drawing
+     * position, text selection by gesture, etc.
+     *
+     * Example: Credit Card Visual Output (inserting hyphens each 4 digits)
+     *  original text   : 1234567890123456
+     *  transformed text: 1234-5678-9012-3456
+     *
+     *  Then, the offset translator should ignore the hyphen characters, so conversion from
+     *  original offset to transformed text works like
+     *  - The 4th char of the original text is 5th char in the transformed text.
+     *  - The 13th char of the original text is 15th char in the transformed text.
+     *  Similarly, the reverse conversion works like
+     *  - The 5th char of the transformed text is 4th char in the original text.
+     *  - The 12th char of the transformed text is 10th char in the original text.
+     *
+     *  The reference implementation would be like as follows:
+     *  <pre>
+     *  val creditCardOffsetTranslator = object : OffsetMap {
+     *      override fun originalToTransformed(originalOffset: Int): Int {
+     *          if (originalOffset <= 3) return originalOffset
+     *          if (originalOffset <= 7) return originalOffset + 1
+     *          if (originalOffset <= 11) return originalOffset + 2
+     *          if (originalOffset <= 16) return originalOffset + 3
+     *          return 19
+     *      }
+     *
+     *      override fun transformedToOriginal(transformedOffset: Int): Int {
+     *          if (transformedOffset <= 4) return transformedOffset
+     *          if (transformedOffset <= 9) return transformedOffset - 1
+     *          if (transformedOffset <= 14) return transformedOffset - 2
+     *          if (transformedOffset <= 19) return transformedOffset - 3
+     *          return 16
+     *      }
+     *  }
+     *  </pre>
+     *
+     * TODO(nona): Add paragraph direction argument for determining offset conversion.
+     *
+     * @param text The original text
+     * @return the pair of filtered text and offset translator.
+     */
+    fun filter(text: AnnotatedString): TransformedText
+}
+
+/**
+ * The Visual Filter can be used for password Input Field.
+ *
+ * Note that this visual filter only works for ASCII characters.
+ *
+ * @param mask The mask character used instead of original text.
+ */
+class PasswordVisualTransformation(val mask: Char = '\u2022') : VisualTransformation {
+    override fun filter(text: AnnotatedString): TransformedText {
+        return TransformedText(AnnotatedString(Character.toString(mask).repeat(text.text.length)),
+            identityOffsetMap)
+    }
+}
+
+/**
+ * The offset map used for identity mapping.
+ */
+internal val identityOffsetMap = object : OffsetMap {
+    override fun originalToTransformed(offset: Int): Int = offset
+    override fun transformedToOriginal(offset: Int): Int = offset
+}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
index 8877c86..8f9ccfa 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/InputFieldDelegateTest.kt
@@ -59,6 +59,37 @@
     private lateinit var textInputService: TextInputService
     private lateinit var layoutCoordinates: LayoutCoordinates
 
+    val creditCardOffsetTranslator = object : OffsetMap {
+        override fun originalToTransformed(offset: Int): Int {
+            if (offset <= 3) return offset
+            if (offset <= 7) return offset + 1
+            if (offset <= 11) return offset + 2
+            if (offset <= 16) return offset + 3
+            return 19
+        }
+
+        override fun transformedToOriginal(offset: Int): Int {
+            if (offset <= 4) return offset
+            if (offset <= 9) return offset - 1
+            if (offset <= 14) return offset - 2
+            if (offset <= 19) return offset - 3
+            return 16
+        }
+    }
+
+    private val identityOffsetMap = object : OffsetMap {
+        override fun originalToTransformed(offset: Int): Int = offset
+        override fun transformedToOriginal(offset: Int): Int = offset
+    }
+
+    /**
+     * Test implementation of offset map which doubles the offset in transformed text.
+     */
+    private val skippingOffsetMap = object : OffsetMap {
+        override fun originalToTransformed(offset: Int): Int = offset * 2
+        override fun transformedToOriginal(offset: Int): Int = offset / 2
+    }
+
     @Before
     fun setup() {
         painter = mock()
@@ -80,7 +111,9 @@
             textPainter = painter,
             value = EditorState(text = "Hello, World", selection = selection),
             editorStyle = EditorStyle(selectionColor = selectionColor),
-            hasFocus = true)
+            hasFocus = true,
+            offsetMap = identityOffsetMap
+        )
 
         verify(painter, times(1)).paintBackground(
             eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas))
@@ -98,7 +131,9 @@
             textPainter = painter,
             value = EditorState(text = "Hello, World", selection = cursor),
             editorStyle = EditorStyle(),
-            hasFocus = true)
+            hasFocus = true,
+            offsetMap = identityOffsetMap
+        )
 
         verify(painter, times(1)).paintCursor(eq(cursor.start), eq(canvas))
         verify(painter, times(1)).paint(eq(canvas))
@@ -114,7 +149,9 @@
             textPainter = painter,
             value = EditorState(text = "Hello, World", selection = cursor),
             editorStyle = EditorStyle(),
-            hasFocus = false)
+            hasFocus = false,
+            offsetMap = identityOffsetMap
+        )
 
         verify(painter, never()).paintCursor(any(), any())
         verify(painter, times(1)).paint(eq(canvas))
@@ -134,7 +171,9 @@
             value = EditorState(text = "Hello, World", selection = cursor,
                 composition = composition),
             editorStyle = EditorStyle(compositionColor = compositionColor),
-            hasFocus = true)
+            hasFocus = true,
+            offsetMap = identityOffsetMap
+        )
 
         verify(painter, times(1)).paintBackground(
             eq(composition.start), eq(composition.end), eq(compositionColor), eq(canvas))
@@ -166,7 +205,8 @@
 
         whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
 
-        InputFieldDelegate.onRelease(position, painter, processor, onValueChange)
+        InputFieldDelegate.onRelease(position, painter, processor,
+            identityOffsetMap, onValueChange)
 
         assertEquals(1, captor.allValues.size)
         assertEquals(1, captor.firstValue.size)
@@ -186,8 +226,9 @@
                 composition = TextRange(1, 3)
             ),
             editorStyle = EditorStyle(compositionColor = Color.Red),
-            hasFocus = true
-        )
+            hasFocus = true,
+            offsetMap = identityOffsetMap
+            )
 
         inOrder(painter) {
             verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas))
@@ -241,7 +282,9 @@
             painter,
             layoutCoordinates,
             textInputService,
-            true /* hasFocus */)
+            true /* hasFocus */,
+            identityOffsetMap
+        )
         verify(textInputService).notifyFocusedRect(any())
     }
 
@@ -253,7 +296,9 @@
             painter,
             layoutCoordinates,
             textInputService,
-            false /* hasFocus */)
+            false /* hasFocus */,
+            identityOffsetMap
+        )
         verify(textInputService, never()).notifyFocusedRect(any())
     }
 
@@ -269,7 +314,9 @@
             painter,
             layoutCoordinates,
             textInputService,
-            true /* hasFocus */)
+            true /* hasFocus */,
+            identityOffsetMap
+        )
         verify(textInputService).notifyFocusedRect(any())
     }
 
@@ -285,7 +332,8 @@
             painter,
             layoutCoordinates,
             textInputService,
-            true /* hasFocus */)
+            true, /* hasFocus */
+            identityOffsetMap)
         val captor = argumentCaptor<Rect>()
         verify(textInputService).notifyFocusedRect(captor.capture())
         assertEquals(dummyHeight, captor.firstValue.height)
@@ -313,4 +361,86 @@
 
         verify(painter, times(1)).layout(constraints)
     }
+
+    @Test
+    fun check_draw_uses_offset_map() {
+        val selection = TextRange(1, 3)
+        val selectionColor = Color.Blue
+
+        InputFieldDelegate.draw(
+            canvas = canvas,
+            textPainter = painter,
+            value = EditorState(text = "Hello, World", selection = selection),
+            editorStyle = EditorStyle(selectionColor = selectionColor),
+            hasFocus = true,
+            offsetMap = skippingOffsetMap
+        )
+
+        val selectionStartInTransformedText = selection.start * 2
+        val selectionEmdInTransformedText = selection.end * 2
+
+        verify(painter, times(1)).paintBackground(
+            eq(selectionStartInTransformedText),
+            eq(selectionEmdInTransformedText),
+            eq(selectionColor),
+            eq(canvas))
+    }
+
+    @Test
+    fun check_notify_rect_uses_offset_map() {
+        val dummyRect = Rect(0f, 1f, 2f, 3f)
+        val dummyPoint = PxPosition(5.px, 6.px)
+        val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 3))
+        whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+        whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
+
+        InputFieldDelegate.notifyFocusedRect(
+            dummyEditorState,
+            painter,
+            layoutCoordinates,
+            textInputService,
+            true /* hasFocus */,
+            skippingOffsetMap
+        )
+        verify(painter).getBoundingBox(6)
+        verify(textInputService).notifyFocusedRect(any())
+    }
+
+    @Test
+    fun check_on_release_uses_offset_map() {
+        val position = PxPosition(100.px, 200.px)
+        val offset = 10
+        val dummyEditorState = EditorState(text = "Hello, World", selection = TextRange(1, 1))
+
+        whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+
+        val captor = argumentCaptor<List<EditOperation>>()
+
+        whenever(processor.onEditCommands(captor.capture())).thenReturn(dummyEditorState)
+
+        InputFieldDelegate.onRelease(position, painter, processor, skippingOffsetMap, onValueChange)
+
+        val cursorOffsetInTransformedText = offset / 2
+        assertEquals(1, captor.allValues.size)
+        assertEquals(1, captor.firstValue.size)
+        assertTrue(captor.firstValue[0] is SetSelectionEditOp)
+        val setSelectionEditOp = captor.firstValue[0] as SetSelectionEditOp
+        assertEquals(cursorOffsetInTransformedText, setSelectionEditOp.start)
+        assertEquals(cursorOffsetInTransformedText, setSelectionEditOp.end)
+        verify(onValueChange, times(1)).invoke(eq(dummyEditorState))
+    }
+
+    @Test
+    fun use_identity_mapping_if_visual_transformation_is_null() {
+        val (visualText, offsetMap) = InputFieldDelegate.applyVisualFilter(
+            EditorState(text = "Hello, World"),
+            null)
+
+        assertEquals("Hello, World", visualText.text)
+        for (i in 0..visualText.text.length) {
+            // Identity mapping returns if no visual filter is provided.
+            assertEquals(i, offsetMap.originalToTransformed(i))
+            assertEquals(i, offsetMap.transformedToOriginal(i))
+        }
+    }
 }
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
new file mode 100644
index 0000000..1284040
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 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.ui.core
+
+import androidx.ui.text.AnnotatedString
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PasswordVisualTransformationTest {
+    @Test
+    fun check_visual_output_is_masked_with_asterisk() {
+        val transformation = PasswordVisualTransformation(mask = '*')
+        val text = AnnotatedString("12345")
+        val (transformedText, map) = transformation.filter(text)
+
+        assertEquals("*****", transformedText.text)
+        for (i in 0..transformedText.text.length) {
+            assertEquals(i, map.originalToTransformed(i))
+            assertEquals(i, map.transformedToOriginal(i))
+        }
+    }
+
+    @Test
+    fun check_visual_output_is_masked_with_default() {
+        val filter = PasswordVisualTransformation()
+        val text = AnnotatedString("1234567890")
+        val (filtered, map) = filter.filter(text)
+
+        assertEquals("\u2022".repeat(10), filtered.text)
+        for (i in 0..filtered.text.length) {
+            assertEquals(i, map.originalToTransformed(i))
+            assertEquals(i, map.transformedToOriginal(i))
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml b/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
index 77e5d1b..66c0d73 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-text/integration-tests/text-demos/src/main/AndroidManifest.xml
@@ -30,7 +30,16 @@
 
         <activity android:name=".CraneInputFieldActivity"
             android:configChanges="orientation|screenSize"
-            android:label="Text/Input Field Demo">
+            android:label="Text/Input Field/Input Field Demo">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".CraneVariousInputFieldActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:label="Text/Input Field/Various Input Field Demo">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="androidx.ui.demos.SAMPLE_CODE" />
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
new file mode 100644
index 0000000..1f46d90
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2019 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.ui.text.demos
+
+import androidx.compose.composer
+import androidx.compose.Composable
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.EditorStyle
+import androidx.ui.core.InputField
+import androidx.ui.core.OffsetMap
+import androidx.ui.core.PasswordVisualTransformation
+import androidx.ui.core.TransformedText
+import androidx.ui.core.VisualTransformation
+import androidx.ui.input.EditorState
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.layout.Column
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.VerticalScroller
+import androidx.ui.text.AnnotatedString
+import androidx.ui.text.TextStyle
+import java.util.Locale
+
+/**
+ * The offset translater used for credit card input field.
+ *
+ * @see creditCardFilter
+ */
+private val creditCardOffsetTranslator = object : OffsetMap {
+    override fun originalToTransformed(offset: Int): Int {
+        if (offset <= 3) return offset
+        if (offset <= 7) return offset + 1
+        if (offset <= 11) return offset + 2
+        if (offset <= 16) return offset + 3
+        return 19
+    }
+
+    override fun transformedToOriginal(offset: Int): Int {
+        if (offset <= 4) return offset
+        if (offset <= 9) return offset - 1
+        if (offset <= 14) return offset - 2
+        if (offset <= 19) return offset - 3
+        return 16
+    }
+}
+
+/**
+ * The visual filter for credit card input field.
+ *
+ * This filter converts up to 16 digits to hyphen connected 4 digits string.
+ * For example, "1234567890123456" will be shown as "1234-5678-9012-3456".
+ */
+private val creditCardFilter = object : VisualTransformation {
+    override fun filter(text: AnnotatedString): TransformedText {
+        val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
+        var out = ""
+        for (i in 0 until trimmed.length) {
+            out += trimmed[i]
+            if (i % 4 == 3 && i != 15) out += "-"
+        }
+        return TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
+    }
+}
+
+/**
+ * The offset translator which works for all offset keep remains the same.
+ */
+private val identityTranslater = object : OffsetMap {
+    override fun originalToTransformed(offset: Int): Int = offset
+    override fun transformedToOriginal(offset: Int): Int = offset
+}
+
+/**
+ * The visual filter for capitalization.
+ *
+ * This filer converts ASCII characters to capital form.
+ */
+private class CapitalizeTransformation(val locale: Locale = Locale.US) : VisualTransformation {
+    override fun filter(text: AnnotatedString): TransformedText {
+        // TODO(nona): identityTranslater doesn't work for some locale, e.g. Turkish
+        return TransformedText(AnnotatedString(text.text.toUpperCase(locale)), identityTranslater)
+    }
+}
+
+/**
+ * The offset translator for phone number
+ *
+ * @see phoneNumberFilter
+ */
+private val phoneNumberOffsetTranslater = object : OffsetMap {
+    override fun originalToTransformed(offset: Int): Int {
+        return when (offset) {
+            0 -> 1
+            1 -> 2
+            2 -> 3
+            3 -> 6
+            4 -> 7
+            5 -> 8
+            6 -> 10
+            7 -> 11
+            8 -> 12
+            9 -> 13
+            else -> 14
+        }
+    }
+
+    override fun transformedToOriginal(offset: Int): Int {
+        return when (offset) {
+            0 -> 0
+            1 -> 0
+            2 -> 1
+            3 -> 2
+            4 -> 3
+            5 -> 3
+            6 -> 3
+            7 -> 4
+            8 -> 5
+            9 -> 6
+            10 -> 6
+            11 -> 7
+            12 -> 8
+            13 -> 9
+            else -> 10
+        }
+    }
+}
+
+/**
+ * The visual filter for phone number.
+ *
+ * This filter converts up to 10 digits to phone number form.
+ * For example, "1234567890" will be shown as "(123) 456-7890".
+ */
+private val phoneNumberFilter = object : VisualTransformation {
+    override fun filter(text: AnnotatedString): TransformedText {
+        val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
+        val filled = trimmed + "_".repeat(10 - trimmed.length)
+        val res = "(" + filled.substring(0..2) + ") " + filled.substring(3..5) + "-" +
+                filled.substring(6..9)
+        return TransformedText(AnnotatedString(text = res), phoneNumberOffsetTranslater)
+    }
+}
+
+private val emailFilter = object : VisualTransformation {
+    override fun filter(text: AnnotatedString): TransformedText {
+        return if (text.text.indexOf("@") == -1) {
+            TransformedText(AnnotatedString(text = text.text + "@gmail.com"), identityTranslater)
+        } else {
+            TransformedText(text, identityTranslater)
+        }
+    }
+}
+
+@Composable
+fun VariousInputFieldDemo() {
+    VerticalScroller {
+        Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+            TagLine(tag = "Capitalization")
+            VariousEditLine(
+                keyboardType = KeyboardType.Ascii,
+                onValueChange = { old, new ->
+                    if (new.text.any { !it.isLetterOrDigit() }) old else new
+                },
+                visualTransformation = CapitalizeTransformation()
+            )
+
+            TagLine(tag = "Capitalization (Turkish)")
+            VariousEditLine(
+                keyboardType = KeyboardType.Ascii,
+                onValueChange = { old, new ->
+                    if (new.text.any { !it.isLetterOrDigit() }) old else new
+                },
+                visualTransformation = CapitalizeTransformation(Locale.forLanguageTag("tr"))
+            )
+
+            TagLine(tag = "Password")
+            VariousEditLine(
+                keyboardType = KeyboardType.Password,
+                onValueChange = { old, new ->
+                    if (new.text.any { !it.isLetterOrDigit() }) old else new
+                },
+                visualTransformation = PasswordVisualTransformation()
+            )
+
+            TagLine(tag = "Phone Number")
+            VariousEditLine(
+                keyboardType = KeyboardType.Number,
+                onValueChange = { old, new ->
+                    if (new.text.length > 10 || new.text.any { !it.isDigit() }) old else new
+                },
+                visualTransformation = phoneNumberFilter
+            )
+
+            TagLine(tag = "Credit Card")
+            VariousEditLine(
+                keyboardType = KeyboardType.Number,
+                onValueChange = { old, new ->
+                    if (new.text.length > 16 || new.text.any { !it.isDigit() }) old else new
+                },
+                visualTransformation = creditCardFilter
+            )
+
+            TagLine(tag = "Email Suggestion")
+            VariousEditLine(
+                keyboardType = KeyboardType.Email,
+                visualTransformation = emailFilter
+            )
+        }
+    }
+}
+
+@Composable
+fun VariousEditLine(
+    keyboardType: KeyboardType = KeyboardType.Text,
+    imeAction: ImeAction = ImeAction.Unspecified,
+    onValueChange: (EditorState, EditorState) -> EditorState = { _, new -> new },
+    visualTransformation: VisualTransformation
+) {
+    val state = +state { EditorState() }
+    InputField(
+        value = state.value,
+        keyboardType = keyboardType,
+        imeAction = imeAction,
+        visualTransformation = visualTransformation,
+        onValueChange = { state.value = onValueChange(state.value, it) },
+        editorStyle = EditorStyle(textStyle = TextStyle(fontSize = fontSize8))
+    )
+}
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
new file mode 100644
index 0000000..0aae714
--- /dev/null
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputFieldActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 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.ui.text.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+
+class CraneVariousInputFieldActivity : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent { VariousInputFieldDemo() }
+    }
+}