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

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

Issue 699333003: Support InputMethodManager#updateCursorAnchorInfo for Android 5.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase before splitting this CL. Created 5 years, 8 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.
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.graphics.Matrix;
8 import android.graphics.RectF;
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 final class CursorAnchorInfoController {
32 /**
33 * An interface to mock out {@link View#getLocationOnScreen(int[])} for test ing.
34 */
35 public interface ViewDelegate {
36 void getLocationOnScreen(View view, int[] location);
37 }
38
39 private static final class CursorAnchorInfoWrapperImpl implements CursorAnch orInfoWrapper {
40 private static final class Builder implements CursorAnchorInfoWrapper.Bu ilder {
41 private final CursorAnchorInfo.Builder mBuilder = new CursorAnchorIn fo.Builder();
42
43 @Override
44 public CursorAnchorInfoWrapper.Builder setSelectionRange(int newStar t, int newEnd) {
45 mBuilder.setSelectionRange(newStart, newEnd);
46 return this;
47 }
48
49 @Override
50 public CursorAnchorInfoWrapper.Builder setComposingText(
51 int composingTextStart, CharSequence composingText) {
52 mBuilder.setComposingText(composingTextStart, composingText);
53 return this;
54 }
55
56 @Override
57 public CursorAnchorInfoWrapper.Builder setInsertionMarkerLocation(
58 float horizontalPosition, float lineTop, float lineBaseline, float lineBottom,
59 int flags) {
60 mBuilder.setInsertionMarkerLocation(horizontalPosition, lineTop, lineBaseline,
61 lineBottom, flags);
62 return this;
63 }
64
65 @Override
66 public CursorAnchorInfoWrapper.Builder addCharacterBounds(
67 int index, float left, float top, float right, float bottom, int flags) {
68 mBuilder.addCharacterBounds(index, left, top, right, bottom, fla gs);
69 return this;
70 }
71
72 @Override
73 public CursorAnchorInfoWrapper.Builder setMatrix(Matrix matrix) {
74 mBuilder.setMatrix(matrix);
75 return this;
76 }
77
78 @Override
79 public CursorAnchorInfoWrapper build() {
80 return new CursorAnchorInfoWrapperImpl(mBuilder.build());
81 }
82
83 @Override
84 public void reset() {
85 mBuilder.reset();
86 }
87 }
88
89 private final CursorAnchorInfo mObj;
90 public CursorAnchorInfoWrapperImpl(CursorAnchorInfo obj) {
91 mObj = obj;
92 }
93
94 @Override
95 public int getSelectionStart() {
96 return mObj.getSelectionStart();
97 }
98
99 @Override
100 public int getSelectionEnd() {
101 return mObj.getSelectionEnd();
102 }
103
104 @Override
105 public int getComposingTextStart() {
106 return mObj.getComposingTextStart();
107 }
108
109 @Override
110 public CharSequence getComposingText() {
111 return mObj.getComposingText();
112 }
113
114 @Override
115 public int getInsertionMarkerFlags() {
116 return mObj.getInsertionMarkerFlags();
117 }
118
119 @Override
120 public float getInsertionMarkerHorizontal() {
121 return mObj.getInsertionMarkerHorizontal();
122 }
123
124 @Override
125 public float getInsertionMarkerTop() {
126 return mObj.getInsertionMarkerTop();
127 }
128
129 @Override
130 public float getInsertionMarkerBaseline() {
131 return mObj.getInsertionMarkerBaseline();
132 }
133
134 @Override
135 public float getInsertionMarkerBottom() {
136 return mObj.getInsertionMarkerBottom();
137 }
138
139 @Override
140 public RectF getCharacterBounds(int index) {
141 return mObj.getCharacterBounds(index);
142 }
143
144 @Override
145 public int getCharacterBoundsFlags(int index) {
146 return mObj.getCharacterBoundsFlags(index);
147 }
148
149 @Override
150 public Matrix getMatrix() {
151 return mObj.getMatrix();
152 }
153
154 @Override
155 public Object unwrap() {
156 return mObj;
157 }
158 }
159
160 @Nullable
161 private CharSequence mText;
162 private int mSelectionStart;
163 private int mSelectionEnd;
164 private int mComposingTextStart;
165 private int mComposingTextEnd;
166 @Nullable
167 private float[] mCompositionCharacterBounds;
168 private boolean mHasInsertionMarker;
169 private boolean mIsInsertionMarkerVisible;
170 private float mInsertionMarkerHorizontal;
171 private float mInsertionMarkerTop;
172 private float mInsertionMarkerBottom;
173 private float mScale;
174 private float mTranslationX;
175 private float mTranslationY;
176 private boolean mIsEditable;
177 private boolean mHasPendingRequest;
178 private boolean mMonitorModeEnabled;
179 private boolean mHasCoordinateInfo;
180
181 @Nonnull
182 private final CursorAnchorInfoWrapper.Builder mCursorAnchorInfoBuilder;
183 @Nullable
184 private volatile CursorAnchorInfoWrapper mLastCursorAnchorInfo;
185
186 @Nonnull
187 private final Matrix mMatrix = new Matrix();
188 @Nonnull
189 private final int[] mViewOrigin = new int[2];
190 @Nonnull
191 private final ViewDelegate mViewDelegate;
192
193 @Nullable
194 private InputMethodManagerWrapper mInputMethodManagerWrapper;
195
196 private static final boolean sIsSupported = isSupportedInit();
197
198 /**
199 * @return {@code true} if {@link CursorAnchorInfo} is supported on this dev ice.
200 */
201 private static boolean isSupportedInit() {
202 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
203 return false;
204 if (CommandLine.getInstance() != null
205 && !CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_C URSOR_ANCHOR_INFO))
206 return false;
207 return true;
208 }
209
210 /**
211 * @return {@code true} if {@link CursorAnchorInfo} is supported on this dev ice.
212 */
213 public static boolean isSupported() {
214 return sIsSupported;
215 }
216
217 private CursorAnchorInfoController(InputMethodManagerWrapper inputMethodMana gerWrapper,
218 CursorAnchorInfoWrapper.Builder builder,
219 ViewDelegate viewDelegate) {
220 mInputMethodManagerWrapper = inputMethodManagerWrapper;
221 mCursorAnchorInfoBuilder = builder;
222 mViewDelegate = viewDelegate;
223 }
224
225 public static CursorAnchorInfoController create(
226 InputMethodManagerWrapper inputMethodManagerWrapper) {
227 return isSupported() ? new CursorAnchorInfoController(inputMethodManager Wrapper,
228 new CursorAnchorInfoWrapperImpl.Builder(), new ViewDelegate() {
229 @Override
230 public void getLocationOnScreen(View view, int[] location) {
231 view.getLocationOnScreen(location);
232 }
233 }) : null;
234 }
235
236 @VisibleForTesting
237 public void setInputMethodManagerWrapper(InputMethodManagerWrapper inputMeth odManagerWrapper) {
238 mInputMethodManagerWrapper = inputMethodManagerWrapper;
239 }
240
241 @VisibleForTesting
242 public static CursorAnchorInfoController createForTest(
243 InputMethodManagerWrapper inputMethodManagerWrapper,
244 CursorAnchorInfoWrapper.Builder builder,
245 ViewDelegate viewDelegate) {
246 return new CursorAnchorInfoController(inputMethodManagerWrapper, builder , viewDelegate);
247 }
248
249 /**
250 * @return Current composing text (if any). {@code null} otherwise.
251 */
252 @Nullable
253 public CharSequence getComposingText() {
254 if (mText == null)
255 return null;
256 if (0 <= mComposingTextStart && mComposingTextStart <= mText.length())
257 return mText.subSequence(mComposingTextStart, mComposingTextEnd);
258 return "";
259 }
260
261 /**
262 * Updates text in the focused text area, selection range, and the composing text range.
263 * @param text Text in the focused text field.
264 * @param composingTextStart Index where the text composition starts. {@code -1} if there is
265 * no selection.
266 * @param composingTextEnd Index where the text composition ends. {@code -1} if there is no
267 * selection.
268 * @param selectionStart Index where the text selection starts. {@code -1} i f there is no
269 * selection.
270 * @param selectionEnd Index where the text selection ends. {@code -1} if th ere is no
271 * selection.
272 */
273 public void updateTextAndSelection(CharSequence text, int composingTextStart ,
274 int composingTextEnd, int selectionStart, int selectionEnd) {
275 if (!mIsEditable)
276 return;
277 if (mLastCursorAnchorInfo == null && mMonitorModeEnabled)
278 mHasPendingRequest = true;
279 if (mLastCursorAnchorInfo != null && !mHasPendingRequest) {
280 if (!TextUtils.equals(text, mText) || selectionStart != mSelectionSt art
281 || selectionEnd != mSelectionEnd || composingTextStart != mC omposingTextStart
282 || composingTextEnd != mComposingTextEnd) {
283 mLastCursorAnchorInfo = null;
284 if (mMonitorModeEnabled) {
285 mHasPendingRequest = true;
286 }
287 }
288 }
289 mText = text;
290 mSelectionStart = selectionStart;
291 mSelectionEnd = selectionEnd;
292 mComposingTextStart = composingTextStart;
293 mComposingTextEnd = composingTextEnd;
294 }
295
296 /**
297 * Sets positional information of composing text as an array of character bo unds.
298 * @param compositionCharacterBounds Array of character bounds in local coor dinates.
299 */
300 public void setCompositionCharacterBounds(float[] compositionCharacterBounds ) {
301 if (!mIsEditable)
302 return;
303 if (mLastCursorAnchorInfo == null && mMonitorModeEnabled)
304 mHasPendingRequest = true;
305 if (mLastCursorAnchorInfo != null && !mHasPendingRequest) {
306 if (!Arrays.equals(compositionCharacterBounds, mCompositionCharacter Bounds)) {
307 mLastCursorAnchorInfo = null;
308 if (mMonitorModeEnabled) {
309 mHasPendingRequest = true;
310 }
311 }
312 }
313 mCompositionCharacterBounds = compositionCharacterBounds;
314 }
315
316 /**
317 * Sets coordinates system parameters and selection marker information.
318 * @param hasInsertionMarker {@code true} if the insertion marker exists.
319 * @param isInsertionMarkerVisible {@code true} if the insertion insertion m arker is visible.
320 * @param insertionMarkerHorizontal X coordinate of the top of the first sel ection marker.
321 * @param insertionMarkerTop Y coordinate of the top of the first selection marker.
322 * @param insertionMarkerBottom Y coordinate of the bottom of the first sele ction marker.
323 * @param view The attached view.
324 */
325 public void onUpdateFrameInfo(@Nonnull RenderCoordinates renderCoordinates,
326 boolean hasInsertionMarker, boolean isInsertionMarkerVisible,
327 float insertionMarkerHorizontal, float insertionMarkerTop,
328 float insertionMarkerBottom, @Nonnull View view) {
329 if (!mIsEditable)
330 return;
331
332 float scale =
333 renderCoordinates.getPageScaleFactor() * renderCoordinates.getDe viceScaleFactor();
334 float translationX;
335 float translationY;
336 // Reuse {@param #mViewOrigin} to avoid object creation, as this method is supposed to be
337 // called at relatively high rate.
338 mViewDelegate.getLocationOnScreen(view, mViewOrigin);
339 translationX = -renderCoordinates.getScrollX() * scale + mViewOrigin[0];
340 translationY = -renderCoordinates.getScrollY() * scale
341 + renderCoordinates.getContentOffsetYPix() + mViewOrigin[1];
342
343 if (mLastCursorAnchorInfo == null && mMonitorModeEnabled)
344 mHasPendingRequest = true;
345 if (mLastCursorAnchorInfo != null && !mHasPendingRequest) {
346 if (!mHasCoordinateInfo
347 || scale != mScale
348 || translationX != mTranslationX
349 || translationY != mTranslationY
350 || hasInsertionMarker != mHasInsertionMarker
351 || isInsertionMarkerVisible != mIsInsertionMarkerVisible
352 || insertionMarkerHorizontal != mInsertionMarkerHorizontal
353 || insertionMarkerTop != mInsertionMarkerTop
354 || insertionMarkerBottom != mInsertionMarkerBottom) {
355 mLastCursorAnchorInfo = null;
356 if (mMonitorModeEnabled) {
357 mHasPendingRequest = true;
358 }
359 }
360 }
361
362 mHasInsertionMarker = hasInsertionMarker;
363 mIsInsertionMarkerVisible = isInsertionMarkerVisible;
364 mInsertionMarkerHorizontal = insertionMarkerHorizontal;
365 mInsertionMarkerTop = insertionMarkerTop;
366 mInsertionMarkerBottom = insertionMarkerBottom;
367 mScale = scale;
368 mTranslationX = translationX;
369 mTranslationY = translationY;
370 mHasCoordinateInfo = true;
371 if (mLastCursorAnchorInfo == null && mHasPendingRequest)
372 updateCursorAnchorInfo();
373 if (mLastCursorAnchorInfo != null && mHasPendingRequest) {
374 if (mInputMethodManagerWrapper != null) {
375 mInputMethodManagerWrapper.updateCursorAnchorInfo(view, mLastCur sorAnchorInfo);
376 }
377 mHasPendingRequest = false;
378 }
379 }
380
381 public void focusedNodeChanged(boolean isEditable) {
382 mIsEditable = isEditable;
383 mHasCoordinateInfo = false;
384 mText = null;
385 mSelectionStart = -1;
386 mSelectionEnd = -1;
387 mComposingTextStart = -1;
388 mComposingTextEnd = -1;
389 mCompositionCharacterBounds = null;
390 mHasInsertionMarker = false;
391 mInsertionMarkerHorizontal = Float.NaN;
392 mInsertionMarkerTop = Float.NaN;
393 mInsertionMarkerBottom = Float.NaN;
394 mScale = 1.0f;
395 mTranslationX = 0.0f;
396 mTranslationY = 0.0f;
397 mLastCursorAnchorInfo = null;
398 mCursorAnchorInfoBuilder.reset();
399 }
400
401 public boolean onRequestCursorUpdates(int cursorUpdateMode, @Nonnull View vi ew) {
402 if (!mIsEditable)
403 return false;
404 final int knownRequestCursorUpdatesFlags =
405 InputConnection.CURSOR_UPDATE_MONITOR | InputConnection.CURSOR_U PDATE_IMMEDIATE;
406 if ((cursorUpdateMode & ~knownRequestCursorUpdatesFlags) != 0) {
407 // Does nothing when at least one unknown bit flag is set.
408 return false;
409 }
410 boolean updateImmediate =
411 (cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
412 boolean updateMonitor =
413 (cursorUpdateMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0;
414 mMonitorModeEnabled = updateMonitor;
415 if (updateImmediate) {
416 if (mLastCursorAnchorInfo == null && mHasCoordinateInfo) {
417 updateCursorAnchorInfo();
418 }
419 if (mLastCursorAnchorInfo != null) {
420 if (mInputMethodManagerWrapper != null) {
421 mInputMethodManagerWrapper.updateCursorAnchorInfo(view, mLas tCursorAnchorInfo);
422 }
423 } else {
424 mHasPendingRequest = true;
425 }
426 }
427 return true;
428 }
429
430 private void updateCursorAnchorInfo() {
431 synchronized (mCursorAnchorInfoBuilder) {
432 CharSequence composingText = getComposingText();
433 int composingTextStart = mComposingTextStart;
434 if (composingText != null) {
435 mCursorAnchorInfoBuilder.setComposingText(composingTextStart, co mposingText);
436 float[] compositionCharacterBounds = mCompositionCharacterBounds ;
437 if (compositionCharacterBounds != null) {
438 int numCharacter = compositionCharacterBounds.length / 4;
439 for (int i = 0; i < numCharacter; ++i) {
440 float left = compositionCharacterBounds[i * 4];
441 float top = compositionCharacterBounds[i * 4 + 1];
442 float right = compositionCharacterBounds[i * 4 + 2];
443 float bottom = compositionCharacterBounds[i * 4 + 3];
444 int charIndex = composingTextStart + i;
445 mCursorAnchorInfoBuilder.addCharacterBounds(charIndex, l eft, top, right,
446 bottom, CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION );
447 }
448 }
449 }
450 mCursorAnchorInfoBuilder.setSelectionRange(mSelectionStart, mSelecti onEnd);
451 mMatrix.setScale(mScale, mScale);
452 mMatrix.postTranslate(mTranslationX, mTranslationY);
453 mCursorAnchorInfoBuilder.setMatrix(mMatrix);
454 if (mHasInsertionMarker) {
455 mCursorAnchorInfoBuilder.setInsertionMarkerLocation(
456 mInsertionMarkerHorizontal,
457 mInsertionMarkerTop,
458 mInsertionMarkerBottom,
459 mInsertionMarkerBottom,
460 mIsInsertionMarkerVisible ? CursorAnchorInfo.FLAG_HAS_VI SIBLE_REGION :
461 CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION);
462 }
463 mLastCursorAnchorInfo = mCursorAnchorInfoBuilder.build();
464 mCursorAnchorInfoBuilder.reset();
465 }
466 }
467 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698