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

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: Fix Findbugs warning / Simplify the state transition. Created 4 years, 10 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 2014 The Chromium Authors. All rights reserved.
aelias_OOO_until_Jul13 2016/02/10 08:23:42 Copyright 2016
kinaba 2016/02/19 12:28:29 Done.
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.text.TextUtils;
11 import android.view.View;
12 import android.view.inputmethod.CursorAnchorInfo;
13 import android.view.inputmethod.InputConnection;
14
15 import org.chromium.base.CommandLine;
16 import org.chromium.base.VisibleForTesting;
17 import org.chromium.content.browser.RenderCoordinates;
18 import org.chromium.content.common.ContentSwitches;
19
20 import java.util.Arrays;
21
22 import javax.annotation.Nonnull;
23 import javax.annotation.Nullable;
24
25 /*
26 * A state machine interface which receives Chromium internal events to determin es when to call
27 * {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)}. Th is interface is
28 * also used in unit tests to mock out {@link CursorAnchorInfo}, which is availa ble only in
29 * Android 5.0 (Lollipop) and later.
30 */
31 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
32 final class CursorAnchorInfoController {
33 /**
34 * An interface to mock out {@link View#getLocationOnScreen(int[])} for test ing.
35 */
36 public interface ViewDelegate {
37 void getLocationOnScreen(View view, int[] location);
38 }
39
40 // Current focus and monitoring states.
41 private boolean mIsEditable;
42 private boolean mHasPendingImmediateRequest;
43 private boolean mMonitorModeEnabled;
44
45 // Parameters for CursorAnchorInfo, updated by updateTextAndSelection.
46 @Nullable
47 private CharSequence mText;
aelias_OOO_until_Jul13 2016/02/10 08:23:42 Let's not store the full text here. We're plannin
kinaba 2016/02/19 12:28:29 Done. This is retrieved from IMEAdapter.
48 private int mSelectionStart;
49 private int mSelectionEnd;
50 private int mComposingTextStart;
51 private int mComposingTextEnd;
52 // Parmeter for CursorAnchorInfo, updated by setCompositionCharacterBounds.
53 @Nullable
54 private float[] mCompositionCharacterBounds;
55 // Paremeters for CursorAnchorInfo, updated by onUpdateFrameInfo.
56 private boolean mHasCoordinateInfo;
57 private float mScale;
aelias_OOO_until_Jul13 2016/02/10 08:23:41 A lot of this is copies of state held by the Rende
Changwan Ryu 2016/02/17 00:21:05 Hmm... Just curious, what happens when marker posi
kinaba 2016/02/19 12:28:29 They some floating UIs IME render may not be place
kinaba 2016/02/19 12:28:29 Removed the duplicates with IMEAdapter. For Render
58 private float mTranslationX;
59 private float mTranslationY;
60 private boolean mHasInsertionMarker;
61 private boolean mIsInsertionMarkerVisible;
62 private float mInsertionMarkerHorizontal;
63 private float mInsertionMarkerTop;
64 private float mInsertionMarkerBottom;
65
66 @Nonnull
67 private final CursorAnchorInfo.Builder mCursorAnchorInfoBuilder =
68 new CursorAnchorInfo.Builder();
69 @Nullable
70 private volatile CursorAnchorInfo mLastCursorAnchorInfo;
aelias_OOO_until_Jul13 2016/02/10 08:23:42 Why "volatile"? This is another scary multithread
kinaba 2016/02/19 12:28:29 Removed.
71
72 @Nonnull
73 private final Matrix mMatrix = new Matrix();
74 @Nonnull
75 private final int[] mViewOrigin = new int[2];
76 @Nonnull
77 private final ViewDelegate mViewDelegate;
78
79 @Nullable
80 private InputMethodManagerWrapper mInputMethodManagerWrapper;
81
82 private static final boolean sIsSupported = isSupportedInit();
aelias_OOO_until_Jul13 2016/02/10 08:23:41 Checking a command-line flags at static initializa
kinaba 2016/02/19 12:28:29 Done.
83
84 /**
85 * @return {@code true} if {@link CursorAnchorInfo} is supported on this dev ice.
86 */
87 private static boolean isSupportedInit() {
88 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
89 return false;
90 }
91 if (CommandLine.getInstance() != null
92 && !CommandLine.getInstance().hasSwitch(
93 ContentSwitches.ENABLE_CURSOR_ANCHOR_INFO)) {
94 return false;
95 }
96 return true;
97 }
98
99 /**
100 * @return {@code true} if {@link CursorAnchorInfo} is supported on this dev ice.
101 */
102 public static boolean isSupported() {
103 return sIsSupported;
104 }
105
106 private CursorAnchorInfoController(InputMethodManagerWrapper inputMethodMana gerWrapper,
107 ViewDelegate viewDelegate) {
108 mInputMethodManagerWrapper = inputMethodManagerWrapper;
109 mViewDelegate = viewDelegate;
110 }
111
112 public static CursorAnchorInfoController create(
113 InputMethodManagerWrapper inputMethodManagerWrapper) {
114 return isSupported() ? new CursorAnchorInfoController(inputMethodManager Wrapper,
115 new ViewDelegate() {
116 @Override
117 public void getLocationOnScreen(View view, int[] location) {
118 view.getLocationOnScreen(location);
119 }
120 }) : null;
121 }
122
123 @VisibleForTesting
124 public void setInputMethodManagerWrapper(InputMethodManagerWrapper inputMeth odManagerWrapper) {
125 mInputMethodManagerWrapper = inputMethodManagerWrapper;
126 }
127
128 @VisibleForTesting
129 public static CursorAnchorInfoController createForTest(
130 InputMethodManagerWrapper inputMethodManagerWrapper,
131 ViewDelegate viewDelegate) {
132 return new CursorAnchorInfoController(inputMethodManagerWrapper, viewDel egate);
133 }
134
135 /**
136 * @return Current composing text (if any). {@code null} otherwise.
137 */
138 @Nullable
139 private CharSequence getComposingText() {
140 if (mText == null) return null;
141
142 if (0 <= mComposingTextStart && mComposingTextStart <= mText.length()) {
143 return mText.subSequence(mComposingTextStart, mComposingTextEnd);
144 }
145 return "";
146 }
147
148 /**
149 * Updates text in the focused text area, selection range, and the composing text range.
150 * @param text Text in the focused text field.
151 * @param composingTextStart Index where the text composition starts. {@code -1} if there is
152 * no selection.
153 * @param composingTextEnd Index where the text composition ends. {@code -1} if there is no
154 * selection.
155 * @param selectionStart Index where the text selection starts. {@code -1} i f there is no
156 * selection.
157 * @param selectionEnd Index where the text selection ends. {@code -1} if th ere is no
158 * selection.
159 */
160 public synchronized void updateTextAndSelection(CharSequence text, int compo singTextStart,
161 int composingTextEnd, int selectionStart, int selectionEnd) {
162 if (!mIsEditable) return;
163
164 if (!TextUtils.equals(text, mText) || selectionStart != mSelectionStart
165 || selectionEnd != mSelectionEnd || composingTextStart != mCompo singTextStart
166 || composingTextEnd != mComposingTextEnd) {
167 mLastCursorAnchorInfo = null;
168 mText = text;
169 mSelectionStart = selectionStart;
170 mSelectionEnd = selectionEnd;
171 mComposingTextStart = composingTextStart;
172 mComposingTextEnd = composingTextEnd;
173 }
174 }
175
176 /**
177 * Sets positional information of composing text as an array of character bo unds.
178 * @param compositionCharacterBounds Array of character bounds in local coor dinates.
179 */
180 public synchronized void setCompositionCharacterBounds(float[] compositionCh aracterBounds) {
aelias_OOO_until_Jul13 2016/02/10 08:23:42 Why "synchronized"? Don't we just have a single U
kinaba 2016/02/19 12:28:29 Done. (As I wrote in the previous comment, I thoug
181 if (!mIsEditable) return;
182
183 if (!Arrays.equals(compositionCharacterBounds, mCompositionCharacterBoun ds)) {
184 mLastCursorAnchorInfo = null;
185 mCompositionCharacterBounds = compositionCharacterBounds;
186 }
187 }
188
189 /**
190 * Sets coordinates system parameters and selection marker information.
191 * @param hasInsertionMarker {@code true} if the insertion marker exists.
192 * @param isInsertionMarkerVisible {@code true} if the insertion insertion m arker is visible.
193 * @param insertionMarkerHorizontal X coordinate of the top of the first sel ection marker.
194 * @param insertionMarkerTop Y coordinate of the top of the first selection marker.
195 * @param insertionMarkerBottom Y coordinate of the bottom of the first sele ction marker.
196 * @param view The attached view.
197 */
198 public synchronized void onUpdateFrameInfo(@Nonnull RenderCoordinates render Coordinates,
199 boolean hasInsertionMarker, boolean isInsertionMarkerVisible,
200 float insertionMarkerHorizontal, float insertionMarkerTop,
201 float insertionMarkerBottom, @Nonnull View view) {
202 if (!mIsEditable) return;
203
204 // Reuse {@param #mViewOrigin} to avoid object creation, as this method is supposed to be
205 // called at relatively high rate.
206 mViewDelegate.getLocationOnScreen(view, mViewOrigin);
207
208 // Character bounds and insertion marker locations come in device indepe ndent pixels
209 // relative from the top-left corner of the web view content area. (In o ther words, the
210 // effects of various kinds of zooming and scrolling are already taken i nto account.)
211 //
212 // We need to prepare parameters that convert such values to physical pi xels, in the
213 // screen coordinate. Hence the following values are derived.
214 float scale = renderCoordinates.getDeviceScaleFactor();
215 float translationX = mViewOrigin[0];
216 float translationY = mViewOrigin[1] + renderCoordinates.getContentOffset YPix();
217
218 if (!mHasCoordinateInfo
219 || Math.abs(scale - mScale) > 1e-5
aelias_OOO_until_Jul13 2016/02/10 08:23:42 What's the point of these epsilon comparisons? Do
kinaba 2016/02/19 12:28:29 The sole reason is to shut up FindBugs checker run
aelias_OOO_until_Jul13 2016/02/26 08:25:32 I strongly disagree with findbugs on best practice
kinaba 2016/03/01 08:46:46 Done.
220 || Math.abs(translationX - mTranslationX) > 1e-5
221 || Math.abs(translationY - mTranslationY) > 1e-5
222 || hasInsertionMarker != mHasInsertionMarker
223 || isInsertionMarkerVisible != mIsInsertionMarkerVisible
224 || Math.abs(insertionMarkerHorizontal - mInsertionMarkerHorizont al) > 1e-5
225 || Math.abs(insertionMarkerTop - mInsertionMarkerTop) > 1e-5
226 || Math.abs(insertionMarkerBottom - mInsertionMarkerBottom) > 1e -5) {
227 mLastCursorAnchorInfo = null;
228 mHasCoordinateInfo = true;
229 mScale = scale;
230 mTranslationX = translationX;
231 mTranslationY = translationY;
232 mHasInsertionMarker = hasInsertionMarker;
233 mIsInsertionMarkerVisible = isInsertionMarkerVisible;
234 mInsertionMarkerHorizontal = insertionMarkerHorizontal;
235 mInsertionMarkerTop = insertionMarkerTop;
236 mInsertionMarkerBottom = insertionMarkerBottom;
237 }
238
239 // Notify to IME if there is a pending request, or if it is in monitor m ode and we have
240 // some change in the state.
241 if (mHasPendingImmediateRequest
242 || (mMonitorModeEnabled && mLastCursorAnchorInfo == null)) {
243 updateCursorAnchorInfo(view);
244 }
245 }
246
247 /**
248 * Resets the current state on update monitoring mode to the default (= do n othing.)
249 */
250 public void resetMonitoringState() {
251 mMonitorModeEnabled = false;
252 }
253
254 public synchronized void focusedNodeChanged(boolean isEditable) {
255 mIsEditable = isEditable;
256
257 mText = null;
258 mSelectionStart = -1;
aelias_OOO_until_Jul13 2016/02/10 08:23:42 Almost all the nulling here except "mHasCoordinate
kinaba 2016/02/19 12:28:29 Done.
259 mSelectionEnd = -1;
260 mComposingTextStart = -1;
261 mComposingTextEnd = -1;
262 mCompositionCharacterBounds = null;
263 mHasCoordinateInfo = false;
264 mScale = 1.0f;
265 mTranslationX = 0.0f;
266 mTranslationY = 0.0f;
267 mHasInsertionMarker = false;
268 mInsertionMarkerHorizontal = Float.NaN;
269 mInsertionMarkerTop = Float.NaN;
270 mInsertionMarkerBottom = Float.NaN;
271
272 mLastCursorAnchorInfo = null;
273 }
274
275 public boolean onRequestCursorUpdates(int cursorUpdateMode, @Nonnull View vi ew) {
276 if (!mIsEditable) return false;
277
278 final int knownRequestCursorUpdatesFlags =
279 InputConnection.CURSOR_UPDATE_MONITOR | InputConnection.CURSOR_U PDATE_IMMEDIATE;
280 if ((cursorUpdateMode & ~knownRequestCursorUpdatesFlags) != 0) {
281 // Does nothing when at least one unknown bit flag is set.
aelias_OOO_until_Jul13 2016/02/10 08:23:41 Is this some kind of API future-proofing plan? It
kinaba 2016/02/19 12:28:29 Done.
282 return false;
283 }
284 mMonitorModeEnabled = (cursorUpdateMode & InputConnection.CURSOR_UPDATE_ MONITOR) != 0;
285 if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
286 mHasPendingImmediateRequest = true;
287 updateCursorAnchorInfo(view);
288 }
289 return true;
290 }
291
292 /**
293 * Computes the CursorAnchorInfo instance and notify to InputMethodManager i f needed.
294 */
295 private synchronized void updateCursorAnchorInfo(@Nonnull View view) {
296 if (!mHasCoordinateInfo) return;
297
298 if (mLastCursorAnchorInfo == null) {
299 // Reuse the builder.
300 mCursorAnchorInfoBuilder.reset();
301
302 CharSequence composingText = getComposingText();
303 int composingTextStart = mComposingTextStart;
304 if (composingText != null) {
305 mCursorAnchorInfoBuilder.setComposingText(composingTextStart, co mposingText);
306 float[] compositionCharacterBounds = mCompositionCharacterBounds ;
307 if (compositionCharacterBounds != null) {
308 int numCharacter = compositionCharacterBounds.length / 4;
309 for (int i = 0; i < numCharacter; ++i) {
310 float left = compositionCharacterBounds[i * 4];
311 float top = compositionCharacterBounds[i * 4 + 1];
312 float right = compositionCharacterBounds[i * 4 + 2];
313 float bottom = compositionCharacterBounds[i * 4 + 3];
314 int charIndex = composingTextStart + i;
315 mCursorAnchorInfoBuilder.addCharacterBounds(charIndex, l eft, top, right,
316 bottom, CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION );
317 }
318 }
319 }
320 mCursorAnchorInfoBuilder.setSelectionRange(mSelectionStart, mSelecti onEnd);
321 mMatrix.setScale(mScale, mScale);
322 mMatrix.postTranslate(mTranslationX, mTranslationY);
323 mCursorAnchorInfoBuilder.setMatrix(mMatrix);
324 if (mHasInsertionMarker) {
325 mCursorAnchorInfoBuilder.setInsertionMarkerLocation(
326 mInsertionMarkerHorizontal,
327 mInsertionMarkerTop,
328 mInsertionMarkerBottom,
329 mInsertionMarkerBottom,
330 mIsInsertionMarkerVisible ? CursorAnchorInfo.FLAG_HAS_VI SIBLE_REGION :
331 CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION);
332 }
333 mLastCursorAnchorInfo = mCursorAnchorInfoBuilder.build();
334 }
335
336 if (mInputMethodManagerWrapper != null) {
337 mInputMethodManagerWrapper.updateCursorAnchorInfo(view, mLastCursorA nchorInfo);
338 }
339 mHasPendingImmediateRequest = false;
340 }
341 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698