Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/CursorAnchorInfoController.java

Issue 1589953005: Support InputMethodManager#updateCursorAnchorInfo for Android 5.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed comments in #38 Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser.input;
6
7 import android.annotation.TargetApi;
8 import android.graphics.Matrix;
9 import android.os.Build;
10 import android.view.View;
11 import android.view.inputmethod.CursorAnchorInfo;
12 import android.view.inputmethod.InputConnection;
13
14 import org.chromium.base.VisibleForTesting;
15 import org.chromium.base.annotations.SuppressFBWarnings;
16 import org.chromium.content.browser.RenderCoordinates;
17
18 import java.util.Arrays;
19
20 import javax.annotation.Nonnull;
21 import javax.annotation.Nullable;
22
23 /**
24 * A state machine interface which receives Chromium internal events to determin es when to call
25 * {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)}. Th is interface is
26 * also used in unit tests to mock out {@link CursorAnchorInfo}, which is availa ble only in
27 * Android 5.0 (Lollipop) and later.
28 */
29 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
30 final class CursorAnchorInfoController {
31 /**
32 * An interface to mock out {@link View#getLocationOnScreen(int[])} for test ing.
33 */
34 public interface ViewDelegate {
35 void getLocationOnScreen(View view, int[] location);
36 }
37
38 /**
39 * An interface to mock out composing text retrieval from ImeAdapter.
40 */
41 public interface ComposingTextDelegate {
42 CharSequence getText();
43 int getSelectionStart();
44 int getSelectionEnd();
45 int getComposingTextStart();
46 int getComposingTextEnd();
47 }
48
49 // Current focus and monitoring states.
50 private boolean mIsEditable;
51 private boolean mHasPendingImmediateRequest;
52 private boolean mMonitorModeEnabled;
53
54 // Parmeter for CursorAnchorInfo, updated by setCompositionCharacterBounds.
55 @Nullable
56 private float[] mCompositionCharacterBounds;
57 // Paremeters for CursorAnchorInfo, updated by onUpdateFrameInfo.
58 private boolean mHasCoordinateInfo;
59 private float mScale;
60 private float mTranslationX;
61 private float mTranslationY;
62 private boolean mHasInsertionMarker;
63 private boolean mIsInsertionMarkerVisible;
64 private float mInsertionMarkerHorizontal;
65 private float mInsertionMarkerTop;
66 private float mInsertionMarkerBottom;
67
68 @Nullable
69 private CursorAnchorInfo mLastCursorAnchorInfo;
70
71 @Nonnull
72 private final Matrix mMatrix = new Matrix();
73 @Nonnull
74 private final int[] mViewOrigin = new int[2];
75 @Nonnull
76 private final CursorAnchorInfo.Builder mCursorAnchorInfoBuilder =
77 new CursorAnchorInfo.Builder();
78
79 @Nullable
80 private InputMethodManagerWrapper mInputMethodManagerWrapper;
81 @Nullable
82 private final ComposingTextDelegate mComposingTextDelegate;
83 @Nonnull
84 private final ViewDelegate mViewDelegate;
85
86 private CursorAnchorInfoController(InputMethodManagerWrapper inputMethodMana gerWrapper,
87 ComposingTextDelegate composingTextDelegate, ViewDelegate viewDelega te) {
88 mInputMethodManagerWrapper = inputMethodManagerWrapper;
89 mComposingTextDelegate = composingTextDelegate;
90 mViewDelegate = viewDelegate;
91 }
92
93 public static CursorAnchorInfoController create(
94 InputMethodManagerWrapper inputMethodManagerWrapper,
95 ComposingTextDelegate composingTextDelegate) {
96 return new CursorAnchorInfoController(inputMethodManagerWrapper,
97 composingTextDelegate, new ViewDelegate() {
98 @Override
99 public void getLocationOnScreen(View view, int[] location) {
100 view.getLocationOnScreen(location);
101 }
102 });
103 }
104
105 @VisibleForTesting
106 public void setInputMethodManagerWrapperForTest(
107 InputMethodManagerWrapper inputMethodManagerWrapper) {
108 mInputMethodManagerWrapper = inputMethodManagerWrapper;
109 }
110
111 @VisibleForTesting
112 public static CursorAnchorInfoController createForTest(
113 InputMethodManagerWrapper inputMethodManagerWrapper,
114 ComposingTextDelegate composingTextDelegate,
115 ViewDelegate viewDelegate) {
116 return new CursorAnchorInfoController(inputMethodManagerWrapper, composi ngTextDelegate,
117 viewDelegate);
118 }
119
120 /**
121 * Called by ImeAdapter when a IME related web content state is changed.
122 */
123 public void invalidateLastCursorAnchorInfo() {
124 if (!mIsEditable) return;
125
126 mLastCursorAnchorInfo = null;
127 }
128
129 /**
130 * Sets positional information of composing text as an array of character bo unds.
131 * @param compositionCharacterBounds Array of character bounds in local coor dinates.
132 */
133 public void setCompositionCharacterBounds(float[] compositionCharacterBounds ) {
134 if (!mIsEditable) return;
135
136 if (!Arrays.equals(compositionCharacterBounds, mCompositionCharacterBoun ds)) {
137 mLastCursorAnchorInfo = null;
138 mCompositionCharacterBounds = compositionCharacterBounds;
139 }
140 }
141
142 /**
143 * Sets coordinates system parameters and selection marker information.
144 * @param hasInsertionMarker {@code true} if the insertion marker exists.
145 * @param isInsertionMarkerVisible {@code true} if the insertion insertion m arker is visible.
146 * @param insertionMarkerHorizontal X coordinate of the top of the first sel ection marker.
147 * @param insertionMarkerTop Y coordinate of the top of the first selection marker.
148 * @param insertionMarkerBottom Y coordinate of the bottom of the first sele ction marker.
149 * @param view The attached view.
150 */
151 @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
152 public void onUpdateFrameInfo(@Nonnull RenderCoordinates renderCoordinates,
153 boolean hasInsertionMarker, boolean isInsertionMarkerVisible,
154 float insertionMarkerHorizontal, float insertionMarkerTop,
155 float insertionMarkerBottom, @Nonnull View view) {
156 if (!mIsEditable) return;
157
158 // Reuse {@param #mViewOrigin} to avoid object creation, as this method is supposed to be
159 // called at relatively high rate.
160 mViewDelegate.getLocationOnScreen(view, mViewOrigin);
161
162 // Character bounds and insertion marker locations come in device indepe ndent pixels
163 // relative from the top-left corner of the web view content area. (In o ther words, the
164 // effects of various kinds of zooming and scrolling are already taken i nto account.)
165 //
166 // We need to prepare parameters that convert such values to physical pi xels, in the
167 // screen coordinate. Hence the following values are derived.
168 float scale = renderCoordinates.getDeviceScaleFactor();
169 float translationX = mViewOrigin[0];
170 float translationY = mViewOrigin[1] + renderCoordinates.getContentOffset YPix();
171
172 if (!mHasCoordinateInfo
173 || scale != mScale
174 || translationX != mTranslationX
175 || translationY != mTranslationY
176 || hasInsertionMarker != mHasInsertionMarker
177 || isInsertionMarkerVisible != mIsInsertionMarkerVisible
178 || insertionMarkerHorizontal != mInsertionMarkerHorizontal
179 || insertionMarkerTop != mInsertionMarkerTop
180 || insertionMarkerBottom != mInsertionMarkerBottom) {
181 mLastCursorAnchorInfo = null;
182 mHasCoordinateInfo = true;
183 mScale = scale;
184 mTranslationX = translationX;
185 mTranslationY = translationY;
186 mHasInsertionMarker = hasInsertionMarker;
187 mIsInsertionMarkerVisible = isInsertionMarkerVisible;
188 mInsertionMarkerHorizontal = insertionMarkerHorizontal;
189 mInsertionMarkerTop = insertionMarkerTop;
190 mInsertionMarkerBottom = insertionMarkerBottom;
191 }
192
193 // Notify to IME if there is a pending request, or if it is in monitor m ode and we have
194 // some change in the state.
195 if (mHasPendingImmediateRequest
196 || (mMonitorModeEnabled && mLastCursorAnchorInfo == null)) {
197 updateCursorAnchorInfo(view);
198 }
199 }
200
201 /**
202 * Resets the current state on update monitoring mode to the default (= do n othing.)
203 */
204 public void resetMonitoringState() {
205 mMonitorModeEnabled = false;
206 mHasPendingImmediateRequest = false;
207 }
208
209 public void focusedNodeChanged(boolean isEditable) {
210 mIsEditable = isEditable;
211 mCompositionCharacterBounds = null;
212 mHasCoordinateInfo = false;
213 mLastCursorAnchorInfo = null;
214 }
215
216 public boolean onRequestCursorUpdates(int cursorUpdateMode, View view) {
217 if (!mIsEditable) return false;
218
219 mMonitorModeEnabled = (cursorUpdateMode & InputConnection.CURSOR_UPDATE_ MONITOR) != 0;
220 if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
221 mHasPendingImmediateRequest = true;
222 updateCursorAnchorInfo(view);
223 }
224 return true;
225 }
226
227 /**
228 * Computes the CursorAnchorInfo instance and notify to InputMethodManager i f needed.
229 */
230 private void updateCursorAnchorInfo(View view) {
231 if (!mHasCoordinateInfo) return;
232
233 if (mLastCursorAnchorInfo == null) {
234 // Reuse the builder object.
235 mCursorAnchorInfoBuilder.reset();
236
237 CharSequence text = mComposingTextDelegate.getText();
238 int selectionStart = mComposingTextDelegate.getSelectionStart();
239 int selectionEnd = mComposingTextDelegate.getSelectionEnd();
240 int composingTextStart = mComposingTextDelegate.getComposingTextStar t();
241 int composingTextEnd = mComposingTextDelegate.getComposingTextEnd();
242 if (text != null && 0 <= composingTextStart && composingTextEnd <= t ext.length()) {
243 mCursorAnchorInfoBuilder.setComposingText(composingTextStart,
244 text.subSequence(composingTextStart, composingTextEnd));
245 float[] compositionCharacterBounds = mCompositionCharacterBounds ;
246 if (compositionCharacterBounds != null) {
247 int numCharacter = compositionCharacterBounds.length / 4;
248 for (int i = 0; i < numCharacter; ++i) {
249 float left = compositionCharacterBounds[i * 4];
250 float top = compositionCharacterBounds[i * 4 + 1];
251 float right = compositionCharacterBounds[i * 4 + 2];
252 float bottom = compositionCharacterBounds[i * 4 + 3];
253 int charIndex = composingTextStart + i;
254 mCursorAnchorInfoBuilder.addCharacterBounds(charIndex, l eft, top, right,
255 bottom, CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION );
256 }
257 }
258 }
259 mCursorAnchorInfoBuilder.setSelectionRange(selectionStart, selection End);
260 mMatrix.setScale(mScale, mScale);
261 mMatrix.postTranslate(mTranslationX, mTranslationY);
262 mCursorAnchorInfoBuilder.setMatrix(mMatrix);
263 if (mHasInsertionMarker) {
264 mCursorAnchorInfoBuilder.setInsertionMarkerLocation(
265 mInsertionMarkerHorizontal,
266 mInsertionMarkerTop,
267 mInsertionMarkerBottom,
268 mInsertionMarkerBottom,
269 mIsInsertionMarkerVisible ? CursorAnchorInfo.FLAG_HAS_VI SIBLE_REGION :
270 CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION);
271 }
272 mLastCursorAnchorInfo = mCursorAnchorInfoBuilder.build();
273 }
274
275 if (mInputMethodManagerWrapper != null) {
276 mInputMethodManagerWrapper.updateCursorAnchorInfo(view, mLastCursorA nchorInfo);
277 }
278 mHasPendingImmediateRequest = false;
279 }
280 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698