Adds support for floating cursor (#25384)

* Adds support for floating cursor.
diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart
index 6aa1ad8..71c6a81 100644
--- a/packages/flutter/lib/src/cupertino/text_field.dart
+++ b/packages/flutter/lib/src/cupertino/text_field.dart
@@ -672,6 +672,7 @@
           cursorWidth: widget.cursorWidth,
           cursorRadius: widget.cursorRadius,
           cursorColor: widget.cursorColor,
+          backgroundCursorColor: CupertinoColors.inactiveGray,
           scrollPadding: widget.scrollPadding,
           keyboardAppearance: keyboardAppearance,
         ),
diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart
index 03ec238..5a11121 100644
--- a/packages/flutter/lib/src/material/text_field.dart
+++ b/packages/flutter/lib/src/material/text_field.dart
@@ -622,6 +622,7 @@
         cursorWidth: widget.cursorWidth,
         cursorRadius: widget.cursorRadius,
         cursorColor: widget.cursorColor ?? Theme.of(context).cursorColor,
+        backgroundCursorColor: CupertinoColors.inactiveGray,
         scrollPadding: widget.scrollPadding,
         keyboardAppearance: keyboardAppearance,
         enableInteractiveSelection: widget.enableInteractiveSelection,
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index 603f028..cb55f70 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -5,7 +5,7 @@
 //ignore: Remove this once Google catches up with dev.4 Dart.
 import 'dart:async';
 import 'dart:math' as math;
-import 'dart:ui' as ui show TextBox;
+import 'dart:ui' as ui show TextBox, lerpDouble;
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
@@ -19,6 +19,13 @@
 const double _kCaretGap = 1.0; // pixels
 const double _kCaretHeightOffset = 2.0; // pixels
 
+// The additional size on the x and y axis with which to expand the prototype
+// cursor to render the floating cursor in pixels.
+const Offset _kFloatingCaretSizeIncrease = Offset(0.5, 2.0);
+
+// The corner radius of the floating cursor in pixels.
+const double _kFloatingCaretRadius = 1.0;
+
 /// Signature for the callback that reports when the user changes the selection
 /// (including the cursor location).
 ///
@@ -129,6 +136,7 @@
     @required TextDirection textDirection,
     TextAlign textAlign = TextAlign.start,
     Color cursorColor,
+    Color backgroundCursorColor,
     ValueNotifier<bool> showCursor,
     bool hasFocus,
     int maxLines = 1,
@@ -143,6 +151,7 @@
     Locale locale,
     double cursorWidth = 1.0,
     Radius cursorRadius,
+    EdgeInsets floatingCursorAddedMargin = const EdgeInsets.fromLTRB(3, 6, 3, 6),
     bool enableInteractiveSelection = true,
     @required this.textSelectionDelegate,
   }) : assert(textAlign != null),
@@ -162,6 +171,7 @@
          locale: locale,
        ),
        _cursorColor = cursorColor,
+       _backgroundCursorColor = backgroundCursorColor,
        _showCursor = showCursor ?? ValueNotifier<bool>(false),
        _hasFocus = hasFocus ?? false,
        _maxLines = maxLines,
@@ -170,6 +180,7 @@
        _offset = offset,
        _cursorWidth = cursorWidth,
        _cursorRadius = cursorRadius,
+       _floatingCursorAddedMargin = floatingCursorAddedMargin,
        _enableInteractiveSelection = enableInteractiveSelection,
        _obscureText = obscureText {
     assert(_showCursor != null);
@@ -566,6 +577,19 @@
     markNeedsPaint();
   }
 
+  /// The color to use when painting the cursor aligned to the text while
+  /// rendering the floating cursor.
+  ///
+  /// The default is light grey.
+  Color get backgroundCursorColor => _backgroundCursorColor;
+  Color _backgroundCursorColor;
+  set backgroundCursorColor(Color value) {
+    if (backgroundCursorColor == value)
+      return;
+    _backgroundCursorColor = value;
+    markNeedsPaint();
+  }
+
   /// Whether to paint the cursor.
   ValueNotifier<bool> get showCursor => _showCursor;
   ValueNotifier<bool> _showCursor;
@@ -701,6 +725,23 @@
     markNeedsPaint();
   }
 
+  /// The padding applied to text field. Used to determine the bounds when
+  /// moving the floating cursor.
+  ///
+  /// Defaults to a padding with left, right set to 3 and top, bottom to 6.
+  EdgeInsets get floatingCursorAddedMargin => _floatingCursorAddedMargin;
+  EdgeInsets _floatingCursorAddedMargin;
+  set floatingCursorAddedMargin(EdgeInsets value) {
+    if (_floatingCursorAddedMargin == value)
+      return;
+    _floatingCursorAddedMargin = value;
+    markNeedsPaint();
+  }
+
+  bool _floatingCursorOn = false;
+  Offset _floatingCursorOffset;
+  TextPosition _floatingCursorTextPosition;
+
   /// If false, [describeSemanticsConfiguration] will not set the
   /// configuration's cursor motion or set selection callbacks.
   ///
@@ -1205,11 +1246,13 @@
     offset.applyContentDimensions(0.0, _maxScrollExtent);
   }
 
