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