diff options
Diffstat (limited to 'core/java/android/text')
18 files changed, 398 insertions, 50 deletions
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 843754b..944f735 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -19,6 +19,7 @@ package android.text; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; +import android.text.style.ParagraphStyle; import android.util.FloatMath; /** @@ -262,6 +263,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextUtils.recycle(temp); + if (boring && text instanceof Spanned) { + Spanned sp = (Spanned) text; + Object[] styles = sp.getSpans(0, text.length(), ParagraphStyle.class); + if (styles.length > 0) { + boring = false; + } + } + if (boring) { Metrics fm = metrics; if (fm == null) { diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java index 41d5b84..8592221 100644 --- a/core/java/android/text/InputType.java +++ b/core/java/android/text/InputType.java @@ -146,6 +146,15 @@ public interface InputType { */ public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000; + /** + * Flag for {@link #TYPE_CLASS_TEXT}: the input method does not need to + * display any dictionary-based candidates. This is useful for text views that + * do not contain words from the language and do not benefit from any + * dictionary-based completions or corrections. It overrides the + * {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} value when set. + */ + public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000; + // ---------------------------------------------------------------------- /** diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index c133cf2..f0a5ffd 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -968,7 +968,13 @@ extends Layout fm.bottom = bottom; for (int i = 0; i < chooseht.length; i++) { - chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm); + if (chooseht[i] instanceof LineHeightSpan.WithDensity) { + ((LineHeightSpan.WithDensity) chooseht[i]). + chooseHeight(text, start, end, choosehtv[i], v, fm, paint); + + } else { + chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm); + } } above = fm.ascent; diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java index f13820d..f9e7cac 100644 --- a/core/java/android/text/TextPaint.java +++ b/core/java/android/text/TextPaint.java @@ -27,6 +27,7 @@ public class TextPaint extends Paint { public int baselineShift; public int linkColor; public int[] drawableState; + public float density = 1.0f; public TextPaint() { super(); @@ -51,5 +52,6 @@ public class TextPaint extends Paint { baselineShift = tp.baselineShift; linkColor = tp.linkColor; drawableState = tp.drawableState; + density = tp.density; } } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 1a4eb69..9dd8ceb 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -25,7 +25,9 @@ import android.pim.DateException; import java.util.Calendar; import java.util.Date; +import java.util.Formatter; import java.util.GregorianCalendar; +import java.util.Locale; import java.util.TimeZone; /** @@ -1040,6 +1042,31 @@ public class DateUtils /** * Formats a date or a time range according to the local conventions. + * <p> + * Note that this is a convenience method. Using it involves creating an + * internal {@link java.util.Formatter} instance on-the-fly, which is + * somewhat costly in terms of memory and time. This is probably acceptable + * if you use the method only rarely, but if you rely on it for formatting a + * large number of dates, consider creating and reusing your own + * {@link java.util.Formatter} instance and use the version of + * {@link #formatDateRange(Context, long, long, int) formatDateRange} + * that takes a {@link java.util.Formatter}. + * + * @param context the context is required only if the time is shown + * @param startMillis the start time in UTC milliseconds + * @param endMillis the end time in UTC milliseconds + * @param flags a bit mask of options See + * {@link #formatDateRange(Context, long, long, int) formatDateRange} + * @return a string containing the formatted date/time range. + */ + public static String formatDateRange(Context context, long startMillis, + long endMillis, int flags) { + Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault()); + return formatDateRange(context, f, startMillis, endMillis, flags).toString(); + } + + /** + * Formats a date or a time range according to the local conventions. * * <p> * Example output strings (date formats in these examples are shown using @@ -1181,14 +1208,17 @@ public class DateUtils * instead of "December 31, 2008". * * @param context the context is required only if the time is shown + * @param formatter the Formatter used for formatting the date range. + * Note: be sure to call setLength(0) on StringBuilder passed to + * the Formatter constructor unless you want the results to accumulate. * @param startMillis the start time in UTC milliseconds * @param endMillis the end time in UTC milliseconds * @param flags a bit mask of options * - * @return a string containing the formatted date/time range. + * @return the formatter with the formatted date/time range appended to the string buffer. */ - public static String formatDateRange(Context context, long startMillis, - long endMillis, int flags) { + public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, + long endMillis, int flags) { Resources res = Resources.getSystem(); boolean showTime = (flags & FORMAT_SHOW_TIME) != 0; boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0; @@ -1423,8 +1453,7 @@ public class DateUtils if (noMonthDay && startMonthNum == endMonthNum) { // Example: "January, 2008" - String startDateString = startDate.format(defaultDateFormat); - return startDateString; + return formatter.format("%s", startDate.format(defaultDateFormat)); } if (startYear != endYear || noMonthDay) { @@ -1436,10 +1465,9 @@ public class DateUtils // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, + return formatter.format(fullFormat, startWeekDayString, startDateString, startTimeString, endWeekDayString, endDateString, endTimeString); - return dateRange; } // Get the month, day, and year strings for the start and end dates @@ -1476,12 +1504,11 @@ public class DateUtils // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, + return formatter.format(fullFormat, startWeekDayString, startMonthString, startMonthDayString, startYearString, startTimeString, endWeekDayString, endMonthString, endMonthDayString, endYearString, endTimeString); - return dateRange; } if (startDay != endDay) { @@ -1496,12 +1523,11 @@ public class DateUtils // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, + return formatter.format(fullFormat, startWeekDayString, startMonthString, startMonthDayString, startYearString, startTimeString, endWeekDayString, endMonthString, endMonthDayString, endYearString, endTimeString); - return dateRange; } // Same start and end day @@ -1522,6 +1548,7 @@ public class DateUtils } else { // Example: "10:00 - 11:00 am" String timeFormat = res.getString(com.android.internal.R.string.time1_time2); + // Don't use the user supplied Formatter because the result will pollute the buffer. timeString = String.format(timeFormat, startTimeString, endTimeString); } } @@ -1545,7 +1572,7 @@ public class DateUtils fullFormat = res.getString(com.android.internal.R.string.time_date); } else { // Example: "Oct 9" - return dateString; + return formatter.format("%s", dateString); } } } else if (showWeekDay) { @@ -1554,16 +1581,15 @@ public class DateUtils fullFormat = res.getString(com.android.internal.R.string.time_wday); } else { // Example: "Tue" - return startWeekDayString; + return formatter.format("%s", startWeekDayString); } } else if (showTime) { - return timeString; + return formatter.format("%s", timeString); } // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, timeString, startWeekDayString, dateString); - return dateRange; + return formatter.format(fullFormat, timeString, startWeekDayString, dateString); } /** diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java index 367b26c..baaa3ce 100644 --- a/core/java/android/text/format/Formatter.java +++ b/core/java/android/text/format/Formatter.java @@ -32,6 +32,18 @@ public final class Formatter { * @return formated string with the number */ public static String formatFileSize(Context context, long number) { + return formatFileSize(context, number, false); + } + + /** + * Like {@link #formatFileSize}, but trying to generate shorter numbers + * (showing fewer digits of precisin). + */ + public static String formatShortFileSize(Context context, long number) { + return formatFileSize(context, number, true); + } + + private static String formatFileSize(Context context, long number, boolean shorter) { if (context == null) { return ""; } @@ -58,13 +70,24 @@ public final class Formatter { suffix = com.android.internal.R.string.petabyteShort; result = result / 1024; } - if (result < 100) { - String value = String.format("%.2f", result); - return context.getResources(). - getString(com.android.internal.R.string.fileSizeSuffix, - value, context.getString(suffix)); + String value; + if (result < 1) { + value = String.format("%.2f", result); + } else if (result < 10) { + if (shorter) { + value = String.format("%.1f", result); + } else { + value = String.format("%.2f", result); + } + } else if (result < 100) { + if (shorter) { + value = String.format("%.0f", result); + } else { + value = String.format("%.2f", result); + } + } else { + value = String.format("%.0f", result); } - String value = String.format("%.0f", result); return context.getResources(). getString(com.android.internal.R.string.fileSizeSuffix, value, context.getString(suffix)); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 92f6289..ab33cb3 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -22,6 +22,7 @@ import android.graphics.Rect; import android.text.*; import android.widget.TextView; import android.view.View; +import android.view.ViewConfiguration; import android.view.MotionEvent; // XXX this doesn't extend MetaKeyKeyListener because the signatures @@ -256,8 +257,32 @@ implements MovementMethod (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0); + DoubleTapState[] tap = buffer.getSpans(0, buffer.length(), + DoubleTapState.class); + boolean doubletap = false; + + if (tap.length > 0) { + if (event.getEventTime() - tap[0].mWhen <= + ViewConfiguration.getDoubleTapTimeout()) { + if (sameWord(buffer, off, Selection.getSelectionEnd(buffer))) { + doubletap = true; + } + } + + tap[0].mWhen = event.getEventTime(); + } else { + DoubleTapState newtap = new DoubleTapState(); + newtap.mWhen = event.getEventTime(); + buffer.setSpan(newtap, 0, buffer.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + if (cap) { Selection.extendSelection(buffer, off); + } else if (doubletap) { + Selection.setSelection(buffer, + findWordStart(buffer, off), + findWordEnd(buffer, off)); } else { Selection.setSelection(buffer, off); } @@ -272,6 +297,62 @@ implements MovementMethod return handled; } + private static class DoubleTapState implements NoCopySpan { + long mWhen; + } + + private static boolean sameWord(CharSequence text, int one, int two) { + int start = findWordStart(text, one); + int end = findWordEnd(text, one); + + if (end == start) { + return false; + } + + return start == findWordStart(text, two) && + end == findWordEnd(text, two); + } + + // TODO: Unify with TextView.getWordForDictionary() + private static int findWordStart(CharSequence text, int start) { + for (; start > 0; start--) { + char c = text.charAt(start - 1); + int type = Character.getType(c); + + if (c != '\'' && + type != Character.UPPERCASE_LETTER && + type != Character.LOWERCASE_LETTER && + type != Character.TITLECASE_LETTER && + type != Character.MODIFIER_LETTER && + type != Character.DECIMAL_DIGIT_NUMBER) { + break; + } + } + + return start; + } + + // TODO: Unify with TextView.getWordForDictionary() + private static int findWordEnd(CharSequence text, int end) { + int len = text.length(); + + for (; end < len; end++) { + char c = text.charAt(end); + int type = Character.getType(c); + + if (c != '\'' && + type != Character.UPPERCASE_LETTER && + type != Character.LOWERCASE_LETTER && + type != Character.TITLECASE_LETTER && + type != Character.MODIFIER_LETTER && + type != Character.DECIMAL_DIGIT_NUMBER) { + break; + } + } + + return end; + } + public boolean canSelectArbitrarily() { return true; } diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java index 3c406751..880e46d 100644 --- a/core/java/android/text/method/CharacterPickerDialog.java +++ b/core/java/android/text/method/CharacterPickerDialog.java @@ -25,15 +25,14 @@ import android.text.*; import android.view.LayoutInflater; import android.view.View.OnClickListener; import android.view.View; -import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.GridView; -import android.widget.TextView; /** * Dialog for choosing accented characters related to a base character. @@ -45,6 +44,7 @@ public class CharacterPickerDialog extends Dialog private String mOptions; private boolean mInsert; private LayoutInflater mInflater; + private Button mCancelButton; /** * Creates a new CharacterPickerDialog that presents the specified @@ -54,7 +54,7 @@ public class CharacterPickerDialog extends Dialog public CharacterPickerDialog(Context context, View view, Editable text, String options, boolean insert) { - super(context); + super(context, com.android.internal.R.style.Theme_Panel); mView = view; mText = text; @@ -70,28 +70,32 @@ public class CharacterPickerDialog extends Dialog WindowManager.LayoutParams params = getWindow().getAttributes(); params.token = mView.getApplicationWindowToken(); params.type = params.TYPE_APPLICATION_ATTACHED_DIALOG; + params.flags = params.flags | Window.FEATURE_NO_TITLE; - setTitle(R.string.select_character); setContentView(R.layout.character_picker); GridView grid = (GridView) findViewById(R.id.characterPicker); grid.setAdapter(new OptionsAdapter(getContext())); grid.setOnItemClickListener(this); - findViewById(R.id.cancel).setOnClickListener(this); + mCancelButton = (Button) findViewById(R.id.cancel); + mCancelButton.setOnClickListener(this); } /** * Handles clicks on the character buttons. */ public void onItemClick(AdapterView parent, View view, int position, long id) { - int selEnd = Selection.getSelectionEnd(mText); String result = String.valueOf(mOptions.charAt(position)); + replaceCharacterAndClose(result); + } + private void replaceCharacterAndClose(CharSequence replace) { + int selEnd = Selection.getSelectionEnd(mText); if (mInsert || selEnd == 0) { - mText.insert(selEnd, result); + mText.insert(selEnd, replace); } else { - mText.replace(selEnd - 1, selEnd, result); + mText.replace(selEnd - 1, selEnd, replace); } dismiss(); @@ -101,21 +105,25 @@ public class CharacterPickerDialog extends Dialog * Handles clicks on the Cancel button. */ public void onClick(View v) { - dismiss(); + if (v == mCancelButton) { + dismiss(); + } else if (v instanceof Button) { + CharSequence result = ((Button) v).getText(); + replaceCharacterAndClose(result); + } } private class OptionsAdapter extends BaseAdapter { - private Context mContext; public OptionsAdapter(Context context) { super(); - mContext = context; } public View getView(int position, View convertView, ViewGroup parent) { Button b = (Button) mInflater.inflate(R.layout.character_picker_button, null); b.setText(String.valueOf(mOptions.charAt(position))); + b.setOnClickListener(CharacterPickerDialog.this); return b; } diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java index e420c27..2e76470 100644 --- a/core/java/android/text/method/QwertyKeyListener.java +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -401,20 +401,17 @@ public class QwertyKeyListener extends BaseKeyListener { private static SparseArray<String> PICKER_SETS = new SparseArray<String>(); static { - PICKER_SETS.put('!', "\u00A1"); - PICKER_SETS.put('<', "\u00AB"); - PICKER_SETS.put('>', "\u00BB"); - PICKER_SETS.put('?', "\u00BF"); PICKER_SETS.put('A', "\u00C0\u00C1\u00C2\u00C4\u00C6\u00C3\u00C5\u0104\u0100"); PICKER_SETS.put('C', "\u00C7\u0106\u010C"); PICKER_SETS.put('D', "\u010E"); PICKER_SETS.put('E', "\u00C8\u00C9\u00CA\u00CB\u0118\u011A\u0112"); + PICKER_SETS.put('G', "\u011E"); PICKER_SETS.put('L', "\u0141"); - PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF\u012A"); + PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF\u012A\u0130"); PICKER_SETS.put('N', "\u00D1\u0143\u0147"); PICKER_SETS.put('O', "\u00D8\u0152\u00D5\u00D2\u00D3\u00D4\u00D6\u014C"); PICKER_SETS.put('R', "\u0158"); - PICKER_SETS.put('S', "\u015A\u0160"); + PICKER_SETS.put('S', "\u015A\u0160\u015E"); PICKER_SETS.put('T', "\u0164"); PICKER_SETS.put('U', "\u00D9\u00DA\u00DB\u00DC\u016E\u016A"); PICKER_SETS.put('Y', "\u00DD\u0178"); @@ -423,18 +420,47 @@ public class QwertyKeyListener extends BaseKeyListener { PICKER_SETS.put('c', "\u00E7\u0107\u010D"); PICKER_SETS.put('d', "\u010F"); PICKER_SETS.put('e', "\u00E8\u00E9\u00EA\u00EB\u0119\u011B\u0113"); - PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF\u012B"); + PICKER_SETS.put('g', "\u011F"); + PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF\u012B\u0131"); PICKER_SETS.put('l', "\u0142"); PICKER_SETS.put('n', "\u00F1\u0144\u0148"); PICKER_SETS.put('o', "\u00F8\u0153\u00F5\u00F2\u00F3\u00F4\u00F6\u014D"); PICKER_SETS.put('r', "\u0159"); - PICKER_SETS.put('s', "\u00A7\u00DF\u015B\u0161"); + PICKER_SETS.put('s', "\u00A7\u00DF\u015B\u0161\u015F"); PICKER_SETS.put('t', "\u0165"); PICKER_SETS.put('u', "\u00F9\u00FA\u00FB\u00FC\u016F\u016B"); PICKER_SETS.put('y', "\u00FD\u00FF"); PICKER_SETS.put('z', "\u017A\u017C\u017E"); PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT, - "\u2026\u00A5\u2022\u00AE\u00A9\u00B1"); + "\u2026\u00A5\u2022\u00AE\u00A9\u00B1[]{}\\"); + PICKER_SETS.put('/', "\\"); + + // From packages/inputmethods/LatinIME/res/xml/kbd_symbols.xml + + PICKER_SETS.put('1', "\u00b9\u00bd\u2153\u00bc\u215b"); + PICKER_SETS.put('2', "\u00b2\u2154"); + PICKER_SETS.put('3', "\u00b3\u00be\u215c"); + PICKER_SETS.put('4', "\u2074"); + PICKER_SETS.put('5', "\u215d"); + PICKER_SETS.put('7', "\u215e"); + PICKER_SETS.put('0', "\u207f\u2205"); + PICKER_SETS.put('$', "\u00a2\u00a3\u20ac\u00a5\u20a3\u20a4\u20b1"); + PICKER_SETS.put('%', "\u2030"); + PICKER_SETS.put('*', "\u2020\u2021"); + PICKER_SETS.put('-', "\u2013\u2014"); + PICKER_SETS.put('+', "\u00b1"); + PICKER_SETS.put('(', "[{<"); + PICKER_SETS.put(')', "]}>"); + PICKER_SETS.put('!', "\u00a1"); + PICKER_SETS.put('"', "\u201c\u201d\u00ab\u00bb\u02dd"); + PICKER_SETS.put('?', "\u00bf"); + PICKER_SETS.put(',', "\u201a\u201e"); + + // From packages/inputmethods/LatinIME/res/xml/kbd_symbols_shift.xml + + PICKER_SETS.put('=', "\u2260\u2248\u221e"); + PICKER_SETS.put('<', "\u2264\u00ab\u2039"); + PICKER_SETS.put('>', "\u2265\u00bb\u203a"); }; private boolean showCharacterPicker(View view, Editable content, char c, diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index dfc16f5..6995107 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -72,6 +72,24 @@ public class Touch { } /** + * @hide + * Returns the maximum scroll value in x. + */ + public static int getMaxScrollX(TextView widget, Layout layout, int y) { + int top = layout.getLineForVertical(y); + int bottom = layout.getLineForVertical(y + widget.getHeight() + - widget.getTotalPaddingTop() -widget.getTotalPaddingBottom()); + int left = Integer.MAX_VALUE; + int right = 0; + for (int i = top; i <= bottom; i++) { + left = (int) Math.min(left, layout.getLineLeft(i)); + right = (int) Math.max(right, layout.getLineRight(i)); + } + return right - left - widget.getWidth() - widget.getTotalPaddingLeft() + - widget.getTotalPaddingRight(); + } + + /** * Handles touch events for dragging. You may want to do other actions * like moving the cursor on touch as well. */ diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java index 484f8ce..1214040 100644 --- a/core/java/android/text/style/AbsoluteSizeSpan.java +++ b/core/java/android/text/style/AbsoluteSizeSpan.java @@ -24,13 +24,28 @@ import android.text.TextUtils; public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mSize; + private boolean mDip; + /** + * Set the text size to <code>size</code> physical pixels. + */ public AbsoluteSizeSpan(int size) { mSize = size; } + /** + * Set the text size to <code>size</code> physical pixels, + * or to <code>size</code> device-independent pixels if + * <code>dip</code> is true. + */ + public AbsoluteSizeSpan(int size, boolean dip) { + mSize = size; + mDip = dip; + } + public AbsoluteSizeSpan(Parcel src) { mSize = src.readInt(); + mDip = src.readInt() != 0; } public int getSpanTypeId() { @@ -43,19 +58,32 @@ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableS public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mSize); + dest.writeInt(mDip ? 1 : 0); } public int getSize() { return mSize; } + public boolean getDip() { + return mDip; + } + @Override public void updateDrawState(TextPaint ds) { - ds.setTextSize(mSize); + if (mDip) { + ds.setTextSize(mSize * ds.density); + } else { + ds.setTextSize(mSize); + } } @Override public void updateMeasureState(TextPaint ds) { - ds.setTextSize(mSize); + if (mDip) { + ds.setTextSize(mSize * ds.density); + } else { + ds.setTextSize(mSize); + } } } diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index 86ef5f6..74b9463 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -36,6 +36,7 @@ public class ImageSpan extends DynamicDrawableSpan { /** * @deprecated Use {@link #ImageSpan(Context, Bitmap)} instead. */ + @Deprecated public ImageSpan(Bitmap b) { this(null, b, ALIGN_BOTTOM); } @@ -43,6 +44,7 @@ public class ImageSpan extends DynamicDrawableSpan { /** * @deprecated Use {@link #ImageSpan(Context, Bitmap, int) instead. */ + @Deprecated public ImageSpan(Bitmap b, int verticalAlignment) { this(null, b, verticalAlignment); } diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java index c0ef97c..44a1706 100644 --- a/core/java/android/text/style/LineHeightSpan.java +++ b/core/java/android/text/style/LineHeightSpan.java @@ -19,6 +19,7 @@ package android.text.style; import android.graphics.Paint; import android.graphics.Canvas; import android.text.Layout; +import android.text.TextPaint; public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan @@ -26,4 +27,10 @@ extends ParagraphStyle, WrapTogetherSpan public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm); + + public interface WithDensity extends LineHeightSpan { + public void chooseHeight(CharSequence text, int start, int end, + int spanstartv, int v, + Paint.FontMetricsInt fm, TextPaint paint); + } } diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index d61e888..ce25c47 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -208,7 +208,7 @@ public class Linkify { if ((mask & WEB_URLS) != 0) { gatherLinks(links, text, Regex.WEB_URL_PATTERN, - new String[] { "http://", "https://" }, + new String[] { "http://", "https://", "rtsp://" }, sUrlMatchFilter, null); } diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java index a349b82..a6844a4 100644 --- a/core/java/android/text/util/Regex.java +++ b/core/java/android/text/util/Regex.java @@ -65,7 +65,7 @@ public class Regex { */ public static final Pattern WEB_URL_PATTERN = Pattern.compile( - "((?:(http|https|Http|Https):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+" // named host diff --git a/core/java/android/text/util/Rfc822InputFilter.java b/core/java/android/text/util/Rfc822InputFilter.java new file mode 100644 index 0000000..8c8b7fc --- /dev/null +++ b/core/java/android/text/util/Rfc822InputFilter.java @@ -0,0 +1,58 @@ +package android.text.util; + +import android.text.InputFilter; +import android.text.Spanned; +import android.text.SpannableStringBuilder; + +/** + * Implements special address cleanup rules: + * The first space key entry following an "@" symbol that is followed by any combination + * of letters and symbols, including one+ dots and zero commas, should insert an extra + * comma (followed by the space). + * + * @hide + */ +public class Rfc822InputFilter implements InputFilter { + + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, + int dstart, int dend) { + + // quick check - did they enter a single space? + if (end-start != 1 || source.charAt(start) != ' ') { + return null; + } + + // determine if the characters before the new space fit the pattern + // follow backwards and see if we find a comma, dot, or @ + int scanBack = dstart; + boolean dotFound = false; + while (scanBack > 0) { + char c = dest.charAt(--scanBack); + switch (c) { + case '.': + dotFound = true; // one or more dots are req'd + break; + case ',': + return null; + case '@': + if (!dotFound) { + return null; + } + // we have found a comma-insert case. now just do it + // in the least expensive way we can. + if (source instanceof Spanned) { + SpannableStringBuilder sb = new SpannableStringBuilder(","); + sb.append(source); + return sb; + } else { + return ", "; + } + default: + // just keep going + } + } + + // no termination cases were found, so don't edit the input + return null; + } +} diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java index 7fe11bc..0edeeb5 100644 --- a/core/java/android/text/util/Rfc822Token.java +++ b/core/java/android/text/util/Rfc822Token.java @@ -168,5 +168,31 @@ public class Rfc822Token { return sb.toString(); } + + public int hashCode() { + int result = 17; + if (mName != null) result = 31 * result + mName.hashCode(); + if (mAddress != null) result = 31 * result + mAddress.hashCode(); + if (mComment != null) result = 31 * result + mComment.hashCode(); + return result; + } + + private static boolean stringEquals(String a, String b) { + if (a == null) { + return (b == null); + } else { + return (a.equals(b)); + } + } + + public boolean equals(Object o) { + if (!(o instanceof Rfc822Token)) { + return false; + } + Rfc822Token other = (Rfc822Token) o; + return (stringEquals(mName, other.mName) && + stringEquals(mAddress, other.mAddress) && + stringEquals(mComment, other.mComment)); + } } diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java index d4e78b0..cb39f7d 100644 --- a/core/java/android/text/util/Rfc822Tokenizer.java +++ b/core/java/android/text/util/Rfc822Tokenizer.java @@ -19,6 +19,7 @@ package android.text.util; import android.widget.MultiAutoCompleteTextView; import java.util.ArrayList; +import java.util.Collection; /** * This class works as a Tokenizer for MultiAutoCompleteTextView for @@ -27,18 +28,22 @@ import java.util.ArrayList; * into a series of Rfc822Tokens. */ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { + /** * This constructor will try to take a string like * "Foo Bar (something) <foo\@google.com>, * blah\@google.com (something)" - * and convert it into one or more Rfc822Tokens. + * and convert it into one or more Rfc822Tokens, output into the supplied + * collection. + * * It does *not* decode MIME encoded-words; charset conversion * must already have taken place if necessary. * It will try to be tolerant of broken syntax instead of * returning an error. + * + * @hide */ - public static Rfc822Token[] tokenize(CharSequence text) { - ArrayList<Rfc822Token> out = new ArrayList<Rfc822Token>(); + public static void tokenize(CharSequence text, Collection<Rfc822Token> out) { StringBuilder name = new StringBuilder(); StringBuilder address = new StringBuilder(); StringBuilder comment = new StringBuilder(); @@ -148,7 +153,21 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { name.toString(), comment.toString())); } + } + /** + * This method will try to take a string like + * "Foo Bar (something) <foo\@google.com>, + * blah\@google.com (something)" + * and convert it into one or more Rfc822Tokens. + * It does *not* decode MIME encoded-words; charset conversion + * must already have taken place if necessary. + * It will try to be tolerant of broken syntax instead of + * returning an error. + */ + public static Rfc822Token[] tokenize(CharSequence text) { + ArrayList<Rfc822Token> out = new ArrayList<Rfc822Token>(); + tokenize(text, out); return out.toArray(new Rfc822Token[out.size()]); } |