-  void _paintCaret(Canvas canvas, Offset effectiveOffset) {
+  void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPosition) {
     assert(_textLayoutLastWidth == constraints.maxWidth);
-    final Offset caretOffset = _textPainter.getOffsetForCaret(_selection.extent, _caretPrototype);
+    final Offset caretOffset = _textPainter.getOffsetForCaret(textPosition, _caretPrototype);
+    // If the floating cursor is enabled, the text cursor's color is [backgroundCursorColor] while
+    // the floating cursor's color is _cursorColor;
     final Paint paint = Paint()
-      ..color = _cursorColor;
+      ..color = _floatingCursorOn ? backgroundCursorColor : _cursorColor;
 
     final Rect caretRect = _caretPrototype.shift(caretOffset + effectiveOffset);
 
@@ -1227,6 +1270,112 @@
     }
   }
 
+  /// Sets the screen position of the floating cursor and the text position
+  /// closest to the cursor.
+  void setFloatingCursor(FloatingCursorDragState state, Offset boundedOffset, TextPosition lastTextPosition, { double resetLerpValue }) {
+    assert(state != null);
+    assert(boundedOffset != null);
+    assert(lastTextPosition != null);
+    if (state == FloatingCursorDragState.Start) {
+      _relativeOrigin = const Offset(0, 0);
+      _previousOffset = null;
+      _resetOriginOnBottom = false;
+      _resetOriginOnTop = false;
+      _resetOriginOnRight = false;
+      _resetOriginOnBottom = false;
+    }
+    _floatingCursorOn = state != FloatingCursorDragState.End;
+    _resetFloatingCursorAnimationValue = resetLerpValue;
+    if(_floatingCursorOn) {
+      _floatingCursorOffset = boundedOffset;
+      _floatingCursorTextPosition = lastTextPosition;
+    }
+    markNeedsPaint();
+  }
+
+  void _paintFloatingCaret(Canvas canvas, Offset effectiveOffset) {
+    assert(_textLayoutLastWidth == constraints.maxWidth);
+    assert(_floatingCursorOn);
+
+    final Paint paint = Paint()..color = _cursorColor;
+
+    double sizeAdjustmentX = _kFloatingCaretSizeIncrease.dx;
+    double sizeAdjustmentY = _kFloatingCaretSizeIncrease.dy;
+
+    if(_resetFloatingCursorAnimationValue != null) {
+      sizeAdjustmentX = ui.lerpDouble(sizeAdjustmentX, 0, _resetFloatingCursorAnimationValue);
+      sizeAdjustmentY = ui.lerpDouble(sizeAdjustmentY, 0, _resetFloatingCursorAnimationValue);
+    }
+
+    final Rect floatingCaretPrototype = Rect.fromLTRB(_caretPrototype.left - sizeAdjustmentX,
+                                              _caretPrototype.top - sizeAdjustmentY,
+                                              _caretPrototype.right + sizeAdjustmentX,
+                                              _caretPrototype.bottom + sizeAdjustmentY);
+    final Rect caretRect = floatingCaretPrototype.shift(effectiveOffset);
+    const Radius floatingCursorRadius = Radius.circular(_kFloatingCaretRadius);
+    final RRect caretRRect = RRect.fromRectAndRadius(caretRect, floatingCursorRadius);
+    canvas.drawRRect(caretRRect, paint);
+  }
+
+  // The relative origin in relation to the distance the user has theoretically
+  // dragged the floating cursor offscreen. This value is used to account for the
+  // difference in the rendering position and the raw offset value.
+  Offset _relativeOrigin = const Offset(0, 0);
+  Offset _previousOffset;
+  bool _resetOriginOnLeft = false;
+  bool _resetOriginOnRight = false;
+  bool _resetOriginOnTop = false;
+  bool _resetOriginOnBottom = false;
+  double _resetFloatingCursorAnimationValue;
+
+  /// Returns the position within the text field closest to the raw cursor offset.
+  Offset calculateBoundedFloatingCursorOffset(Offset rawCursorOffset) {
+    Offset deltaPosition = const Offset(0, 0);
+    final double topBound = -floatingCursorAddedMargin.top;
+    final double bottomBound = _textPainter.height - preferredLineHeight + floatingCursorAddedMargin.bottom;
+    final double leftBound = -floatingCursorAddedMargin.left;
+    final double rightBound = _textPainter.width + floatingCursorAddedMargin.right;
+
+    if (_previousOffset != null)
+      deltaPosition = rawCursorOffset - _previousOffset;
+
+    // If the raw cursor offset has gone off an edge, we want to reset the relative
+    // origin of the dragging when the user drags back into the field.
+    if (_resetOriginOnLeft && deltaPosition.dx > 0) {
+      _relativeOrigin = Offset(rawCursorOffset.dx - leftBound, _relativeOrigin.dy);
+      _resetOriginOnLeft = false;
+    } else if (_resetOriginOnRight && deltaPosition.dx < 0) {
+      _relativeOrigin = Offset(rawCursorOffset.dx - rightBound, _relativeOrigin.dy);
+      _resetOriginOnRight = false;
+    }
+    if (_resetOriginOnTop && deltaPosition.dy > 0) {
+      _relativeOrigin = Offset(_relativeOrigin.dx, rawCursorOffset.dy - topBound);
+      _resetOriginOnTop = false;
+    } else if (_resetOriginOnBottom && deltaPosition.dy < 0) {
+      _relativeOrigin = Offset(_relativeOrigin.dx, rawCursorOffset.dy - bottomBound);
+      _resetOriginOnBottom = false;
+    }
+
+    final double currentX = rawCursorOffset.dx - _relativeOrigin.dx;
+    final double currentY = rawCursorOffset.dy - _relativeOrigin.dy;
+    final double adjustedX = math.min(math.max(currentX, leftBound), rightBound);
+    final double adjustedY = math.min(math.max(currentY, topBound), bottomBound);
+    final Offset adjustedOffset = Offset(adjustedX, adjustedY);
+
+    if (currentX < leftBound && deltaPosition.dx < 0) {
+      _resetOriginOnLeft = true;
+    } else if(currentX > rightBound && deltaPosition.dx > 0)
+      _resetOriginOnRight = true;
+    if (currentY < topBound && deltaPosition.dy < 0)
+      _resetOriginOnTop = true;
+    else if (currentY > bottomBound && deltaPosition.dy > 0)
+      _resetOriginOnBottom = true;
+
+    _previousOffset = rawCursorOffset;
+
+    return adjustedOffset;
+  }
+
   void _paintSelection(Canvas canvas, Offset effectiveOffset) {
     assert(_textLayoutLastWidth == constraints.maxWidth);
     assert(_selectionRects != null);
@@ -1238,17 +1387,20 @@
   void _paintContents(PaintingContext context, Offset offset) {
     assert(_textLayoutLastWidth == constraints.maxWidth);
     final Offset effectiveOffset = offset + _paintOffset;
-
-    if (_selection != null) {
+    if (_selection != null && !_floatingCursorOn) {
       if (_selection.isCollapsed && _showCursor.value && cursorColor != null) {
-        _paintCaret(context.canvas, effectiveOffset);
+        _paintCaret(context.canvas, effectiveOffset, _selection.extent);
       } else if (!_selection.isCollapsed && _selectionColor != null) {
         _selectionRects ??= _textPainter.getBoxesForSelection(_selection);
         _paintSelection(context.canvas, effectiveOffset);
       }
     }
-
     _textPainter.paint(context.canvas, effectiveOffset);
+    if (_floatingCursorOn) {
+      if (_resetFloatingCursorAnimationValue == null)
+        _paintCaret(context.canvas, effectiveOffset, _floatingCursorTextPosition);
+      _paintFloatingCaret(context.canvas, _floatingCursorOffset);
+    }
   }
 
   @override
diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart
index 36b7d81..160c5c7 100644
--- a/packages/flutter/lib/src/services/text_input.dart
+++ b/packages/flutter/lib/src/services/text_input.dart
@@ -4,7 +4,7 @@
 
 import 'dart:async';
 import 'dart:io' show Platform;
-import 'dart:ui' show TextAffinity, hashValues;
+import 'dart:ui' show TextAffinity, hashValues, Offset;
 
 import 'package:flutter/foundation.dart';
 
@@ -439,6 +439,40 @@
   return null;
 }
 
+/// A floating cursor state the user has induced by force pressing an iOS
+/// keyboard.
+enum FloatingCursorDragState {
+  /// A user has just activated a floating cursor.
+  Start,
+
+  /// A user is dragging a floating cursor.
+  Update,
+
+  /// A user has lifted their finger off the screen after using a floating
+  /// cursor.
+  End,
+}
+
+/// The current state and position of the floating cursor.
+class RawFloatingCursorPoint {
+  /// Creates information for setting the position and state of a floating
+  /// cursor.
+  ///
+  /// [state] must not be null and [offset] must not be null if the state is
+  /// [FloatingCursorDragState.Update].
+  RawFloatingCursorPoint({
+    this.offset,
+    @required this.state,
+  }) : assert(state != null),
+       assert(state == FloatingCursorDragState.Update ? offset != null : true);
+
+  /// The raw position of the floating cursor as determined by the iOS sdk.
+  final Offset offset;
+
+  /// The state of the floating cursor.
+  final FloatingCursorDragState state;
+}
+
 /// The current text, selection, and composing state for editing a run of text.
 @immutable
 class TextEditingValue {
@@ -566,6 +600,9 @@
 
   /// Requests that this client perform the given action.
   void performAction(TextInputAction action);
+
+  /// Updates the floating cursor position and state.
+  void updateFloatingCursor(RawFloatingCursorPoint point);
 }
 
 /// An interface for interacting with a text input control.
@@ -648,6 +685,26 @@
   throw FlutterError('Unknown text input action: $action');
 }
 
