diff --git a/api/current.txt b/api/current.txt index fa77000b1fbe2..2e2cae692c9f6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5822,6 +5822,8 @@ package android.app.assist { method public java.lang.CharSequence getText(); method public int getTextBackgroundColor(); method public int getTextColor(); + method public int[] getTextLineBaselines(); + method public int[] getTextLineCharOffsets(); method public int getTextSelectionEnd(); method public int getTextSelectionStart(); method public float getTextSize(); @@ -36953,6 +36955,7 @@ package android.view { method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); + method public abstract void setTextLines(int[], int[]); method public abstract void setTextStyle(float, int, int, int); method public abstract void setTransformation(android.graphics.Matrix); method public abstract void setVisibility(int); diff --git a/api/system-current.txt b/api/system-current.txt index dbe4b8f0f8074..e84ad3d21eb89 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5954,6 +5954,8 @@ package android.app.assist { method public java.lang.CharSequence getText(); method public int getTextBackgroundColor(); method public int getTextColor(); + method public int[] getTextLineBaselines(); + method public int[] getTextLineCharOffsets(); method public int getTextSelectionEnd(); method public int getTextSelectionStart(); method public float getTextSize(); @@ -39243,6 +39245,7 @@ package android.view { method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); + method public abstract void setTextLines(int[], int[]); method public abstract void setTextStyle(float, int, int, int); method public abstract void setTransformation(android.graphics.Matrix); method public abstract void setVisibility(int); diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 3429b6e2ddf21..73c551f5af400 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -61,37 +61,53 @@ public class AssistStructure implements Parcelable { final static class ViewNodeText { CharSequence mText; - int mTextSelectionStart; - int mTextSelectionEnd; - int mTextColor; - int mTextBackgroundColor; float mTextSize; int mTextStyle; + int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED; + int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED; + int mTextSelectionStart; + int mTextSelectionEnd; + int[] mLineCharOffsets; + int[] mLineBaselines; String mHint; ViewNodeText() { } - ViewNodeText(Parcel in) { - mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - mTextSelectionStart = in.readInt(); - mTextSelectionEnd = in.readInt(); - mTextColor = in.readInt(); - mTextBackgroundColor = in.readInt(); - mTextSize = in.readFloat(); - mTextStyle = in.readInt(); - mHint = in.readString(); + boolean isSimple() { + return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED + && mTextSelectionStart == 0 && mTextSelectionEnd == 0 + && mLineCharOffsets == null && mLineBaselines == null && mHint == null; } - void writeToParcel(Parcel out) { + ViewNodeText(Parcel in, boolean simple) { + mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mTextSize = in.readFloat(); + mTextStyle = in.readInt(); + mTextColor = in.readInt(); + if (!simple) { + mTextBackgroundColor = in.readInt(); + mTextSelectionStart = in.readInt(); + mTextSelectionEnd = in.readInt(); + mLineCharOffsets = in.createIntArray(); + mLineBaselines = in.createIntArray(); + mHint = in.readString(); + } + } + + void writeToParcel(Parcel out, boolean simple) { TextUtils.writeToParcel(mText, out, 0); - out.writeInt(mTextSelectionStart); - out.writeInt(mTextSelectionEnd); - out.writeInt(mTextColor); - out.writeInt(mTextBackgroundColor); out.writeFloat(mTextSize); out.writeInt(mTextStyle); - out.writeString(mHint); + out.writeInt(mTextColor); + if (!simple) { + out.writeInt(mTextBackgroundColor); + out.writeInt(mTextSelectionStart); + out.writeInt(mTextSelectionEnd); + out.writeIntArray(mLineCharOffsets); + out.writeIntArray(mLineBaselines); + out.writeString(mHint); + } } } @@ -252,9 +268,10 @@ public class AssistStructure implements Parcelable { static final int FLAGS_HAS_LARGE_COORDS = 0x04000000; static final int FLAGS_HAS_CONTENT_DESCRIPTION = 0x02000000; static final int FLAGS_HAS_TEXT = 0x01000000; - static final int FLAGS_HAS_EXTRAS = 0x00800000; - static final int FLAGS_HAS_ID = 0x00400000; - static final int FLAGS_HAS_CHILDREN = 0x00200000; + static final int FLAGS_HAS_COMPLEX_TEXT = 0x00800000; + static final int FLAGS_HAS_EXTRAS = 0x00400000; + static final int FLAGS_HAS_ID = 0x00200000; + static final int FLAGS_HAS_CHILDREN = 0x00100000; static final int FLAGS_ALL_CONTROL = 0xfff00000; int mFlags; @@ -316,7 +333,7 @@ public class AssistStructure implements Parcelable { mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } if ((flags&FLAGS_HAS_TEXT) != 0) { - mText = new ViewNodeText(in); + mText = new ViewNodeText(in, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0); } if ((flags&FLAGS_HAS_EXTRAS) != 0) { mExtras = in.readBundle(); @@ -356,6 +373,9 @@ public class AssistStructure implements Parcelable { } if (mText != null) { flags |= FLAGS_HAS_TEXT; + if (!mText.isSimple()) { + flags |= FLAGS_HAS_COMPLEX_TEXT; + } } if (mExtras != null) { flags |= FLAGS_HAS_EXTRAS; @@ -403,7 +423,7 @@ public class AssistStructure implements Parcelable { TextUtils.writeToParcel(mContentDescription, out, 0); } if ((flags&FLAGS_HAS_TEXT) != 0) { - mText.writeToParcel(out); + mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0); } if ((flags&FLAGS_HAS_EXTRAS) != 0) { out.writeBundle(mExtras); @@ -702,6 +722,26 @@ public class AssistStructure implements Parcelable { return mText != null ? mText.mTextStyle : 0; } + /** + * Return per-line offsets into the text returned by {@link #getText()}. Each entry + * in the array is a formatted line of text, and the value it contains is the offset + * into the text string where that line starts. May return null if there is no line + * information. + */ + public int[] getTextLineCharOffsets() { + return mText != null ? mText.mLineCharOffsets : null; + } + + /** + * Return per-line baselines into the text returned by {@link #getText()}. Each entry + * in the array is a formatted line of text, and the value it contains is the baseline + * where that text appears in the view. May return null if there is no line + * information. + */ + public int[] getTextLineBaselines() { + return mText != null ? mText.mLineBaselines : null; + } + /** * Return additional hint text associated with the node; this is typically used with * a node that takes user input, describing to the user what the input means. @@ -900,6 +940,13 @@ public class AssistStructure implements Parcelable { t.mTextStyle = style; } + @Override + public void setTextLines(int[] charOffsets, int[] baselines) { + ViewNodeText t = getNodeText(); + t.mLineCharOffsets = charOffsets; + t.mLineBaselines = baselines; + } + @Override public void setHint(CharSequence hint) { getNodeText().mHint = hint != null ? hint.toString() : null; diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 794622a7b88e1..2e4ba74159f9f 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -177,6 +177,17 @@ public abstract class ViewStructure { */ public abstract void setTextStyle(float size, int fgColor, int bgColor, int style); + /** + * Set line information for test that was previously supplied through + * {@link #setText(CharSequence)}. This provides the line breaking of the text as it + * is shown on screen. This function takes ownership of the provided arrays; you should + * not make further modification to them. + * + * @param charOffsets The offset in to {@link #setText} where a line starts. + * @param baselines The baseline where the line is drawn on screen. + */ + public abstract void setTextLines(int[] charOffsets, int[] baselines); + /** * Set optional hint text associated with this view; this is for example the text that is * shown by an EditText when it is empty to indicate to the user the kind of text to input. diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 43edc4441b35d..42ac599d8979c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -46,7 +46,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.inputmethodservice.ExtractEditText; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcel; @@ -116,6 +115,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewParent; import android.view.ViewStructure; import android.view.ViewConfiguration; import android.view.ViewDebug; @@ -5823,6 +5823,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.getBaseline(); } + return getBaselineOffset() + mLayout.getLineBaseline(0); + } + + int getBaselineOffset() { int voffset = 0; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { voffset = getVerticalOffset(true); @@ -5832,7 +5836,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener voffset -= getOpticalInsets().top; } - return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); + return getExtendedPaddingTop() + voffset; } /** @@ -8767,7 +8771,79 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean isPassword = hasPasswordTransformationMethod() || isPasswordInputType(getInputType()); if (!isPassword) { - structure.setText(getText(), getSelectionStart(), getSelectionEnd()); + if (mLayout == null) { + assumeLayout(); + } + Layout layout = mLayout; + final int lineCount = layout.getLineCount(); + if (lineCount <= 1) { + // Simple case: this is a single line. + structure.setText(getText(), getSelectionStart(), getSelectionEnd()); + } else { + // Complex case: multi-line, could be scrolled or within a scroll container + // so some lines are not visible. + final int[] tmpCords = new int[2]; + getLocationInWindow(tmpCords); + final int topWindowLocation = tmpCords[1]; + View root = this; + ViewParent viewParent = getParent(); + while (viewParent instanceof View) { + root = (View) viewParent; + viewParent = root.getParent(); + } + final int windowHeight = root.getHeight(); + final int topLine; + final int bottomLine; + if (topWindowLocation >= 0) { + // The top of the view is fully within its window; start text at line 0. + topLine = getLineAtCoordinateUnclamped(0); + bottomLine = getLineAtCoordinateUnclamped(windowHeight-1); + } else { + // The top of hte window has scrolled off the top of the window; figure out + // the starting line for this. + topLine = getLineAtCoordinateUnclamped(-topWindowLocation); + bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation); + } + // We want to return some contextual lines above/below the lines that are + // actually visible. + int expandedTopLine = topLine - (bottomLine-topLine)/2; + if (expandedTopLine < 0) { + expandedTopLine = 0; + } + int expandedBottomLine = bottomLine + (bottomLine-topLine)/2; + if (expandedBottomLine >= lineCount) { + expandedBottomLine = lineCount-1; + } + // Convert lines into character offsets. + int expandedTopChar = layout.getLineStart(expandedTopLine); + int expandedBottomChar = layout.getLineEnd(expandedBottomLine); + // Take into account selection -- if there is a selection, we need to expand + // the text we are returning to include that selection. + final int selStart = getSelectionStart(); + final int selEnd = getSelectionEnd(); + if (selStart < selEnd) { + if (selStart < expandedTopChar) { + expandedTopChar = selStart; + } + if (selEnd > expandedBottomChar) { + expandedBottomChar = selEnd; + } + } + // Get the text and trim it to the range we are reporting. + CharSequence text = getText(); + if (expandedTopChar > 0 || expandedBottomChar < text.length()) { + text = text.subSequence(expandedTopChar, expandedBottomChar); + } + structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar); + final int[] lineOffsets = new int[bottomLine-topLine+1]; + final int[] lineBaselines = new int[bottomLine-topLine+1]; + final int baselineOffset = getBaselineOffset(); + for (int i=topLine; i<=bottomLine; i++) { + lineOffsets[i-topLine] = layout.getLineStart(i); + lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset; + } + structure.setTextLines(lineOffsets, lineBaselines); + } // Extract style information that applies to the TextView as a whole. int style = 0; @@ -9200,24 +9276,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * If provided, this ActionMode.Callback will be used to create the ActionMode when text * selection is initiated in this View. * - * The standard implementation populates the menu with a subset of Select All, Cut, Copy, + *
The standard implementation populates the menu with a subset of Select All, Cut, Copy, * Paste, Replace and Share actions, depending on what this View supports. * - * A custom implementation can add new entries in the default menu in its - * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The - * default actions can also be removed from the menu using + *
A custom implementation can add new entries in the default menu in its + * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} + * method. The default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. * - * Returning false from - * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent - * the action mode from being started. + *
Returning false from + * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} + * will prevent the action mode from being started. * - * Action click events should be handled by the custom implementation of - * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}. + *
Action click events should be handled by the custom implementation of + * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, + * android.view.MenuItem)}. * - * Note that text selection mode is not started when a TextView receives focus and the + *
Note that text selection mode is not started when a TextView receives focus and the
* {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
* that case, to allow for quick replacement.
*/
@@ -9448,6 +9525,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return getLayout().getLineForVertical((int) y);
}
+ int getLineAtCoordinateUnclamped(float y) {
+ y -= getTotalPaddingTop();
+ y += getScrollY();
+ return getLayout().getLineForVertical((int) y);
+ }
+
int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
return getLayout().getOffsetForHorizontal(line, x);
diff --git a/tests/VoiceInteraction/res/layout/main.xml b/tests/VoiceInteraction/res/layout/main.xml
index 092d37dc4a205..0f968eb65996a 100644
--- a/tests/VoiceInteraction/res/layout/main.xml
+++ b/tests/VoiceInteraction/res/layout/main.xml
@@ -30,10 +30,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="64dp"
- android:paddingBottom="64dp"
android:text="@string/asyncStructure"
/>
+