summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorYohei Yukawa <yukawa@google.com>2014-05-09 10:43:44 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-05-09 10:43:44 +0000
commit2ac997917924161ed2eed5f4387246beb58c456e (patch)
tree156efe623ee78b68dfc00c9e3f908d1c4271bee3 /core/java/android
parenta8c5f577d0dfa23fb955faed4966e65dd8323bc6 (diff)
parentc2ddd6023688db5ecf6c586e05f55e262b4a802e (diff)
Merge "Introduce new API for floating window support"
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java12
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java23
-rw-r--r--core/java/android/view/inputmethod/CorrectionInfo.java19
-rw-r--r--core/java/android/view/inputmethod/CursorAnchorInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/CursorAnchorInfo.java449
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java29
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java12
-rw-r--r--core/java/android/view/inputmethod/SparseRectFArray.java265
8 files changed, 816 insertions, 12 deletions
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 8437228257a0..ed223d1fd503 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -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,
@@ -181,6 +187,12 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
}
@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(
mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index f6438b444cb5..4bccaf1d6f52 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -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);
+ }
}
/**
@@ -1717,6 +1729,17 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * 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.
*/
public void setCursorAnchorMonitorMode(int monitorMode) {
diff --git a/core/java/android/view/inputmethod/CorrectionInfo.java b/core/java/android/view/inputmethod/CorrectionInfo.java
index 1b04e4980588..a43dfe89cee1 100644
--- a/core/java/android/view/inputmethod/CorrectionInfo.java
+++ b/core/java/android/view/inputmethod/CorrectionInfo.java
@@ -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;
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.aidl b/core/java/android/view/inputmethod/CursorAnchorInfo.aidl
new file mode 100644
index 000000000000..2ee9edbd1b31
--- /dev/null
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.aidl
@@ -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;
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
new file mode 100644
index 000000000000..92455df900b1
--- /dev/null
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -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;
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 02278733d270..e1c6f522f101 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -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) {
@@ -1556,6 +1558,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.
* @param view Optional View that is sending the command, or null if
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index 63862999519d..74fbbc7e22ad 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -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);
}
diff --git a/core/java/android/view/inputmethod/SparseRectFArray.java b/core/java/android/view/inputmethod/SparseRectFArray.java
new file mode 100644
index 000000000000..40cade74f864
--- /dev/null
+++ b/core/java/android/view/inputmethod/SparseRectFArray.java
@@ -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;
+ }
+}
+