+FloatingCursorDragState _toTextCursorAction(String state) {
+  switch (state) {
+    case 'FloatingCursorDragState.start':
+      return FloatingCursorDragState.Start;
+    case 'FloatingCursorDragState.update':
+      return FloatingCursorDragState.Update;
+    case 'FloatingCursorDragState.end':
+      return FloatingCursorDragState.End;
+  }
+  throw FlutterError('Unknown text cursor action: $state');
+}
+
+RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, dynamic> encoded) {
+  assert(state != null, 'You must provide a state to set a new editing point.');
+  assert(encoded['X'] != null, 'You must provide a value for the horizontal location of the floating cursor.');
+  assert(encoded['Y'] != null, 'You must provide a value for the vertical location of the floating cursor.');
+  final Offset offset = state == FloatingCursorDragState.Update ? Offset(encoded['X'], encoded['Y']) : const Offset(0, 0);
+  return RawFloatingCursorPoint(offset: offset, state: state);
+}
+
 class _TextInputClientHandler {
   _TextInputClientHandler() {
     SystemChannels.textInput.setMethodCallHandler(_handleTextInputInvocation);
@@ -671,6 +728,9 @@
       case 'TextInputClient.performAction':
         _currentConnection._client.performAction(_toTextInputAction(args[1]));
         break;
+      case 'TextInputClient.updateFloatingCursor':
+        _currentConnection._client.updateFloatingCursor(_toTextPoint(_toTextCursorAction(args[1]), args[2]));
+        break;
       default:
         throw MissingPluginException();
     }
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index e4ed2b0..b5dade4 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -21,6 +21,7 @@
 import 'scroll_physics.dart';
 import 'scrollable.dart';
 import 'text_selection.dart';
+import 'ticker_provider.dart';
 
 export 'package:flutter/services.dart' show TextEditingValue, TextSelection, TextInputType;
 export 'package:flutter/rendering.dart' show SelectionChangedCause;
@@ -182,9 +183,9 @@
   /// [TextInputType.text] unless [maxLines] is greater than one, when it will
   /// default to [TextInputType.multiline].
   ///
-  /// The [controller], [focusNode], [style], [cursorColor], [textAlign],
-  /// [rendererIgnoresPointer], and [enableInteractiveSelection] arguments must
-  /// not be null.
+  /// The [controller], [focusNode], [style], [cursorColor], [backgroundCursorColor],
+  /// [textAlign], [rendererIgnoresPointer], and [enableInteractiveSelection]
+  /// arguments must not be null.
   EditableText({
     Key key,
     @required this.controller,
@@ -193,6 +194,7 @@
     this.autocorrect = true,
     @required this.style,
     @required this.cursorColor,
+    @required this.backgroundCursorColor,
     this.textAlign = TextAlign.start,
     this.textDirection,
     this.locale,
@@ -221,6 +223,7 @@
        assert(autocorrect != null),
        assert(style != null),
        assert(cursorColor != null),
+       assert(backgroundCursorColor != null),
        assert(textAlign != null),
        assert(maxLines == null || maxLines > 0),
        assert(autofocus != null),
@@ -324,6 +327,13 @@
   /// Cannot be null.
   final Color cursorColor;
 
+  /// The color to use when painting the background cursor aligned with the text
+  /// while rendering the floating cursor.
+  ///
+  /// Cannot be null. By default it is the disabled grey color from
+  /// CupertinoColors.
+  final Color backgroundCursorColor;
+
   /// {@template flutter.widgets.editableText.maxLines}
   /// The maximum number of lines for the text to span, wrapping if necessary.
   ///
@@ -491,7 +501,7 @@
 }
 
 /// State for a [EditableText].
-class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver implements TextInputClient, TextSelectionDelegate {
+class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText> implements TextInputClient, TextSelectionDelegate {
   Timer _cursorTimer;
   final ValueNotifier<bool> _showCursor = ValueNotifier<bool>(false);
   final GlobalKey _editableKey = GlobalKey();
@@ -503,6 +513,12 @@
   final LayerLink _layerLink = LayerLink();
   bool _didAutoFocus = false;
 
+  // The time it takes for the floating cursor to snap to the text aligned
+  // cursor position after the user has finished placing it.
+  static const Duration _floatingCursorResetTime = Duration(milliseconds: 125);
+
+  AnimationController _floatingCursorResetController;
+
   @override
   bool get wantKeepAlive => widget.focusNode.hasFocus;
 
@@ -514,6 +530,8 @@
     widget.controller.addListener(_didChangeTextEditingValue);
     widget.focusNode.addListener(_handleFocusChanged);
     _scrollController.addListener(() { _selectionOverlay?.updateForScroll(); });
+    _floatingCursorResetController = AnimationController(vsync: this);
+    _floatingCursorResetController.addListener(_onFloatingCursorResetTick);
   }
 
   @override
@@ -595,6 +613,72 @@
     }
   }
 
+  // The original position of the caret on FloatingCursorDragState.start.
+  Rect _startCaretRect;
+
+  // The most recent text position as determined by the location of the floating
+  // cursor.
+  TextPosition _lastTextPosition;
+
+  // The offset of the floating cursor as determined from the first update call.
+  Offset _pointOffsetOrigin;
+
+  // The most recent position of the floating cursor.
+  Offset _lastBoundedOffset;
+
+  // Because the center of the cursor is preferredLineHeight / 2 below the touch
+  // origin, but the touch origin is used to determine which line the cursor is
+  // on, we need this offset to correctly render and move the cursor.
+  Offset get _floatingCursorOffset => Offset(0, renderEditable.preferredLineHeight / 2);
+
+  @override
+  void updateFloatingCursor(RawFloatingCursorPoint point) {
+    switch(point.state){
+      case FloatingCursorDragState.Start:
+        final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection.baseOffset);
+        _startCaretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
+        renderEditable.setFloatingCursor(point.state, _startCaretRect.center - _floatingCursorOffset, currentTextPosition);
+        break;
+      case FloatingCursorDragState.Update:
+        // We want to send in points that are centered around a (0,0) origin, so we cache the
+        // position on the first update call.
+        if (_pointOffsetOrigin != null) {
+          final Offset centeredPoint = point.offset - _pointOffsetOrigin;
+          final Offset rawCursorOffset = _startCaretRect.center + centeredPoint - _floatingCursorOffset;
+          _lastBoundedOffset = renderEditable.calculateBoundedFloatingCursorOffset(rawCursorOffset);
+          _lastTextPosition = renderEditable.getPositionForPoint(renderEditable.localToGlobal(_lastBoundedOffset + _floatingCursorOffset));
+          renderEditable.setFloatingCursor(point.state, _lastBoundedOffset, _lastTextPosition);
+        } else {
+          _pointOffsetOrigin = point.offset;
+        }
+        break;
+      case FloatingCursorDragState.End:
+        _floatingCursorResetController.value = 0.0;
+        _floatingCursorResetController.animateTo(1.0, duration: _floatingCursorResetTime, curve: Curves.decelerate);
+      break;
+    }
+  }
+
+  void _onFloatingCursorResetTick() {
+    final Offset finalPosition = renderEditable.getLocalRectForCaret(_lastTextPosition).center - _floatingCursorOffset;
+    if (_floatingCursorResetController.isCompleted) {
+      renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, _lastTextPosition);
+      if (_lastTextPosition.offset != renderEditable.selection.baseOffset)
+        // The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
+        _handleSelectionChanged(TextSelection.collapsed(offset: _lastTextPosition.offset), renderEditable, SelectionChangedCause.tap);
+      _startCaretRect = null;
+      _lastTextPosition = null;
+      _pointOffsetOrigin = null;
+      _lastBoundedOffset = null;
+    } else {
+      final double lerpValue = _floatingCursorResetController.value;
+      final double lerpX = ui.lerpDouble(_lastBoundedOffset.dx, finalPosition.dx, lerpValue);
+      final double lerpY = ui.lerpDouble(_lastBoundedOffset.dy, finalPosition.dy, lerpValue);
+
+      renderEditable.setFloatingCursor(FloatingCursorDragState.Update, Offset(lerpX, lerpY), _lastTextPosition, resetLerpValue: lerpValue);
+    }
+  }
+
   void _finalizeEditing(bool shouldUnfocus) {
     // Take any actions necessary now that the user has completed editing.
     if (widget.onEditingComplete != null) {
@@ -969,6 +1053,7 @@
               textSpan: buildTextSpan(),
               value: _value,
               cursorColor: widget.cursorColor,
+              backgroundCursorColor: widget.backgroundCursorColor,
               showCursor: EditableText.debugDeterministicCursor ? ValueNotifier<bool>(true) : _showCursor,
               hasFocus: _hasFocus,
               maxLines: widget.maxLines,
@@ -1034,6 +1119,7 @@
     this.textSpan,
     this.value,
     this.cursorColor,
+    this.backgroundCursorColor,
     this.showCursor,
     this.hasFocus,
     this.maxLines,
@@ -1060,6 +1146,7 @@
   final TextSpan textSpan;
   final TextEditingValue value;
   final Color cursorColor;
+  final Color backgroundCursorColor;
   final ValueNotifier<bool> showCursor;
   final bool hasFocus;
   final int maxLines;
@@ -1084,6 +1171,7 @@
     return RenderEditable(
       text: textSpan,
       cursorColor: cursorColor,
+      backgroundCursorColor: backgroundCursorColor,
       showCursor: showCursor,
       hasFocus: hasFocus,
       maxLines: maxLines,
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart
index c682d7b..100d0b2 100644
--- a/packages/flutter/test/rendering/editable_test.dart
+++ b/packages/flutter/test/rendering/editable_test.dart
@@ -1,10 +1,12 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+ // Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 import 'package:flutter/rendering.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/services.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/foundation.dart';
 
 import '../rendering/mock_canvas.dart';
 import '../rendering/recording_canvas.dart';
@@ -24,6 +26,10 @@
 }
 
 void main() {
+
+  final TextEditingController controller = TextEditingController();
+  const TextStyle textStyle = TextStyle();
+
   test('editable intrinsics', () {
     final TextSelectionDelegate delegate = FakeEditableTextState();
     final RenderEditable editable = RenderEditable(
@@ -91,4 +97,68 @@
       paints..clipRect(rect: Rect.fromLTRB(0.0, 0.0, 1000.0, 10.0))
     );
   });
+
+  RenderEditable findRenderEditable(WidgetTester tester) {
+    final RenderObject root = tester.renderObject(find.byType(EditableText));
+    expect(root, isNotNull);
+
+    RenderEditable renderEditable;
+    void recursiveFinder(RenderObject child) {
+      if (child is RenderEditable) {
+        renderEditable = child;
+        return;
+      }
+      child.visitChildren(recursiveFinder);
+    }
+    root.visitChildren(recursiveFinder);
+    expect(renderEditable, isNotNull);
+    return renderEditable;
+  }
+
+  testWidgets('Floating cursor is painted', (WidgetTester tester) async {
+    debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
+    const String text = 'hello world this is fun and cool and awesome!';
+    controller.text = text;
+    final FocusNode focusNode = FocusNode();
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Material(
+          child: TextField(
+            controller: controller,
+            focusNode: focusNode,
+            style: textStyle,
+          ),
+        ),
+      ),
+    );
+
+    await tester.tap(find.byType(EditableText));
+    final RenderEditable editable = findRenderEditable(tester);
+    editable.selection = const TextSelection(baseOffset: 29, extentOffset: 29);
+
+    final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+      offset: const Offset(20, 20)));
+    await tester.pump();
+
+    expect(find.byType(EditableText), paints..rrect(
+      rrect: RRect.fromRectAndRadius(Rect.fromLTRB(464.5, 0, 467.5, 16.0), const Radius.circular(1.0)), color: const Color(0xff4285f4))
+    );
+
+    // Moves the cursor right a few characters.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+      offset: const Offset(-250, 20)));
+
+    expect(find.byType(EditableText), paints..rrect(
+      rrect: RRect.fromRectAndRadius(Rect.fromLTRB(194.5, 0, 197.5, 16.0), const Radius.circular(1.0)), color: const Color(0xff4285f4))
+    );
+
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End));
+
+    await tester.pumpAndSettle();
+
+    debugDefaultTargetPlatformOverride = null;
+  });
 }
