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:
Yohei Yukawa
2014-05-06 21:22:49 +09:00
parent 86e8029079
commit c2ddd60236
13 changed files with 1249 additions and 13 deletions

View File

@@ -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);

View File

@@ -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(

View File

@@ -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.
*/

View File

@@ -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;

View 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;

View 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;
}
}

View File

@@ -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.

View File

@@ -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);
}

View 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;
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}