Introduce new API for floating window support
This CL introduces a new API IMM#updateCursorAnchorInfo for floating window support. BUG: 14579622 Change-Id: I61dec2f8fa671ba891da1d4af08975750e3acb04
This commit is contained in:
@@ -12867,6 +12867,7 @@ package android.inputmethodservice {
|
||||
method public void onStartInputView(android.view.inputmethod.EditorInfo, boolean);
|
||||
method public void onUnbindInput();
|
||||
method public void onUpdateCursor(android.graphics.Rect);
|
||||
method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
|
||||
method public void onUpdateExtractedText(int, android.view.inputmethod.ExtractedText);
|
||||
method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
|
||||
method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
|
||||
@@ -12916,6 +12917,7 @@ package android.inputmethodservice {
|
||||
method public void finishInput();
|
||||
method public void toggleSoftInput(int, int);
|
||||
method public void updateCursor(android.graphics.Rect);
|
||||
method public void updateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
|
||||
method public void updateExtractedText(int, android.view.inputmethod.ExtractedText);
|
||||
method public void updateSelection(int, int, int, int, int, int);
|
||||
method public void viewClicked(boolean);
|
||||
@@ -32548,6 +32550,34 @@ package android.view.inputmethod {
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
}
|
||||
|
||||
public final class CursorAnchorInfo implements android.os.Parcelable {
|
||||
ctor public CursorAnchorInfo(android.os.Parcel);
|
||||
method public int describeContents();
|
||||
method public int getCandidatesEnd();
|
||||
method public int getCandidatesStart();
|
||||
method public android.graphics.RectF getCharacterRect(int);
|
||||
method public float getInsertionMarkerBaseline();
|
||||
method public float getInsertionMarkerBottom();
|
||||
method public float getInsertionMarkerHorizontal();
|
||||
method public float getInsertionMarkerTop();
|
||||
method public android.graphics.Matrix getMatrix();
|
||||
method public int getSelectionEnd();
|
||||
method public int getSelectionStart();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
}
|
||||
|
||||
public static final class CursorAnchorInfo.CursorAnchorInfoBuilder {
|
||||
ctor public CursorAnchorInfo.CursorAnchorInfoBuilder();
|
||||
method public android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder addCharacterRect(int, float, float, float, float);
|
||||
method public android.view.inputmethod.CursorAnchorInfo build();
|
||||
method public void reset();
|
||||
method public android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder setCandidateRange(int, int);
|
||||
method public android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder setInsertionMarkerLocation(float, float, float, float);
|
||||
method public android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder setMatrix(android.graphics.Matrix);
|
||||
method public android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder setSelectionRange(int, int);
|
||||
}
|
||||
|
||||
public class EditorInfo implements android.text.InputType android.os.Parcelable {
|
||||
ctor public EditorInfo();
|
||||
method public int describeContents();
|
||||
@@ -32756,6 +32786,7 @@ package android.view.inputmethod {
|
||||
method public void toggleSoftInput(int, int);
|
||||
method public void toggleSoftInputFromWindow(android.os.IBinder, int, int);
|
||||
method public void updateCursor(android.view.View, int, int, int, int);
|
||||
method public void updateCursorAnchorInfo(android.view.View, android.view.inputmethod.CursorAnchorInfo);
|
||||
method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
|
||||
method public void updateSelection(android.view.View, int, int, int, int);
|
||||
method public void viewClicked(android.view.View);
|
||||
@@ -32778,6 +32809,7 @@ package android.view.inputmethod {
|
||||
method public abstract void finishInput();
|
||||
method public abstract void toggleSoftInput(int, int);
|
||||
method public abstract void updateCursor(android.graphics.Rect);
|
||||
method public abstract void updateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
|
||||
method public abstract void updateExtractedText(int, android.view.inputmethod.ExtractedText);
|
||||
method public abstract void updateSelection(int, int, int, int, int, int);
|
||||
method public abstract void viewClicked(boolean);
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.view.MotionEvent;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.InputMethodSession;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
|
||||
class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
implements HandlerCaller.Callback {
|
||||
@@ -46,6 +47,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
|
||||
private static final int DO_UPDATE_SELECTION = 90;
|
||||
private static final int DO_UPDATE_CURSOR = 95;
|
||||
private static final int DO_UPDATE_CURSOR_ANCHOR_INFO = 99;
|
||||
private static final int DO_APP_PRIVATE_COMMAND = 100;
|
||||
private static final int DO_TOGGLE_SOFT_INPUT = 105;
|
||||
private static final int DO_FINISH_SESSION = 110;
|
||||
@@ -108,6 +110,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
mInputMethodSession.updateCursor((Rect)msg.obj);
|
||||
return;
|
||||
}
|
||||
case DO_UPDATE_CURSOR_ANCHOR_INFO: {
|
||||
mInputMethodSession.updateCursorAnchorInfo((CursorAnchorInfo)msg.obj);
|
||||
return;
|
||||
}
|
||||
case DO_APP_PRIVATE_COMMAND: {
|
||||
SomeArgs args = (SomeArgs)msg.obj;
|
||||
mInputMethodSession.appPrivateCommand((String)args.arg1,
|
||||
@@ -180,6 +186,12 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
||||
mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
|
||||
mCaller.executeOrSendMessage(
|
||||
mCaller.obtainMessageO(DO_UPDATE_CURSOR_ANCHOR_INFO, cursorAnchorInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appPrivateCommand(String action, Bundle data) {
|
||||
mCaller.executeOrSendMessage(
|
||||
|
||||
@@ -51,6 +51,7 @@ import android.view.WindowManager;
|
||||
import android.view.WindowManager.BadTokenException;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
@@ -545,6 +546,17 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
public void toggleSoftInput(int showFlags, int hideFlags) {
|
||||
InputMethodService.this.onToggleSoftInput(showFlags, hideFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link InputMethodService#onUpdateCursorAnchorInfo
|
||||
* InputMethodService.onUpdateCursorAnchorInfo()}.
|
||||
*/
|
||||
public void updateCursorAnchorInfo(CursorAnchorInfo info) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
InputMethodService.this.onUpdateCursorAnchorInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1716,6 +1728,17 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the application has reported a new location of its text insertion point and
|
||||
* characters in the composition string. This is only called if explicitly requested by the
|
||||
* input method. The default implementation does nothing.
|
||||
* @param cursorAnchorInfo The positional information of the text insertion point and the
|
||||
* composition string.
|
||||
*/
|
||||
public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cursor/anthor monitor mode.
|
||||
*/
|
||||
|
||||
@@ -88,16 +88,15 @@ public final class CorrectionInfo implements Parcelable {
|
||||
/**
|
||||
* Used to make this class parcelable.
|
||||
*/
|
||||
public static final Parcelable.Creator<CorrectionInfo> CREATOR
|
||||
= new Parcelable.Creator<CorrectionInfo>() {
|
||||
public CorrectionInfo createFromParcel(Parcel source) {
|
||||
return new CorrectionInfo(source);
|
||||
}
|
||||
|
||||
public CorrectionInfo[] newArray(int size) {
|
||||
return new CorrectionInfo[size];
|
||||
}
|
||||
};
|
||||
public static final Parcelable.Creator<CorrectionInfo> CREATOR =
|
||||
new Parcelable.Creator<CorrectionInfo>() {
|
||||
public CorrectionInfo createFromParcel(Parcel source) {
|
||||
return new CorrectionInfo(source);
|
||||
}
|
||||
public CorrectionInfo[] newArray(int size) {
|
||||
return new CorrectionInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
||||
19
core/java/android/view/inputmethod/CursorAnchorInfo.aidl
Normal file
19
core/java/android/view/inputmethod/CursorAnchorInfo.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
parcelable CursorAnchorInfo;
|
||||
449
core/java/android/view/inputmethod/CursorAnchorInfo.java
Normal file
449
core/java/android/view/inputmethod/CursorAnchorInfo.java
Normal file
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Layout;
|
||||
import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Positional information about the text insertion point and characters in the composition string.
|
||||
*
|
||||
* <p>This class encapsulates locations of the text insertion point and the composition string in
|
||||
* the screen coordinates so that IMEs can render their UI components near where the text is
|
||||
* actually inserted.</p>
|
||||
*/
|
||||
public final class CursorAnchorInfo implements Parcelable {
|
||||
private final int mSelectionStart;
|
||||
private final int mSelectionEnd;
|
||||
private final int mCandidatesStart;
|
||||
private final int mCandidatesEnd;
|
||||
|
||||
/**
|
||||
* Horizontal position of the insertion marker, in the local coordinates that will be
|
||||
* transformed with the transformation matrix when rendered on the screen. This should be
|
||||
* calculated or compatible with {@link Layout#getPrimaryHorizontal(int)}. This can be
|
||||
* {@code java.lang.Float.NaN} when no value is specified.
|
||||
*/
|
||||
private final float mInsertionMarkerHorizontal;
|
||||
/**
|
||||
* Vertical position of the insertion marker, in the local coordinates that will be
|
||||
* transformed with the transformation matrix when rendered on the screen. This should be
|
||||
* calculated or compatible with {@link Layout#getLineTop(int)}. This can be
|
||||
* {@code java.lang.Float.NaN} when no value is specified.
|
||||
*/
|
||||
private final float mInsertionMarkerTop;
|
||||
/**
|
||||
* Vertical position of the insertion marker, in the local coordinates that will be
|
||||
* transformed with the transformation matrix when rendered on the screen. This should be
|
||||
* calculated or compatible with {@link Layout#getLineBaseline(int)}. This can be
|
||||
* {@code java.lang.Float.NaN} when no value is specified.
|
||||
*/
|
||||
private final float mInsertionMarkerBaseline;
|
||||
/**
|
||||
* Vertical position of the insertion marker, in the local coordinates that will be
|
||||
* transformed with the transformation matrix when rendered on the screen. This should be
|
||||
* calculated or compatible with {@link Layout#getLineBottom(int)}. This can be
|
||||
* {@code java.lang.Float.NaN} when no value is specified.
|
||||
*/
|
||||
private final float mInsertionMarkerBottom;
|
||||
|
||||
/**
|
||||
* Container of rectangular position of characters, keyed with character index in a unit of
|
||||
* Java chars, in the local coordinates that will be transformed with the transformation matrix
|
||||
* when rendered on the screen.
|
||||
*/
|
||||
private final SparseRectFArray mCharacterRects;
|
||||
|
||||
/**
|
||||
* Transformation matrix that is applied to any positional information of this class to
|
||||
* transform local coordinates into screen coordinates.
|
||||
*/
|
||||
private final Matrix mMatrix;
|
||||
|
||||
public CursorAnchorInfo(final Parcel source) {
|
||||
mSelectionStart = source.readInt();
|
||||
mSelectionEnd = source.readInt();
|
||||
mCandidatesStart = source.readInt();
|
||||
mCandidatesEnd = source.readInt();
|
||||
mInsertionMarkerHorizontal = source.readFloat();
|
||||
mInsertionMarkerTop = source.readFloat();
|
||||
mInsertionMarkerBaseline = source.readFloat();
|
||||
mInsertionMarkerBottom = source.readFloat();
|
||||
mCharacterRects = source.readParcelable(SparseRectFArray.class.getClassLoader());
|
||||
mMatrix = new Matrix();
|
||||
mMatrix.setValues(source.createFloatArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to package this object into a {@link Parcel}.
|
||||
*
|
||||
* @param dest The {@link Parcel} to be written.
|
||||
* @param flags The flags used for parceling.
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mSelectionStart);
|
||||
dest.writeInt(mSelectionEnd);
|
||||
dest.writeInt(mCandidatesStart);
|
||||
dest.writeInt(mCandidatesEnd);
|
||||
dest.writeFloat(mInsertionMarkerHorizontal);
|
||||
dest.writeFloat(mInsertionMarkerTop);
|
||||
dest.writeFloat(mInsertionMarkerBaseline);
|
||||
dest.writeFloat(mInsertionMarkerBottom);
|
||||
dest.writeParcelable(mCharacterRects, flags);
|
||||
final float[] matrixArray = new float[9];
|
||||
mMatrix.getValues(matrixArray);
|
||||
dest.writeFloatArray(matrixArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(){
|
||||
// TODO: Improve the hash function.
|
||||
final float floatHash = mSelectionStart + mSelectionEnd + mCandidatesStart + mCandidatesEnd
|
||||
+ mInsertionMarkerHorizontal + mInsertionMarkerTop + mInsertionMarkerBaseline
|
||||
+ mInsertionMarkerBottom;
|
||||
int hash = floatHash > 0 ? (int) floatHash : (int)(-floatHash);
|
||||
if (mCharacterRects != null) {
|
||||
hash += mCharacterRects.hashCode();
|
||||
}
|
||||
hash += mMatrix.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj){
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof CursorAnchorInfo)) {
|
||||
return false;
|
||||
}
|
||||
final CursorAnchorInfo that = (CursorAnchorInfo) obj;
|
||||
if (hashCode() != that.hashCode()) {
|
||||
return false;
|
||||
}
|
||||
if (mSelectionStart != that.mSelectionStart
|
||||
|| mSelectionEnd != that.mSelectionEnd
|
||||
|| mCandidatesStart != that.mCandidatesStart
|
||||
|| mCandidatesEnd != that.mCandidatesEnd) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(mCharacterRects, that.mCharacterRects)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(mMatrix, that.mMatrix)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SelectionInfo{mSelection=" + mSelectionStart + "," + mSelectionEnd
|
||||
+ " mCandiadtes=" + mCandidatesStart + "," + mCandidatesEnd
|
||||
+ " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
|
||||
+ " mInsertionMarkerTop=" + mInsertionMarkerTop
|
||||
+ " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
|
||||
+ " mInsertionMarkerBottom=" + mInsertionMarkerBottom
|
||||
+ " mCharacterRects=" + (mCharacterRects != null ? mCharacterRects : "null")
|
||||
+ " mMatrix=" + mMatrix
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe.
|
||||
*/
|
||||
public static final class CursorAnchorInfoBuilder {
|
||||
/**
|
||||
* Sets the text range of the selection. Calling this can be skipped if there is no
|
||||
* selection.
|
||||
*/
|
||||
public CursorAnchorInfoBuilder setSelectionRange(final int newStart, final int newEnd) {
|
||||
mSelectionStart = newStart;
|
||||
mSelectionEnd = newEnd;
|
||||
return this;
|
||||
}
|
||||
private int mSelectionStart = -1;
|
||||
private int mSelectionEnd = -1;
|
||||
|
||||
/**
|
||||
* Sets the text range of the composition string. Calling this can be skipped if there is
|
||||
* no composition.
|
||||
*/
|
||||
public CursorAnchorInfoBuilder setCandidateRange(final int start, final int end) {
|
||||
mCandidateStart = start;
|
||||
mCandidateEnd = end;
|
||||
return this;
|
||||
}
|
||||
private int mCandidateStart = -1;
|
||||
private int mCandidateEnd = -1;
|
||||
|
||||
/**
|
||||
* Sets the location of the text insertion point (zero width cursor) as a rectangle in
|
||||
* local coordinates. Calling this can be skipped when there is no text insertion point;
|
||||
* however if there is an insertion point, editors must call this method.
|
||||
* @param horizontalPosition horizontal position of the insertion marker, in the local
|
||||
* coordinates that will be transformed with the transformation matrix when rendered on the
|
||||
* screen. This should be calculated or compatible with
|
||||
* {@link Layout#getPrimaryHorizontal(int)}.
|
||||
* @param lineTop vertical position of the insertion marker, in the local coordinates that
|
||||
* will be transformed with the transformation matrix when rendered on the screen. This
|
||||
* should be calculated or compatible with {@link Layout#getLineTop(int)}.
|
||||
* @param lineBaseline vertical position of the insertion marker, in the local coordinates
|
||||
* that will be transformed with the transformation matrix when rendered on the screen. This
|
||||
* should be calculated or compatible with {@link Layout#getLineBaseline(int)}.
|
||||
* @param lineBottom vertical position of the insertion marker, in the local coordinates
|
||||
* that will be transformed with the transformation matrix when rendered on the screen. This
|
||||
* should be calculated or compatible with {@link Layout#getLineBottom(int)}.
|
||||
*/
|
||||
public CursorAnchorInfoBuilder setInsertionMarkerLocation(
|
||||
final float horizontalPosition, final float lineTop, final float lineBaseline,
|
||||
final float lineBottom){
|
||||
mInsertionMarkerHorizontal = horizontalPosition;
|
||||
mInsertionMarkerTop = lineTop;
|
||||
mInsertionMarkerBaseline = lineBaseline;
|
||||
mInsertionMarkerBottom = lineBottom;
|
||||
return this;
|
||||
}
|
||||
private float mInsertionMarkerHorizontal = Float.NaN;
|
||||
private float mInsertionMarkerTop = Float.NaN;
|
||||
private float mInsertionMarkerBaseline = Float.NaN;
|
||||
private float mInsertionMarkerBottom = Float.NaN;
|
||||
|
||||
/**
|
||||
* Adds the bounding box of the character specified with the index.
|
||||
* <p>
|
||||
* Editor authors should not call this method for characters that are invisible.
|
||||
* </p>
|
||||
*
|
||||
* @param index index of the character in Java chars units. Must be specified in
|
||||
* ascending order across successive calls.
|
||||
* @param leadingEdgeX x coordinate of the leading edge of the character in local
|
||||
* coordinates, that is, left edge for LTR text and right edge for RTL text.
|
||||
* @param leadingEdgeY y coordinate of the leading edge of the character in local
|
||||
* coordinates.
|
||||
* @param trailingEdgeX x coordinate of the trailing edge of the character in local
|
||||
* coordinates, that is, right edge for LTR text and left edge for RTL text.
|
||||
* @param trailingEdgeY y coordinate of the trailing edge of the character in local
|
||||
* coordinates.
|
||||
* @throws IllegalArgumentException If the index is a negative value, or not greater than
|
||||
* all of the previously called indices.
|
||||
*/
|
||||
public CursorAnchorInfoBuilder addCharacterRect(final int index,
|
||||
final float leadingEdgeX, final float leadingEdgeY, final float trailingEdgeX,
|
||||
final float trailingEdgeY) {
|
||||
if (index < 0) {
|
||||
throw new IllegalArgumentException("index must not be a negative integer.");
|
||||
}
|
||||
if (mCharacterRectBuilder == null) {
|
||||
mCharacterRectBuilder = new SparseRectFArrayBuilder();
|
||||
}
|
||||
mCharacterRectBuilder.append(index, leadingEdgeX, leadingEdgeY, trailingEdgeX,
|
||||
trailingEdgeY);
|
||||
return this;
|
||||
}
|
||||
private SparseRectFArrayBuilder mCharacterRectBuilder = null;
|
||||
|
||||
/**
|
||||
* Sets the matrix that transforms local coordinates into screen coordinates.
|
||||
* @param matrix transformation matrix from local coordinates into screen coordinates. null
|
||||
* is interpreted as an identity matrix.
|
||||
*/
|
||||
public CursorAnchorInfoBuilder setMatrix(final Matrix matrix) {
|
||||
if (matrix != null) {
|
||||
mMatrix = matrix;
|
||||
} else {
|
||||
mMatrix = Matrix.IDENTITY_MATRIX;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
private Matrix mMatrix = Matrix.IDENTITY_MATRIX;
|
||||
|
||||
/**
|
||||
* @return {@link CursorAnchorInfo} using parameters in this
|
||||
* {@link CursorAnchorInfoBuilder}.
|
||||
*/
|
||||
public CursorAnchorInfo build() {
|
||||
return new CursorAnchorInfo(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the internal state so that this instance can be reused to build another
|
||||
* instance of {@link CursorAnchorInfo}.
|
||||
*/
|
||||
public void reset() {
|
||||
mSelectionStart = -1;
|
||||
mSelectionEnd = -1;
|
||||
mCandidateStart = -1;
|
||||
mCandidateEnd = -1;
|
||||
mInsertionMarkerHorizontal = Float.NaN;
|
||||
mInsertionMarkerTop = Float.NaN;
|
||||
mInsertionMarkerBaseline = Float.NaN;
|
||||
mInsertionMarkerBottom = Float.NaN;
|
||||
mMatrix = Matrix.IDENTITY_MATRIX;
|
||||
if (mCharacterRectBuilder != null) {
|
||||
mCharacterRectBuilder.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CursorAnchorInfo(final CursorAnchorInfoBuilder builder) {
|
||||
mSelectionStart = builder.mSelectionStart;
|
||||
mSelectionEnd = builder.mSelectionEnd;
|
||||
mCandidatesStart = builder.mCandidateStart;
|
||||
mCandidatesEnd = builder.mCandidateEnd;
|
||||
mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal;
|
||||
mInsertionMarkerTop = builder.mInsertionMarkerTop;
|
||||
mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline;
|
||||
mInsertionMarkerBottom = builder.mInsertionMarkerBottom;
|
||||
mCharacterRects = builder.mCharacterRectBuilder != null ?
|
||||
builder.mCharacterRectBuilder.build() : null;
|
||||
mMatrix = builder.mMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index where the selection starts.
|
||||
* @return -1 if there is no selection.
|
||||
*/
|
||||
public int getSelectionStart() {
|
||||
return mSelectionStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index where the selection ends.
|
||||
* @return -1 if there is no selection.
|
||||
*/
|
||||
public int getSelectionEnd() {
|
||||
return mSelectionEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index where the composition starts.
|
||||
* @return -1 if there is no composition.
|
||||
*/
|
||||
public int getCandidatesStart() {
|
||||
return mCandidatesStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index where the composition ends.
|
||||
* @return -1 if there is no composition.
|
||||
*/
|
||||
public int getCandidatesEnd() {
|
||||
return mCandidatesEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the horizontal start of the insertion marker, in the local coordinates that will
|
||||
* be transformed with {@link #getMatrix()} when rendered on the screen.
|
||||
* @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}.
|
||||
* Pay special care to RTL/LTR handling.
|
||||
* {@code java.lang.Float.NaN} if not specified.
|
||||
* @see Layout#getPrimaryHorizontal(int)
|
||||
*/
|
||||
public float getInsertionMarkerHorizontal() {
|
||||
return mInsertionMarkerHorizontal;
|
||||
}
|
||||
/**
|
||||
* Returns the vertical top position of the insertion marker, in the local coordinates that
|
||||
* will be transformed with {@link #getMatrix()} when rendered on the screen.
|
||||
* @return y coordinate that is compatible with {@link Layout#getLineTop(int)}.
|
||||
* {@code java.lang.Float.NaN} if not specified.
|
||||
*/
|
||||
public float getInsertionMarkerTop() {
|
||||
return mInsertionMarkerTop;
|
||||
}
|
||||
/**
|
||||
* Returns the vertical baseline position of the insertion marker, in the local coordinates
|
||||
* that will be transformed with {@link #getMatrix()} when rendered on the screen.
|
||||
* @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}.
|
||||
* {@code java.lang.Float.NaN} if not specified.
|
||||
*/
|
||||
public float getInsertionMarkerBaseline() {
|
||||
return mInsertionMarkerBaseline;
|
||||
}
|
||||
/**
|
||||
* Returns the vertical bottom position of the insertion marker, in the local coordinates
|
||||
* that will be transformed with {@link #getMatrix()} when rendered on the screen.
|
||||
* @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}.
|
||||
* {@code java.lang.Float.NaN} if not specified.
|
||||
*/
|
||||
public float getInsertionMarkerBottom() {
|
||||
return mInsertionMarkerBottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@link RectF} that indicates the location of the character
|
||||
* specified with the index.
|
||||
* <p>
|
||||
* Note that coordinates are not necessarily contiguous or even monotonous, especially when
|
||||
* RTL text and LTR text are mixed.
|
||||
* </p>
|
||||
* @param index index of the character in a Java chars.
|
||||
* @return a new instance of {@link RectF} that represents the location of the character in
|
||||
* local coordinates. null if the character is invisible or the application did not provide
|
||||
* the location. Note that the {@code left} field can be greater than the {@code right} field
|
||||
* if the character is in RTL text.
|
||||
*/
|
||||
// TODO: Prepare a document about the expected behavior for surrogate pairs, combining
|
||||
// characters, and non-graphical chars.
|
||||
public RectF getCharacterRect(final int index) {
|
||||
if (mCharacterRects == null) {
|
||||
return null;
|
||||
}
|
||||
return mCharacterRects.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
|
||||
* matrix that is to be applied other positional data in this class.
|
||||
* @return a new instance (copy) of the transformation matrix.
|
||||
*/
|
||||
public Matrix getMatrix() {
|
||||
return new Matrix(mMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to make this class parcelable.
|
||||
*/
|
||||
public static final Parcelable.Creator<CursorAnchorInfo> CREATOR
|
||||
= new Parcelable.Creator<CursorAnchorInfo>() {
|
||||
@Override
|
||||
public CursorAnchorInfo createFromParcel(Parcel source) {
|
||||
return new CursorAnchorInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorAnchorInfo[] newArray(int size) {
|
||||
return new CursorAnchorInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import android.view.InputEventSender;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -321,6 +322,7 @@ public final class InputMethodManager {
|
||||
* The buffer to retrieve the view location in screen coordinates in {@link #updateCursor}.
|
||||
*/
|
||||
private final int[] mViewTopLeft = new int[2];
|
||||
private final CursorAnchorInfoBuilder mCursorAnchorInfoBuilder = new CursorAnchorInfoBuilder();
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
@@ -1435,7 +1437,7 @@ public final class InputMethodManager {
|
||||
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
|
||||
|| mCursorCandStart != candidatesStart
|
||||
|| mCursorCandEnd != candidatesEnd) {
|
||||
@@ -1555,6 +1557,31 @@ public final class InputMethodManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report positional change of the text insertion point and/or characters in the composition
|
||||
* string.
|
||||
*/
|
||||
public void updateCursorAnchorInfo(View view, final CursorAnchorInfo cursorAnchorInfo) {
|
||||
if (view == null || cursorAnchorInfo == null) {
|
||||
return;
|
||||
}
|
||||
checkFocus();
|
||||
synchronized (mH) {
|
||||
if ((mServedView != view &&
|
||||
(mServedView == null || !mServedView.checkInputConnectionProxy(view)))
|
||||
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "updateCursorAnchorInfo");
|
||||
|
||||
try {
|
||||
mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "IME died: " + mCurId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
|
||||
* InputMethodSession.appPrivateCommand()} on the current Input Method.
|
||||
|
||||
@@ -165,7 +165,7 @@ public interface InputMethodSession {
|
||||
public void appPrivateCommand(String action, Bundle data);
|
||||
|
||||
/**
|
||||
* Toggle the soft input window.
|
||||
* Toggle the soft input window.
|
||||
* Applications can toggle the state of the soft input window.
|
||||
* @param showFlags Provides additional operating flags. May be
|
||||
* 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
|
||||
@@ -175,4 +175,14 @@ public interface InputMethodSession {
|
||||
* {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
|
||||
*/
|
||||
public void toggleSoftInput(int showFlags, int hideFlags);
|
||||
|
||||
/**
|
||||
* This method is called when the cursor and/or the character position relevant to text input
|
||||
* is changed on the screen. This is not called by default. It will only be reported if
|
||||
* requested by the input method.
|
||||
*
|
||||
* @param cursorAnchorInfo Positional information relevant to text input, such as text
|
||||
* insertion point and composition string.
|
||||
*/
|
||||
public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
|
||||
}
|
||||
|
||||
265
core/java/android/view/inputmethod/SparseRectFArray.java
Normal file
265
core/java/android/view/inputmethod/SparseRectFArray.java
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* An implementation of SparseArray specialized for {@link android.graphics.RectF}.
|
||||
* <p>
|
||||
* As this is a sparse array, it represents an array of {@link RectF} most of which are null. This
|
||||
* class could be in some other packages like android.graphics or android.util but currently
|
||||
* belong to android.view.inputmethod because this class is hidden and used only in input method
|
||||
* framework.
|
||||
* </p>
|
||||
* @hide
|
||||
*/
|
||||
public final class SparseRectFArray implements Parcelable {
|
||||
/**
|
||||
* The keys, in ascending order, of those {@link RectF} that are not null. For example,
|
||||
* {@code [null, null, null, Rect1, null, Rect2]} would be represented by {@code [3,5]}.
|
||||
* @see #mCoordinates
|
||||
*/
|
||||
private final int[] mKeys;
|
||||
|
||||
/**
|
||||
* Stores coordinates of the rectangles, in the order of
|
||||
* {@code rects[mKeys[0]].left}, {@code rects[mKeys[0]].top},
|
||||
* {@code rects[mKeys[0]].right}, {@code rects[mKeys[0]].bottom},
|
||||
* {@code rects[mKeys[1]].left}, {@code rects[mKeys[1]].top},
|
||||
* {@code rects[mKeys[1]].right}, {@code rects[mKeys[1]].bottom},
|
||||
* {@code rects[mKeys[2]].left}, {@code rects[mKeys[2]].top}, ....
|
||||
*/
|
||||
private final float[] mCoordinates;
|
||||
|
||||
public SparseRectFArray(final Parcel source) {
|
||||
mKeys = source.createIntArray();
|
||||
mCoordinates = source.createFloatArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to package this object into a {@link Parcel}.
|
||||
*
|
||||
* @param dest The {@link Parcel} to be written.
|
||||
* @param flags The flags used for parceling.
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeIntArray(mKeys);
|
||||
dest.writeFloatArray(mCoordinates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// TODO: Improve the hash function.
|
||||
if (mKeys == null || mKeys.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int hash = mKeys.length;
|
||||
// For performance reasons, only the first rectangle is used for the hash code now.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
hash *= 31;
|
||||
hash += mCoordinates[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj){
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof SparseRectFArray)) {
|
||||
return false;
|
||||
}
|
||||
final SparseRectFArray that = (SparseRectFArray) obj;
|
||||
|
||||
return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mKeys == null || mCoordinates == null) {
|
||||
return "SparseRectFArray{}";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("SparseRectFArray{");
|
||||
for (int i = 0; i < mKeys.length; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
final int baseIndex = i * 4;
|
||||
sb.append(mKeys[i]);
|
||||
sb.append(":[");
|
||||
sb.append(mCoordinates[baseIndex + 0]);
|
||||
sb.append(",");
|
||||
sb.append(mCoordinates[baseIndex + 1]);
|
||||
sb.append("],[");
|
||||
sb.append(mCoordinates[baseIndex + 2]);
|
||||
sb.append(",");
|
||||
sb.append(mCoordinates[baseIndex + 3]);
|
||||
sb.append("]");
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link SparseRectFArray}. This class is not designed to be thread-safe.
|
||||
* @hide
|
||||
*/
|
||||
public static final class SparseRectFArrayBuilder {
|
||||
/**
|
||||
* Throws {@link IllegalArgumentException} to make sure that this class is correctly used.
|
||||
* @param key key to be checked.
|
||||
*/
|
||||
private void checkIndex(final int key) {
|
||||
if (mCount == 0) {
|
||||
return;
|
||||
}
|
||||
if (mKeys[mCount - 1] >= key) {
|
||||
throw new IllegalArgumentException("key must be greater than all existing keys.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the internal array if necessary.
|
||||
*/
|
||||
private void ensureBufferSize() {
|
||||
if (mKeys == null) {
|
||||
mKeys = new int[INITIAL_SIZE];
|
||||
}
|
||||
if (mCoordinates == null) {
|
||||
mCoordinates = new float[INITIAL_SIZE * 4];
|
||||
}
|
||||
final int requiredIndexArraySize = mCount + 1;
|
||||
if (mKeys.length <= requiredIndexArraySize) {
|
||||
final int[] newArray = new int[requiredIndexArraySize * 2];
|
||||
System.arraycopy(mKeys, 0, newArray, 0, mCount);
|
||||
mKeys = newArray;
|
||||
}
|
||||
final int requiredCoordinatesArraySize = (mCount + 1) * 4;
|
||||
if (mCoordinates.length <= requiredCoordinatesArraySize) {
|
||||
final float[] newArray = new float[requiredCoordinatesArraySize * 2];
|
||||
System.arraycopy(mCoordinates, 0, newArray, 0, mCount * 4);
|
||||
mCoordinates = newArray;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the rectangle with an integer key.
|
||||
* @param key the key to be associated with the rectangle. It must be greater than all
|
||||
* existing keys that have been previously specified.
|
||||
* @param left left of the rectangle.
|
||||
* @param top top of the rectangle.
|
||||
* @param right right of the rectangle.
|
||||
* @param bottom bottom of the rectangle.
|
||||
* @return the receiver object itself for chaining method calls.
|
||||
* @throws IllegalArgumentException If the index is not greater than all of existing keys.
|
||||
*/
|
||||
public SparseRectFArrayBuilder append(final int key,
|
||||
final float left, final float top, final float right, final float bottom) {
|
||||
checkIndex(key);
|
||||
ensureBufferSize();
|
||||
final int baseCoordinatesIndex = mCount * 4;
|
||||
mCoordinates[baseCoordinatesIndex + 0] = left;
|
||||
mCoordinates[baseCoordinatesIndex + 1] = top;
|
||||
mCoordinates[baseCoordinatesIndex + 2] = right;
|
||||
mCoordinates[baseCoordinatesIndex + 3] = bottom;
|
||||
mKeys[mCount] = key;
|
||||
++mCount;
|
||||
return this;
|
||||
}
|
||||
private int mCount = 0;
|
||||
private int[] mKeys = null;
|
||||
private float[] mCoordinates = null;
|
||||
private static int INITIAL_SIZE = 16;
|
||||
|
||||
/**
|
||||
* @return {@link SparseRectFArray} using parameters in this {@link SparseRectFArray}.
|
||||
*/
|
||||
public SparseRectFArray build() {
|
||||
return new SparseRectFArray(this);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (mCount == 0) {
|
||||
mKeys = null;
|
||||
mCoordinates = null;
|
||||
}
|
||||
mCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private SparseRectFArray(final SparseRectFArrayBuilder builder) {
|
||||
if (builder.mCount == 0) {
|
||||
mKeys = null;
|
||||
mCoordinates = null;
|
||||
} else {
|
||||
mKeys = new int[builder.mCount];
|
||||
mCoordinates = new float[builder.mCount * 4];
|
||||
System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount);
|
||||
System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4);
|
||||
}
|
||||
}
|
||||
|
||||
public RectF get(final int index) {
|
||||
if (mKeys == null) {
|
||||
return null;
|
||||
}
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
final int arrayIndex = Arrays.binarySearch(mKeys, index);
|
||||
if (arrayIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
final int baseCoordIndex = arrayIndex * 4;
|
||||
return new RectF(mCoordinates[baseCoordIndex],
|
||||
mCoordinates[baseCoordIndex + 1],
|
||||
mCoordinates[baseCoordIndex + 2],
|
||||
mCoordinates[baseCoordIndex + 3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to make this class parcelable.
|
||||
*/
|
||||
public static final Parcelable.Creator<SparseRectFArray> CREATOR =
|
||||
new Parcelable.Creator<SparseRectFArray>() {
|
||||
@Override
|
||||
public SparseRectFArray createFromParcel(Parcel source) {
|
||||
return new SparseRectFArray(source);
|
||||
}
|
||||
@Override
|
||||
public SparseRectFArray[] newArray(int size) {
|
||||
return new SparseRectFArray[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
|
||||
/**
|
||||
@@ -47,4 +48,6 @@ oneway interface IInputMethodSession {
|
||||
void toggleSoftInput(int showFlags, int hideFlags);
|
||||
|
||||
void finishSession();
|
||||
|
||||
void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
|
||||
}
|
||||
|
||||
@@ -21,4 +21,4 @@ if [[ $rebuild == true ]]; then
|
||||
$COMMAND
|
||||
fi
|
||||
|
||||
adb shell am instrument -w -e class android.os.InputMethodTest,android.os.InputMethodSubtypeArrayTest,android.os.InputMethodSubtypeSwitchingControllerTest com.android.frameworks.coretests.inputmethod/android.test.InstrumentationTestRunner
|
||||
adb shell am instrument -w -e class android.os.InputMethodTest,android.os.InputMethodSubtypeArrayTest,android.os.InputMethodSubtypeSwitchingControllerTest,android.os.CursorAnchorInfoTest,android.os.SparseRectFArrayTest com.android.frameworks.coretests.inputmethod/android.test.InstrumentationTestRunner
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.os;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder;
|
||||
|
||||
public class CursorAnchorInfoTest extends InstrumentationTestCase {
|
||||
// null represents a character that is invisible, for example because it's overlapped by some
|
||||
// other UI elements.
|
||||
private static final RectF[] MANY_RECTS = new RectF[] {
|
||||
null,
|
||||
new RectF(102.0f, 202.0f, 302.0f, 402.0f),
|
||||
new RectF(103.0f, 203.0f, 303.0f, 403.0f),
|
||||
new RectF(104.0f, 204.0f, 304.0f, 404.0f),
|
||||
new RectF(105.0f, 205.0f, 305.0f, 405.0f),
|
||||
new RectF(106.0f, 206.0f, 306.0f, 406.0f),
|
||||
null,
|
||||
new RectF(108.0f, 208.0f, 308.0f, 408.0f),
|
||||
new RectF(109.0f, 209.0f, 309.0f, 409.0f),
|
||||
new RectF(110.0f, 210.0f, 310.0f, 410.0f),
|
||||
new RectF(111.0f, 211.0f, 311.0f, 411.0f),
|
||||
new RectF(112.0f, 212.0f, 312.0f, 412.0f),
|
||||
new RectF(113.0f, 213.0f, 313.0f, 413.0f),
|
||||
new RectF(114.0f, 214.0f, 314.0f, 414.0f),
|
||||
new RectF(115.0f, 215.0f, 315.0f, 415.0f),
|
||||
new RectF(116.0f, 216.0f, 316.0f, 416.0f),
|
||||
new RectF(117.0f, 217.0f, 317.0f, 417.0f),
|
||||
null,
|
||||
null,
|
||||
};
|
||||
|
||||
@SmallTest
|
||||
public void testBuilder() throws Exception {
|
||||
final int SELECTION_START = 30;
|
||||
final int SELECTION_END = 40;
|
||||
final int CANDIDATES_START = 32;
|
||||
final int CANDIDATES_END = 33;
|
||||
final float INSERTION_MARKER_HORIZONTAL = 10.5f;
|
||||
final float INSERTION_MARKER_TOP = 100.1f;
|
||||
final float INSERTION_MARKER_BASELINE = 110.4f;
|
||||
final float INSERTION_MARKER_BOTOM = 111.0f;
|
||||
Matrix TRANSFORM_MATRIX = new Matrix(Matrix.IDENTITY_MATRIX);
|
||||
TRANSFORM_MATRIX.setScale(10.0f, 20.0f);
|
||||
|
||||
final CursorAnchorInfoBuilder builder = new CursorAnchorInfoBuilder();
|
||||
builder.setSelectionRange(SELECTION_START, SELECTION_END)
|
||||
.setCandidateRange(CANDIDATES_START, CANDIDATES_END)
|
||||
.setInsertionMarkerLocation(INSERTION_MARKER_HORIZONTAL, INSERTION_MARKER_TOP,
|
||||
INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM)
|
||||
.setMatrix(TRANSFORM_MATRIX);
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
if (rect != null) {
|
||||
builder.addCharacterRect(i, rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
final CursorAnchorInfo info = builder.build();
|
||||
assertEquals(SELECTION_START, info.getSelectionStart());
|
||||
assertEquals(SELECTION_END, info.getSelectionEnd());
|
||||
assertEquals(CANDIDATES_START, info.getCandidatesStart());
|
||||
assertEquals(CANDIDATES_END, info.getCandidatesEnd());
|
||||
assertEquals(INSERTION_MARKER_HORIZONTAL, info.getInsertionMarkerHorizontal());
|
||||
assertEquals(INSERTION_MARKER_TOP, info.getInsertionMarkerTop());
|
||||
assertEquals(INSERTION_MARKER_BASELINE, info.getInsertionMarkerBaseline());
|
||||
assertEquals(INSERTION_MARKER_BOTOM, info.getInsertionMarkerBottom());
|
||||
assertEquals(TRANSFORM_MATRIX, info.getMatrix());
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
assertEquals(rect, info.getCharacterRect(i));
|
||||
}
|
||||
|
||||
// Make sure that the builder can reproduce the same object.
|
||||
final CursorAnchorInfo info2 = builder.build();
|
||||
assertEquals(SELECTION_START, info2.getSelectionStart());
|
||||
assertEquals(SELECTION_END, info2.getSelectionEnd());
|
||||
assertEquals(CANDIDATES_START, info2.getCandidatesStart());
|
||||
assertEquals(CANDIDATES_END, info2.getCandidatesEnd());
|
||||
assertEquals(INSERTION_MARKER_HORIZONTAL, info2.getInsertionMarkerHorizontal());
|
||||
assertEquals(INSERTION_MARKER_TOP, info2.getInsertionMarkerTop());
|
||||
assertEquals(INSERTION_MARKER_BASELINE, info2.getInsertionMarkerBaseline());
|
||||
assertEquals(INSERTION_MARKER_BOTOM, info2.getInsertionMarkerBottom());
|
||||
assertEquals(TRANSFORM_MATRIX, info2.getMatrix());
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
assertEquals(rect, info2.getCharacterRect(i));
|
||||
}
|
||||
assertEquals(info, info2);
|
||||
assertEquals(info.hashCode(), info2.hashCode());
|
||||
|
||||
// Make sure that object can be marshalled via {@link Parsel}.
|
||||
final CursorAnchorInfo info3 = cloneViaParcel(info2);
|
||||
assertEquals(SELECTION_START, info3.getSelectionStart());
|
||||
assertEquals(SELECTION_END, info3.getSelectionEnd());
|
||||
assertEquals(CANDIDATES_START, info3.getCandidatesStart());
|
||||
assertEquals(CANDIDATES_END, info3.getCandidatesEnd());
|
||||
assertEquals(INSERTION_MARKER_HORIZONTAL, info3.getInsertionMarkerHorizontal());
|
||||
assertEquals(INSERTION_MARKER_TOP, info3.getInsertionMarkerTop());
|
||||
assertEquals(INSERTION_MARKER_BASELINE, info3.getInsertionMarkerBaseline());
|
||||
assertEquals(INSERTION_MARKER_BOTOM, info3.getInsertionMarkerBottom());
|
||||
assertEquals(TRANSFORM_MATRIX, info3.getMatrix());
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
assertEquals(rect, info3.getCharacterRect(i));
|
||||
}
|
||||
assertEquals(info.hashCode(), info3.hashCode());
|
||||
|
||||
builder.reset();
|
||||
final CursorAnchorInfo uninitializedInfo = builder.build();
|
||||
assertEquals(-1, uninitializedInfo.getSelectionStart());
|
||||
assertEquals(-1, uninitializedInfo.getSelectionEnd());
|
||||
assertEquals(-1, uninitializedInfo.getCandidatesStart());
|
||||
assertEquals(-1, uninitializedInfo.getCandidatesEnd());
|
||||
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerHorizontal());
|
||||
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop());
|
||||
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline());
|
||||
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBottom());
|
||||
assertEquals(Matrix.IDENTITY_MATRIX, uninitializedInfo.getMatrix());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testBuilderAdd() throws Exception {
|
||||
// A negative index should be rejected.
|
||||
try {
|
||||
new CursorAnchorInfoBuilder().addCharacterRect(-1, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static CursorAnchorInfo cloneViaParcel(final CursorAnchorInfo src) {
|
||||
Parcel parcel = null;
|
||||
try {
|
||||
parcel = Parcel.obtain();
|
||||
src.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
return new CursorAnchorInfo(parcel);
|
||||
} finally {
|
||||
if (parcel != null) {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.os;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.view.inputmethod.SparseRectFArray;
|
||||
import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SparseRectFArrayTest extends InstrumentationTestCase {
|
||||
// A test data for {@link SparseRectFArray}. null represents the gap of indices.
|
||||
private static final RectF[] MANY_RECTS = new RectF[] {
|
||||
null,
|
||||
new RectF(102.0f, 202.0f, 302.0f, 402.0f),
|
||||
new RectF(103.0f, 203.0f, 303.0f, 403.0f),
|
||||
new RectF(104.0f, 204.0f, 304.0f, 404.0f),
|
||||
new RectF(105.0f, 205.0f, 305.0f, 405.0f),
|
||||
new RectF(106.0f, 206.0f, 306.0f, 406.0f),
|
||||
null,
|
||||
new RectF(108.0f, 208.0f, 308.0f, 408.0f),
|
||||
new RectF(109.0f, 209.0f, 309.0f, 409.0f),
|
||||
new RectF(110.0f, 210.0f, 310.0f, 410.0f),
|
||||
new RectF(111.0f, 211.0f, 311.0f, 411.0f),
|
||||
new RectF(112.0f, 212.0f, 312.0f, 412.0f),
|
||||
new RectF(113.0f, 213.0f, 313.0f, 413.0f),
|
||||
new RectF(114.0f, 214.0f, 314.0f, 414.0f),
|
||||
new RectF(115.0f, 215.0f, 315.0f, 415.0f),
|
||||
new RectF(116.0f, 216.0f, 316.0f, 416.0f),
|
||||
new RectF(117.0f, 217.0f, 317.0f, 417.0f),
|
||||
null,
|
||||
null,
|
||||
new RectF(118.0f, 218.0f, 318.0f, 418.0f),
|
||||
};
|
||||
|
||||
@SmallTest
|
||||
public void testBuilder() throws Exception {
|
||||
final RectF TEMP_RECT = new RectF(10.0f, 20.0f, 30.0f, 40.0f);
|
||||
|
||||
final SparseRectFArrayBuilder builder = new SparseRectFArrayBuilder();
|
||||
builder.append(100, TEMP_RECT.left, TEMP_RECT.top, TEMP_RECT.right, TEMP_RECT.bottom);
|
||||
assertNull(builder.build().get(-1));
|
||||
assertNull(builder.build().get(0));
|
||||
assertNull(builder.build().get(99));
|
||||
assertEquals(TEMP_RECT, builder.build().get(100));
|
||||
assertNull(builder.build().get(101));
|
||||
|
||||
// Test if {@link SparseRectFArrayBuilder#reset} resets its internal state.
|
||||
builder.reset();
|
||||
assertNull(builder.build().get(100));
|
||||
|
||||
builder.reset();
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
if (rect != null) {
|
||||
builder.append(i, rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
}
|
||||
final SparseRectFArray array = builder.build();
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
assertEquals(rect, array.get(i));
|
||||
}
|
||||
|
||||
// Make sure the builder reproduces an equivalent object.
|
||||
final SparseRectFArray array2 = builder.build();
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
assertEquals(rect, array2.get(i));
|
||||
}
|
||||
assertEqualRects(array, array2);
|
||||
|
||||
// Make sure the instance can be marshaled via {@link Parcel}.
|
||||
final SparseRectFArray array3 = cloneViaParcel(array);
|
||||
for (int i = 0; i < MANY_RECTS.length; i++) {
|
||||
final RectF rect = MANY_RECTS[i];
|
||||
assertEquals(rect, array3.get(i));
|
||||
}
|
||||
assertEqualRects(array, array3);
|
||||
|
||||
// Make sure the builder can be reset.
|
||||
builder.reset();
|
||||
assertNull(builder.build().get(0));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testEquality() throws Exception {
|
||||
// Empty array should be equal.
|
||||
assertEqualRects(new SparseRectFArrayBuilder().build(),
|
||||
new SparseRectFArrayBuilder().build());
|
||||
|
||||
assertEqualRects(
|
||||
new SparseRectFArrayBuilder().append(100, 1.0f, 2.0f, 3.0f, 4.0f).build(),
|
||||
new SparseRectFArrayBuilder().append(100, 1.0f, 2.0f, 3.0f, 4.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder().append(100, 1.0f, 2.0f, 3.0f, 4.0f).build(),
|
||||
new SparseRectFArrayBuilder().append(100, 2.0f, 2.0f, 3.0f, 4.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder().append(100, 1.0f, 2.0f, 3.0f, 4.0f).build(),
|
||||
new SparseRectFArrayBuilder().append(101, 1.0f, 2.0f, 3.0f, 4.0f).build());
|
||||
|
||||
assertEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f).build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 1.0f, 0.0f, 0.0f, 0.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 1.0f, 0.0f, 0.0f, 0.0f).build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(101, 0.0f, 0.0f, 0.0f, 0.0f).build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(100, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(102, 0.0f, 0.0f, 0.0f, 0.0f).build());
|
||||
|
||||
assertEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(1, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(1000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.append(100000000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(1, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(1000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.append(100000000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.build());
|
||||
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(1, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(1000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.append(100000000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(1, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.build());
|
||||
assertNotEqualRects(
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(1, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(1000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.append(100000000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.build(),
|
||||
new SparseRectFArrayBuilder()
|
||||
.append(1, 1.0f, 2.0f, 3.0f, 4.0f)
|
||||
.append(1000, 1.0f, 0.0f, 0.0f, 0.0f)
|
||||
.append(100000000, 0.0f, 0.0f, 0.0f, 0.0f)
|
||||
.build());
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testBuilderAppend() throws Exception {
|
||||
// Key should be appended in ascending order.
|
||||
try {
|
||||
new SparseRectFArrayBuilder().append(10, 0, 0, 0, 0).append(0, 1, 2, 3, 4);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
try {
|
||||
new SparseRectFArrayBuilder().append(10, 0, 0, 0, 0).append(10, 1, 2, 3, 4);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertEqualRects(SparseRectFArray a, SparseRectFArray b) {
|
||||
assertEquals(a, b);
|
||||
if (a != null && b != null) {
|
||||
assertEquals(a.hashCode(), b.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNotEqualRects(SparseRectFArray a, SparseRectFArray b) {
|
||||
assertFalse(Objects.equals(a, b));
|
||||
}
|
||||
|
||||
private static SparseRectFArray cloneViaParcel(final SparseRectFArray src) {
|
||||
Parcel parcel = null;
|
||||
try {
|
||||
parcel = Parcel.obtain();
|
||||
src.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
return new SparseRectFArray(parcel);
|
||||
} finally {
|
||||
if (parcel != null) {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user