diff --git a/packages/flutter/test/rendering/mock_canvas.dart b/packages/flutter/test/rendering/mock_canvas.dart
index 172801f..5956dc8 100644
--- a/packages/flutter/test/rendering/mock_canvas.dart
+++ b/packages/flutter/test/rendering/mock_canvas.dart
@@ -1024,16 +1024,49 @@
   );
 }
 
-class _RRectPaintPredicate extends _OneParameterPaintPredicate<RRect> {
-  _RRectPaintPredicate({ RRect rrect, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) : super(
+class _RRectPaintPredicate extends _DrawCommandPaintPredicate {
+  _RRectPaintPredicate({ this.rrect, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) : super(
     #drawRRect,
     'a rounded rectangle',
-    expected: rrect,
+    2,
+    1,
     color: color,
     strokeWidth: strokeWidth,
     hasMaskFilter: hasMaskFilter,
-    style: style,
+    style: style
   );
+
+  final RRect rrect;
+
+  @override
+  void verifyArguments(List<dynamic> arguments) {
+    super.verifyArguments(arguments);
+    const double eps = .0001;
+    final RRect actual = arguments[0];
+    if (rrect != null &&
+       ((actual.left - rrect.left).abs() > eps ||
+        (actual.right - rrect.right).abs() > eps ||
+        (actual.top - rrect.top).abs() > eps ||
+        (actual.bottom - rrect.bottom).abs() > eps ||
+        (actual.blRadiusX - rrect.blRadiusX).abs() > eps ||
+        (actual.blRadiusY - rrect.blRadiusY).abs() > eps ||
+        (actual.brRadiusX - rrect.brRadiusX).abs() > eps ||
+        (actual.brRadiusY - rrect.brRadiusY).abs() > eps ||
+        (actual.tlRadiusX - rrect.tlRadiusX).abs() > eps ||
+        (actual.tlRadiusY - rrect.tlRadiusY).abs() > eps ||
+        (actual.trRadiusX - rrect.trRadiusX).abs() > eps ||
+        (actual.trRadiusY - rrect.trRadiusY).abs() > eps)) {
+      throw 'It called $methodName with RRect, $actual, which was not exactly the expected RRect ($rrect).';
+    }
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (rrect != null) {
+      description.add('RRect: $rrect');
+    }
+  }
 }
 
 class _DRRectPaintPredicate extends _TwoParameterPaintPredicate<RRect, RRect> {
diff --git a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart
index 63be25b..19ae1b4 100644
--- a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart
+++ b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart
@@ -26,6 +26,7 @@
             controller: scrollController,
             children: <Widget>[
               EditableText(
+                backgroundCursorColor: Colors.grey,
                 controller: controller,
                 focusNode: focusNode,
                 style: textStyle,
@@ -67,6 +68,7 @@
                 height: 200.0,
               ),
               EditableText(
+                backgroundCursorColor: Colors.grey,
                 scrollPadding: const EdgeInsets.all(50.0),
                 controller: controller,
                 focusNode: focusNode,
@@ -112,6 +114,7 @@
                 height: 350.0,
               ),
               EditableText(
+                backgroundCursorColor: Colors.grey,
                 controller: controller,
                 focusNode: focusNode,
                 style: textStyle,
@@ -161,6 +164,7 @@
                 height: 350.0,
               ),
               EditableText(
+                backgroundCursorColor: Colors.grey,
                 controller: controller,
                 focusNode: focusNode,
                 style: textStyle,
@@ -247,6 +251,7 @@
             controller: scrollController,
             children: <Widget>[
               EditableText(
+                backgroundCursorColor: Colors.grey,
                 maxLines: null, // multi-line
                 controller: controller,
                 focusNode: focusNode,
@@ -302,6 +307,7 @@
                 height: 200.0,
               ),
               EditableText(
+                backgroundCursorColor: Colors.grey,
                 scrollPadding: const EdgeInsets.only(bottom: 300.0),
                 controller: controller,
                 focusNode: focusNode,
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index f4a1b86..85ae629 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -42,6 +42,7 @@
           node: focusScopeNode,
           autofocus: true,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             textInputAction: action,
@@ -67,6 +68,7 @@
         textDirection: TextDirection.ltr,
         child: EditableText(
           controller: controller,
+          backgroundCursorColor: Colors.grey,
           focusNode: focusNode,
           style: textStyle,
           cursorColor: cursorColor,
@@ -87,6 +89,7 @@
     await tester.pumpWidget(Directionality(
         textDirection: TextDirection.ltr,
         child: EditableText(
+          backgroundCursorColor: Colors.grey,
           controller: controller,
           focusNode: focusNode,
           style: textStyle,
@@ -111,6 +114,7 @@
           autofocus: true,
           child: EditableText(
             controller: controller,
+            backgroundCursorColor: Colors.grey,
             focusNode: focusNode,
             style: textStyle,
             cursorColor: cursorColor,
@@ -272,6 +276,7 @@
           autofocus: true,
           child: EditableText(
             controller: controller,
+            backgroundCursorColor: Colors.grey,
             focusNode: focusNode,
             keyboardType: TextInputType.multiline,
             style: textStyle,
@@ -301,6 +306,7 @@
           autofocus: true,
           child: EditableText(
             controller: controller,
+            backgroundCursorColor: Colors.grey,
             focusNode: focusNode,
             maxLines: null,
             style: textStyle,
@@ -329,6 +335,7 @@
           node: focusScopeNode,
           autofocus: true,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             maxLines: null,
@@ -361,6 +368,7 @@
           node: focusScopeNode,
           autofocus: true,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             keyboardType: TextInputType.phone,
@@ -392,6 +400,7 @@
           node: focusScopeNode,
           autofocus: true,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             maxLines: 3, // Sets multiline keyboard implicitly.
@@ -422,6 +431,7 @@
           node: focusScopeNode,
           autofocus: true,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             maxLines: 1, // Sets text keyboard implicitly.
@@ -451,6 +461,7 @@
     String changedValue;
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: FocusNode(),
@@ -494,6 +505,7 @@
       home: RepaintBoundary(
         key: const ValueKey<int>(1),
         child: EditableText(
+          backgroundCursorColor: Colors.grey,
           key: editableTextKey,
           controller: TextEditingController(),
           focusNode: FocusNode(),
@@ -544,6 +556,7 @@
       home: RepaintBoundary(
         key: const ValueKey<int>(1),
         child: EditableText(
+          backgroundCursorColor: Colors.grey,
           key: editableTextKey,
           controller: TextEditingController(),
           focusNode: FocusNode(),
@@ -594,6 +607,7 @@
 
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: focusNode,
@@ -628,6 +642,7 @@
 
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: focusNode,
@@ -669,6 +684,7 @@
 
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: focusNode,
@@ -713,6 +729,7 @@
 
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: focusNode,
@@ -757,6 +774,7 @@
 
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: focusNode,
@@ -801,6 +819,7 @@
 
     final Widget widget = MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         key: editableTextKey,
         controller: TextEditingController(),
         focusNode: focusNode,
@@ -853,6 +872,7 @@
             child: Center(
               child: Material(
                 child: EditableText(
+                  backgroundCursorColor: Colors.grey,
                   key: editableTextKey,
                   controller: currentController,
                   focusNode: FocusNode(),
@@ -912,6 +932,7 @@
           node: focusScopeNode,
           autofocus: true,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             style: textStyle,
@@ -952,6 +973,7 @@
         child: FocusScope(
           node: focusScopeNode,
           child: EditableText(
+            backgroundCursorColor: Colors.grey,
             controller: controller,
             focusNode: focusNode,
             style: textStyle,
@@ -992,6 +1014,7 @@
     await tester.pumpWidget(
       MaterialApp(
         home: EditableText(
+          backgroundCursorColor: Colors.grey,
           controller: controller,
           selectionControls: materialTextSelectionControls,
           focusNode: focusNode,
@@ -1034,6 +1057,7 @@
 
     await tester.pumpWidget(MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         controller: controller,
         focusNode: focusNode,
         style: textStyle,
@@ -1109,6 +1133,7 @@
 
     await tester.pumpWidget(MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         controller: controller,
         focusNode: focusNode,
         style: textStyle,
@@ -1198,6 +1223,7 @@
 
     await tester.pumpWidget(MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         controller: controller,
         focusNode: focusNode,
         style: textStyle,
@@ -1297,6 +1323,7 @@
 
     await tester.pumpWidget(MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         controller: controller,
         focusNode: focusNode,
         style: textStyle,
@@ -1395,6 +1422,7 @@
 
       await tester.pumpWidget(MaterialApp(
         home: EditableText(
+          backgroundCursorColor: Colors.grey,
           controller: controller,
           focusNode: focusNode,
           style: textStyle,
@@ -1490,6 +1518,7 @@
 
     await tester.pumpWidget(MaterialApp(
       home: EditableText(
+        backgroundCursorColor: Colors.grey,
         obscureText: true,
         controller: controller,
         focusNode: focusNode,
@@ -1536,6 +1565,7 @@
         MockTextSelectionControls controls, WidgetTester tester) {
       return tester.pumpWidget(MaterialApp(
         home: EditableText(
+          backgroundCursorColor: Colors.grey,
           controller: controller,
           focusNode: focusNode,
           style: textStyle,
@@ -1743,6 +1773,7 @@
         node: focusScopeNode,
         autofocus: true,
         child: EditableText(
+          backgroundCursorColor: Colors.grey,
           controller: controller,
           focusNode: focusNode,
           autofocus: true,
@@ -1756,6 +1787,157 @@
     expect(controller.selection.isCollapsed, true);
     expect(controller.selection.baseOffset, text.length);
   });
+
+  RenderEditable findRenderEditable(WidgetTester tester) {
+    final RenderObject root = tester.renderObject(find.byType(EditableText));
+    expect(root, isNotNull);
+
+    RenderEditable renderEditable;
+    void recursiveFinder(RenderObject child) {
+      if (child is RenderEditable) {
+        renderEditable = child;
+        return;
+      }
+      child.visitChildren(recursiveFinder);
+    }
+    root.visitChildren(recursiveFinder);
+    expect(renderEditable, isNotNull);
+    return renderEditable;
+  }
+
+  testWidgets('Updating the floating cursor correctly moves the cursor', (WidgetTester tester) async {
+    const String text = 'hello world this is fun and cool and awesome!';
+    controller.text = text;
+    final FocusNode focusNode = FocusNode();
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: FocusScope(
+          node: focusScopeNode,
+          autofocus: true,
+          child: EditableText(
+            backgroundCursorColor: Colors.grey,
+            controller: controller,
+            focusNode: focusNode,
+            style: textStyle,
+            cursorColor: cursorColor,
+          ),
+        ),
+      ),
+    );
+
+    await tester.tap(find.byType(EditableText));
+    final RenderEditable renderEditable = findRenderEditable(tester);
+    renderEditable.selection = const TextSelection(baseOffset: 29, extentOffset: 29);
+
+    expect(controller.selection.baseOffset, 29);
+
+    final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start));
+
+    expect(controller.selection.baseOffset, 29);
+
+    // Sets the origin.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+      offset: const Offset(20, 20)));
+
+    expect(controller.selection.baseOffset, 29);
+
+    // Moves the cursor right a few characters.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+      offset: const Offset(-250, 20)));
+
+    // But we have not yet set the offset because the user is not done placing the cursor.
+    expect(controller.selection.baseOffset, 29);
+
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End));
+
+    await tester.pumpAndSettle();
+    // The cursor has been set.
+    expect(controller.selection.baseOffset, 10);
+  });
+
+  testWidgets('Cursor gets placed correctly after going out of bounds', (WidgetTester tester) async {
+    const String text = 'hello world this is fun and cool and awesome!';
+    controller.text = text;
+    final FocusNode focusNode = FocusNode();
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: FocusScope(
+          node: focusScopeNode,
+          autofocus: true,
+          child: EditableText(
+            backgroundCursorColor: Colors.grey,
+            controller: controller,
+            focusNode: focusNode,
+            style: textStyle,
+            cursorColor: cursorColor,
+          ),
+        ),
+      ),
+    );
+
+    await tester.tap(find.byType(EditableText));
+    final RenderEditable renderEditable = findRenderEditable(tester);
+    renderEditable.selection = const TextSelection(baseOffset: 29, extentOffset: 29);
+
+    expect(controller.selection.baseOffset, 29);
+
+    final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start));
+
+    expect(controller.selection.baseOffset, 29);
+
+    // Sets the origin.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(20, 20)));
+
+    expect(controller.selection.baseOffset, 29);
+
+    // Moves the cursor super far right
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(2090, 20)));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(2100, 20)));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(2090, 20)));
+
+    // After peaking the cursor, we move in the opposite direction.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(1400, 20)));
+
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End));
+
+    await tester.pumpAndSettle();
+    // The cursor has been set.
+    expect(controller.selection.baseOffset, 8);
+
+    // Go in the other direction.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start));
+    // Sets the origin.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(20, 20)));
+
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(-5000, 20)));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(-5010, 20)));
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(-5000, 20)));
+
+    // Move back in the opposite direction only a few hundred.
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
+        offset: const Offset(-4850, 20)));
+
+    editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End));
+
+    await tester.pumpAndSettle();
+
+    expect(controller.selection.baseOffset, 11);
+  });
 }
 
 class MockTextSelectionControls extends Mock implements TextSelectionControls {}
@@ -1769,6 +1951,7 @@
   }) : super(
           controller: controller,
           cursorColor: cursorColor,
+          backgroundCursorColor: Colors.grey,
           focusNode: focusNode,
           style: style,
         );