summaryrefslogtreecommitdiffstats
path: root/core/java/android/text
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-01-09 17:51:23 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-01-09 17:51:23 -0800
commitb798689749c64baba81f02e10cf2157c747d6b46 (patch)
treeda394a395ddb1a6cf69193314846b03fe47a397e /core/java/android/text
parentf013e1afd1e68af5e3b868c26a653bbfb39538f8 (diff)
downloadframeworks_base-b798689749c64baba81f02e10cf2157c747d6b46.zip
frameworks_base-b798689749c64baba81f02e10cf2157c747d6b46.tar.gz
frameworks_base-b798689749c64baba81f02e10cf2157c747d6b46.tar.bz2
auto import from //branches/cupcake/...@125939
Diffstat (limited to 'core/java/android/text')
-rw-r--r--core/java/android/text/InputType.java32
-rw-r--r--core/java/android/text/Layout.java4
-rw-r--r--core/java/android/text/format/DateUtils.java201
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java36
-rw-r--r--core/java/android/text/method/MetaKeyKeyListener.java239
-rw-r--r--core/java/android/text/style/BackgroundColorSpan.java2
-rw-r--r--core/java/android/text/style/CharacterStyle.java4
-rw-r--r--core/java/android/text/style/ClickableSpan.java2
-rw-r--r--core/java/android/text/style/DynamicDrawableSpan.java50
-rw-r--r--core/java/android/text/style/ForegroundColorSpan.java3
-rw-r--r--core/java/android/text/style/ImageSpan.java46
-rw-r--r--core/java/android/text/style/MaskFilterSpan.java3
-rw-r--r--core/java/android/text/style/RasterizerSpan.java3
-rw-r--r--core/java/android/text/style/StrikethroughSpan.java2
-rw-r--r--core/java/android/text/style/UnderlineSpan.java2
-rw-r--r--core/java/android/text/style/UpdateAppearance.java10
-rw-r--r--core/java/android/text/style/UpdateLayout.java7
17 files changed, 557 insertions, 89 deletions
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index 0ffe4ac..bd86834 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 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.text;
import android.text.TextUtils;
@@ -104,6 +120,11 @@ public interface InputType {
*/
public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000;
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: flags any text being used as a search string
+ */
+ public static final int TYPE_TEXT_FLAG_SEARCH = 0x00040000;
+
// ----------------------------------------------------------------------
/**
@@ -139,8 +160,7 @@ public interface InputType {
public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000050;
/**
- * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing
- * address.
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
*/
public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000060;
@@ -150,14 +170,12 @@ public interface InputType {
public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000070;
/**
- * Variation of {@link #TYPE_CLASS_TEXT}: entering a search string
- * for a web search.
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a simple text search (e.g. web search)
*/
- public static final int TYPE_TEXT_VARIATION_WEB_SEARCH = 0x00000080;
+ public static final int TYPE_TEXT_VARIATION_SEARCH_STRING = 0x00000080;
/**
- * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of
- * a web form.
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
*/
public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 346db49..95acf9d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1074,7 +1074,9 @@ public abstract class Layout {
float h2 = getSecondaryHorizontal(point) - 0.5f;
int caps = TextKeyListener.getMetaState(editingBuffer,
- KeyEvent.META_SHIFT_ON);
+ KeyEvent.META_SHIFT_ON) |
+ TextKeyListener.getMetaState(editingBuffer,
+ TextKeyListener.META_SELECTING);
int fn = TextKeyListener.getMetaState(editingBuffer,
KeyEvent.META_ALT_ON);
int dist = 0;
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 48f65c6..feae6cf 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -146,26 +146,26 @@ public class DateUtils
// The following FORMAT_* symbols are used for specifying the format of
// dates and times in the formatDateRange method.
- public static final int FORMAT_SHOW_TIME = 0x00001;
- public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
- public static final int FORMAT_SHOW_YEAR = 0x00004;
- public static final int FORMAT_NO_YEAR = 0x00008;
- public static final int FORMAT_SHOW_DATE = 0x00010;
- public static final int FORMAT_NO_MONTH_DAY = 0x00020;
- public static final int FORMAT_12HOUR = 0x00040;
- public static final int FORMAT_24HOUR = 0x00080;
- public static final int FORMAT_CAP_AMPM = 0x00100;
- public static final int FORMAT_NO_NOON = 0x00200;
- public static final int FORMAT_CAP_NOON = 0x00400;
- public static final int FORMAT_NO_MIDNIGHT = 0x00800;
- public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
- public static final int FORMAT_UTC = 0x02000;
- public static final int FORMAT_ABBREV_TIME = 0x04000;
+ public static final int FORMAT_SHOW_TIME = 0x00001;
+ public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
+ public static final int FORMAT_SHOW_YEAR = 0x00004;
+ public static final int FORMAT_NO_YEAR = 0x00008;
+ public static final int FORMAT_SHOW_DATE = 0x00010;
+ public static final int FORMAT_NO_MONTH_DAY = 0x00020;
+ public static final int FORMAT_12HOUR = 0x00040;
+ public static final int FORMAT_24HOUR = 0x00080;
+ public static final int FORMAT_CAP_AMPM = 0x00100;
+ public static final int FORMAT_NO_NOON = 0x00200;
+ public static final int FORMAT_CAP_NOON = 0x00400;
+ public static final int FORMAT_NO_MIDNIGHT = 0x00800;
+ public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
+ public static final int FORMAT_UTC = 0x02000;
+ public static final int FORMAT_ABBREV_TIME = 0x04000;
public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
- public static final int FORMAT_ABBREV_MONTH = 0x10000;
- public static final int FORMAT_NUMERIC_DATE = 0x20000;
- public static final int FORMAT_ABBREV_ALL = (FORMAT_ABBREV_TIME
- | FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_MONTH);
+ public static final int FORMAT_ABBREV_MONTH = 0x10000;
+ public static final int FORMAT_NUMERIC_DATE = 0x20000;
+ public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
+ public static final int FORMAT_ABBREV_ALL = 0x80000;
public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT);
public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT);
@@ -233,18 +233,20 @@ public class DateUtils
};
/**
- * Request the full spelled-out name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
- * @more
- * <p>e.g. "Sunday" or "January"
+ * Request the full spelled-out name. For use with the 'abbrev' parameter of
+ * {@link #getDayOfWeekString} and {@link #getMonthString}.
+ *
+ * @more <p>
+ * e.g. "Sunday" or "January"
*/
public static final int LENGTH_LONG = 10;
/**
- * Request an abbreviated version of the name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
- * @more
- * <p>e.g. "Sun" or "Jan"
+ * Request an abbreviated version of the name. For use with the 'abbrev'
+ * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
+ *
+ * @more <p>
+ * e.g. "Sun" or "Jan"
*/
public static final int LENGTH_MEDIUM = 20;
@@ -364,53 +366,162 @@ public class DateUtils
* 0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS
*/
public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) {
- Resources r = Resources.getSystem();
+ int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH;
+ return getRelativeTimeSpanString(time, now, minResolution, flags);
+ }
+ /**
+ * Returns a string describing 'time' as a time relative to 'now'.
+ * <p>
+ * Time spans in the past are formatted like "42 minutes ago". Time spans in
+ * the future are formatted like "in 42 minutes".
+ * <p>
+ * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative
+ * times, like "42 mins ago".
+ *
+ * @param time the time to describe, in milliseconds
+ * @param now the current time in milliseconds
+ * @param minResolution the minimum timespan to report. For example, a time
+ * 3 seconds in the past will be reported as "0 minutes ago" if
+ * this is set to MINUTE_IN_MILLIS. Pass one of 0,
+ * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS,
+ * WEEK_IN_MILLIS
+ * @param flags a bit mask of formatting options, such as
+ * {@link #FORMAT_NUMERIC_DATE} or
+ * {@link #FORMAT_ABBREV_RELATIVE}
+ */
+ public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution,
+ int flags) {
+ Resources r = Resources.getSystem();
+ boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0;
+
boolean past = (now >= time);
long duration = Math.abs(now - time);
-
+
int resId;
long count;
if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
count = duration / SECOND_IN_MILLIS;
if (past) {
- resId = com.android.internal.R.plurals.num_seconds_ago;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_num_seconds_ago;
+ } else {
+ resId = com.android.internal.R.plurals.num_seconds_ago;
+ }
} else {
- resId = com.android.internal.R.plurals.in_num_seconds;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_in_num_seconds;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_seconds;
+ }
}
} else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
count = duration / MINUTE_IN_MILLIS;
if (past) {
- resId = com.android.internal.R.plurals.num_minutes_ago;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_num_minutes_ago;
+ } else {
+ resId = com.android.internal.R.plurals.num_minutes_ago;
+ }
} else {
- resId = com.android.internal.R.plurals.in_num_minutes;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_in_num_minutes;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_minutes;
+ }
}
} else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
count = duration / HOUR_IN_MILLIS;
if (past) {
- resId = com.android.internal.R.plurals.num_hours_ago;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_num_hours_ago;
+ } else {
+ resId = com.android.internal.R.plurals.num_hours_ago;
+ }
} else {
- resId = com.android.internal.R.plurals.in_num_hours;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_in_num_hours;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_hours;
+ }
}
} else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
count = duration / DAY_IN_MILLIS;
if (past) {
- resId = com.android.internal.R.plurals.num_days_ago;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_num_days_ago;
+ } else {
+ resId = com.android.internal.R.plurals.num_days_ago;
+ }
} else {
- resId = com.android.internal.R.plurals.in_num_days;
+ if (abbrevRelative) {
+ resId = com.android.internal.R.plurals.abbrev_in_num_days;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_days;
+ }
}
} else {
- // Longer than a week ago, so just show the date.
- int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH;
-
// We know that we won't be showing the time, so it is safe to pass
// in a null context.
return formatDateRange(null, time, time, flags);
}
-
+
String format = r.getQuantityString(resId, (int) count);
return String.format(format, count);
}
+
+ /**
+ * Return string describing the elapsed time since startTime formatted like
+ * "[relative time/date], [time]".
+ * <p>
+ * Example output strings for the US date format.
+ * <ul>
+ * <li>3 mins ago, 10:15 AM</li>
+ * <li>yesterday, 12:20 PM</li>
+ * <li>Dec 12, 4:12 AM</li>
+ * <li>11/14/2007, 8:20 AM</li>
+ * </ul>
+ *
+ * @param time some time in the past.
+ * @param minResolution the minimum elapsed time (in milliseconds) to report
+ * when showing relative times. For example, a time 3 seconds in
+ * the past will be reported as "0 minutes ago" if this is set to
+ * {@link #MINUTE_IN_MILLIS}.
+ * @param transitionResolution the elapsed time (in milliseconds) at which
+ * to stop reporting relative measurements. Elapsed times greater
+ * than this resolution will default to normal date formatting.
+ * For example, will transition from "6 days ago" to "Dec 12"
+ * when using {@link #WEEK_IN_MILLIS}.
+ */
+ public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution,
+ long transitionResolution, int flags) {
+ Resources r = Resources.getSystem();
+
+ long now = System.currentTimeMillis();
+ long duration = Math.abs(now - time);
+
+ // getRelativeTimeSpanString() doesn't correctly format relative dates
+ // above a week or exact dates below a day, so clamp
+ // transitionResolution as needed.
+ if (transitionResolution > WEEK_IN_MILLIS) {
+ transitionResolution = WEEK_IN_MILLIS;
+ } else if (transitionResolution < DAY_IN_MILLIS) {
+ transitionResolution = DAY_IN_MILLIS;
+ }
+
+ CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME);
+
+ String result;
+ if (duration < transitionResolution) {
+ CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags);
+ result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause);
+ } else {
+ CharSequence dateClause = getRelativeTimeSpanString(c, time, false);
+ result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause);
+ }
+
+ return result;
+ }
/**
* Returns a string describing a day relative to the current day. For example if the day is
@@ -1005,8 +1116,8 @@ public class DateUtils
boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0;
boolean noYear = (flags & FORMAT_NO_YEAR) != 0;
boolean useUTC = (flags & FORMAT_UTC) != 0;
- boolean abbrevWeekDay = (flags & FORMAT_ABBREV_WEEKDAY) != 0;
- boolean abbrevMonth = (flags & FORMAT_ABBREV_MONTH) != 0;
+ boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0;
+ boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
@@ -1087,7 +1198,7 @@ public class DateUtils
startTimeFormat = HOUR_MINUTE_24;
endTimeFormat = HOUR_MINUTE_24;
} else {
- boolean abbrevTime = (flags & FORMAT_ABBREV_TIME) != 0;
+ boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0;
boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0;
boolean noNoon = (flags & FORMAT_NO_NOON) != 0;
boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
@@ -1419,7 +1530,6 @@ public class DateUtils
long now = System.currentTimeMillis();
long span = now - millis;
- Resources res = c.getResources();
if (sNowTime == null) {
sNowTime = new Time();
sThenTime = new Time();
@@ -1449,6 +1559,7 @@ public class DateUtils
prepositionId = R.string.preposition_for_date;
}
if (withPreposition) {
+ Resources res = c.getResources();
result = res.getString(prepositionId, result);
}
return result;
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 652413e..7457439 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -31,8 +31,10 @@ ArrowKeyMovementMethod
implements MovementMethod
{
private boolean up(TextView widget, Spannable buffer) {
- boolean cap = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1;
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
boolean alt = MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_ALT_ON) == 1;
Layout layout = widget.getLayout();
@@ -55,8 +57,10 @@ implements MovementMethod
}
private boolean down(TextView widget, Spannable buffer) {
- boolean cap = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1;
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
boolean alt = MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_ALT_ON) == 1;
Layout layout = widget.getLayout();
@@ -79,8 +83,10 @@ implements MovementMethod
}
private boolean left(TextView widget, Spannable buffer) {
- boolean cap = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1;
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
boolean alt = MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_ALT_ON) == 1;
Layout layout = widget.getLayout();
@@ -101,8 +107,10 @@ implements MovementMethod
}
private boolean right(TextView widget, Spannable buffer) {
- boolean cap = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1;
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
boolean alt = MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_ALT_ON) == 1;
Layout layout = widget.getLayout();
@@ -141,6 +149,13 @@ implements MovementMethod
case KeyEvent.KEYCODE_DPAD_RIGHT:
handled |= right(widget, buffer);
break;
+
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
+ if (widget.showContextMenu()) {
+ handled = true;
+ }
+ }
}
if (handled) {
@@ -179,7 +194,10 @@ implements MovementMethod
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
- boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0;
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
if (cap) {
Selection.extendSelection(buffer, off);
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index f0305d9..d5a473b 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -22,7 +22,8 @@ import android.text.*;
/**
* This base class encapsulates the behavior for handling the meta keys
- * (caps, fn, sym). Key listener that care about meta state should
+ * (shift and alt) and the pseudo-meta state of selecting text.
+ * Key listeners that care about meta state should
* inherit from it; you should not instantiate this class directly in a client.
*/
@@ -31,13 +32,49 @@ public abstract class MetaKeyKeyListener {
public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
- public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8;
- public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8;
- public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8;
+ private static final int LOCKED_SHIFT = 8;
+
+ public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT;
+ public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT;
+ public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT;
+
+ /**
+ * @hide pending API review
+ */
+ public static final int META_SELECTING = 1 << 16;
+
+ private static final int USED_SHIFT = 24;
+
+ private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT;
+ private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT;
+ private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT;
+
+ private static final int PRESSED_SHIFT = 32;
+
+ private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT;
+ private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT;
+ private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT;
+ private static final int RELEASED_SHIFT = 40;
+
+ private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT;
+ private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT;
+ private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT;
+
+ private static final long META_SHIFT_MASK = META_SHIFT_ON
+ | META_CAP_LOCKED | META_CAP_USED
+ | META_CAP_PRESSED | META_CAP_RELEASED;
+ private static final long META_ALT_MASK = META_ALT_ON
+ | META_ALT_LOCKED | META_ALT_USED
+ | META_ALT_PRESSED | META_ALT_RELEASED;
+ private static final long META_SYM_MASK = META_SYM_ON
+ | META_SYM_LOCKED | META_SYM_USED
+ | META_SYM_PRESSED | META_SYM_RELEASED;
+
private static final Object CAP = new Object();
private static final Object ALT = new Object();
private static final Object SYM = new Object();
+ private static final Object SELECTING = new Object();
/**
* Resets all meta state to inactive.
@@ -46,6 +83,7 @@ public abstract class MetaKeyKeyListener {
text.removeSpan(CAP);
text.removeSpan(ALT);
text.removeSpan(SYM);
+ text.removeSpan(SELECTING);
}
/**
@@ -59,13 +97,14 @@ public abstract class MetaKeyKeyListener {
public static final int getMetaState(CharSequence text) {
return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
- getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED);
+ getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
+ getActive(text, SELECTING, META_SELECTING, META_SELECTING);
}
/**
* Gets the state of a particular meta key.
*
- * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
+ * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
* @param text the buffer in which the meta key would have been pressed.
*
* @return 0 if inactive, 1 if active, 2 if locked.
@@ -81,6 +120,9 @@ public abstract class MetaKeyKeyListener {
case META_SYM_ON:
return getActive(text, SYM, 1, 2);
+ case META_SELECTING:
+ return getActive(text, SELECTING, 1, 2);
+
default:
return 0;
}
@@ -120,7 +162,8 @@ public abstract class MetaKeyKeyListener {
* keep track of meta state in the specified text.
*/
public static boolean isMetaTracker(CharSequence text, Object what) {
- return what == CAP || what == ALT || what == SYM;
+ return what == CAP || what == ALT || what == SYM ||
+ what == SELECTING;
}
private static void adjust(Spannable content, Object what) {
@@ -140,6 +183,7 @@ public abstract class MetaKeyKeyListener {
resetLock(content, CAP);
resetLock(content, ALT);
resetLock(content, SYM);
+ resetLock(content, SELECTING);
}
private static void resetLock(Spannable content, Object what) {
@@ -189,6 +233,23 @@ public abstract class MetaKeyKeyListener {
}
/**
+ * Start selecting text.
+ * @hide pending API review
+ */
+ public static void startSelecting(View view, Spannable content) {
+ content.setSpan(SELECTING, 0, 0, PRESSED);
+ }
+
+ /**
+ * Stop selecting text. This does not actually collapse the selection;
+ * call {@link android.text.Selection#setSelection} too.
+ * @hide pending API review
+ */
+ public static void stopSelecting(View view, Spannable content) {
+ content.removeSpan(SELECTING);
+ }
+
+ /**
* Handles release of the meta keys.
*/
public boolean onKeyUp(View view, Editable content, int keyCode,
@@ -225,6 +286,170 @@ public abstract class MetaKeyKeyListener {
if ((states&META_SHIFT_ON) != 0) resetLock(content, CAP);
if ((states&META_ALT_ON) != 0) resetLock(content, ALT);
if ((states&META_SYM_ON) != 0) resetLock(content, SYM);
+ if ((states&META_SELECTING) != 0) resetLock(content, SELECTING);
+ }
+
+ /**
+ * Call this if you are a method that ignores the locked meta state
+ * (arrow keys, for example) and you handle a key.
+ */
+ public static long resetLockedMeta(long state) {
+ state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
+ state = resetLock(state, META_ALT_ON, META_ALT_MASK);
+ state = resetLock(state, META_SYM_ON, META_SYM_MASK);
+ return state;
+ }
+
+ private static long resetLock(long state, int what, long mask) {
+ if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) {
+ state &= ~mask;
+ }
+ return state;
+ }
+
+ // ---------------------------------------------------------------------
+ // Version of API that operates on a state bit mask
+ // ---------------------------------------------------------------------
+
+ /**
+ * Gets the state of the meta keys.
+ *
+ * @param state the current meta state bits.
+ *
+ * @return an integer in which each bit set to one represents a pressed
+ * or locked meta key.
+ */
+ public static final int getMetaState(long state) {
+ return getActive(state, META_SHIFT_ON, META_SHIFT_ON, META_CAP_LOCKED) |
+ getActive(state, META_ALT_ON, META_ALT_ON, META_ALT_LOCKED) |
+ getActive(state, META_SYM_ON, META_SYM_ON, META_SYM_LOCKED);
+ }
+
+ /**
+ * Gets the state of a particular meta key.
+ *
+ * @param state the current state bits.
+ * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
+ *
+ * @return 0 if inactive, 1 if active, 2 if locked.
+ */
+ public static final int getMetaState(long state, int meta) {
+ switch (meta) {
+ case META_SHIFT_ON:
+ return getActive(state, meta, 1, 2);
+
+ case META_ALT_ON:
+ return getActive(state, meta, 1, 2);
+
+ case META_SYM_ON:
+ return getActive(state, meta, 1, 2);
+
+ default:
+ return 0;
+ }
+ }
+
+ private static int getActive(long state, int meta, int on, int lock) {
+ if ((state&(meta<<LOCKED_SHIFT)) != 0) {
+ return lock;
+ } else if ((state&meta) != 0) {
+ return on;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Call this method after you handle a keypress so that the meta
+ * state will be reset to unshifted (if it is not still down)
+ * or primed to be reset to unshifted (once it is released). Takes
+ * the current state, returns the new state.
+ */
+ public static long adjustMetaAfterKeypress(long state) {
+ state = adjust(state, META_SHIFT_ON, META_SHIFT_MASK);
+ state = adjust(state, META_ALT_ON, META_ALT_MASK);
+ state = adjust(state, META_SYM_ON, META_SYM_MASK);
+ return state;
+ }
+
+ private static long adjust(long state, int what, long mask) {
+ if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
+ return (state&~mask) | what | ((long)what)<<USED_SHIFT;
+ else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
+ return state & ~mask;
+ return state;
+ }
+
+ /**
+ * Handles presses of the meta keys.
+ */
+ public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+ return press(state, META_SHIFT_ON, META_SHIFT_MASK);
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+ || keyCode == KeyEvent.KEYCODE_NUM) {
+ return press(state, META_ALT_ON, META_ALT_MASK);
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_SYM) {
+ return press(state, META_SYM_ON, META_SYM_MASK);
+ }
+
+ return state;
+ }
+
+ private static long press(long state, int what, long mask) {
+ if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
+ ; // repeat before use
+ else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
+ state = (state&~mask) | what | (((long)what) << LOCKED_SHIFT);
+ else if ((state&(((long)what)<<USED_SHIFT)) != 0)
+ ; // repeat after use
+ else if ((state&(((long)what)<<LOCKED_SHIFT)) != 0)
+ state = state&~mask;
+ else
+ state = state | what | (((long)what)<<PRESSED_SHIFT);
+ return state;
+ }
+
+ /**
+ * Handles release of the meta keys.
+ */
+ public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+ return release(state, META_SHIFT_ON, META_SHIFT_MASK);
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+ || keyCode == KeyEvent.KEYCODE_NUM) {
+ return release(state, META_ALT_ON, META_ALT_MASK);
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_SYM) {
+ return release(state, META_SYM_ON, META_SYM_MASK);
+ }
+
+ return state;
+ }
+
+ private static long release(long state, int what, long mask) {
+ if ((state&(((long)what)<<USED_SHIFT)) != 0)
+ state = state&~mask;
+ else if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
+ state = state | what | (((long)what)<<RELEASED_SHIFT);
+ return state;
+ }
+
+ public long clearMetaKeyState(long state, int which) {
+ if ((which&META_SHIFT_ON) != 0)
+ state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
+ if ((which&META_ALT_ON) != 0)
+ state = resetLock(state, META_ALT_ON, META_ALT_MASK);
+ if ((which&META_SYM_ON) != 0)
+ state = resetLock(state, META_SYM_ON, META_SYM_MASK);
+ return state;
}
/**
diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
index be6ef77..27eda69 100644
--- a/core/java/android/text/style/BackgroundColorSpan.java
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -18,7 +18,7 @@ package android.text.style;
import android.text.TextPaint;
-public class BackgroundColorSpan extends CharacterStyle {
+public class BackgroundColorSpan extends CharacterStyle implements UpdateAppearance {
private int mColor;
diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java
index 7620d29..14dfddd 100644
--- a/core/java/android/text/style/CharacterStyle.java
+++ b/core/java/android/text/style/CharacterStyle.java
@@ -16,12 +16,12 @@
package android.text.style;
-import android.graphics.Paint;
import android.text.TextPaint;
/**
* The classes that affect character-level text formatting extend this
- * class. Most also extend {@link MetricAffectingSpan}.
+ * class. Most extend its subclass {@link MetricAffectingSpan}, but simple
+ * ones may just implement {@link UpdateAppearance}.
*/
public abstract class CharacterStyle {
public abstract void updateDrawState(TextPaint tp);
diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java
index a232ed7..989ef54 100644
--- a/core/java/android/text/style/ClickableSpan.java
+++ b/core/java/android/text/style/ClickableSpan.java
@@ -25,7 +25,7 @@ import android.view.View;
* text can be selected. If clicked, the {@link #onClick} method will
* be called.
*/
-public abstract class ClickableSpan extends CharacterStyle {
+public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {
/**
* Performs the click action associated with this span.
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
index dd89b68..89dc45b 100644
--- a/core/java/android/text/style/DynamicDrawableSpan.java
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -17,18 +17,55 @@
package android.text.style;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import java.lang.ref.WeakReference;
/**
*
*/
-public abstract class DynamicDrawableSpan
-extends ReplacementSpan
-{
+public abstract class DynamicDrawableSpan extends ReplacementSpan {
+ private static final String TAG = "DynamicDrawableSpan";
+
+ /**
+ * A constant indicating that the bottom of this span should be aligned
+ * with the bottom of the surrounding text, i.e., at the same level as the
+ * lowest descender in the text.
+ */
+ public static final int ALIGN_BOTTOM = 0;
+
+ /**
+ * A constant indicating that the bottom of this span should be aligned
+ * with the baseline of the surrounding text.
+ */
+ public static final int ALIGN_BASELINE = 1;
+
+ protected final int mVerticalAlignment;
+
+ public DynamicDrawableSpan() {
+ mVerticalAlignment = ALIGN_BOTTOM;
+ }
+
+ /**
+ * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}.
+ */
+ protected DynamicDrawableSpan(int verticalAlignment) {
+ mVerticalAlignment = verticalAlignment;
+ }
+
+ /**
+ * Returns the vertical alignment of this span, one of {@link #ALIGN_BOTTOM} or
+ * {@link #ALIGN_BASELINE}.
+ */
+ public int getVerticalAlignment() {
+ return mVerticalAlignment;
+ }
+
/**
* Your subclass must implement this method to provide the bitmap
* to be drawn. The dimensions of the bitmap must be the same
@@ -61,7 +98,12 @@ extends ReplacementSpan
Drawable b = getCachedDrawable();
canvas.save();
- canvas.translate(x, bottom - b.getBounds().bottom);
+ int transY = bottom - b.getBounds().bottom;
+ if (mVerticalAlignment == ALIGN_BASELINE) {
+ transY -= paint.getFontMetricsInt().descent;
+ }
+
+ canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index 5cccd9c..99b3381 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -16,10 +16,9 @@
package android.text.style;
-import android.graphics.Paint;
import android.text.TextPaint;
-public class ForegroundColorSpan extends CharacterStyle {
+public class ForegroundColorSpan extends CharacterStyle implements UpdateAppearance {
private int mColor;
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index de067b1..2eebc0d 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -32,29 +32,73 @@ public class ImageSpan extends DynamicDrawableSpan {
private int mResourceId;
private Context mContext;
private String mSource;
-
public ImageSpan(Bitmap b) {
+ this(b, ALIGN_BOTTOM);
+ }
+
+ /**
+ * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+ * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+ */
+ public ImageSpan(Bitmap b, int verticalAlignment) {
+ super(verticalAlignment);
mDrawable = new BitmapDrawable(b);
mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(),
mDrawable.getIntrinsicHeight());
}
public ImageSpan(Drawable d) {
+ this(d, ALIGN_BOTTOM);
+ }
+
+ /**
+ * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+ * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+ */
+ public ImageSpan(Drawable d, int verticalAlignment) {
+ super(verticalAlignment);
mDrawable = d;
}
public ImageSpan(Drawable d, String source) {
+ this(d, source, ALIGN_BOTTOM);
+ }
+
+ /**
+ * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+ * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+ */
+ public ImageSpan(Drawable d, String source, int verticalAlignment) {
+ super(verticalAlignment);
mDrawable = d;
mSource = source;
}
public ImageSpan(Context context, Uri uri) {
+ this(context, uri, ALIGN_BOTTOM);
+ }
+
+ /**
+ * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+ * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+ */
+ public ImageSpan(Context context, Uri uri, int verticalAlignment) {
+ super(verticalAlignment);
mContext = context;
mContentUri = uri;
}
public ImageSpan(Context context, int resourceId) {
+ this(context, resourceId, ALIGN_BOTTOM);
+ }
+
+ /**
+ * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
+ * {@link DynamicDrawableSpan#ALIGN_BASELINE}.
+ */
+ public ImageSpan(Context context, int resourceId, int verticalAlignment) {
+ super(verticalAlignment);
mContext = context;
mResourceId = resourceId;
}
diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java
index 781bcec..64ab0d8 100644
--- a/core/java/android/text/style/MaskFilterSpan.java
+++ b/core/java/android/text/style/MaskFilterSpan.java
@@ -16,11 +16,10 @@
package android.text.style;
-import android.graphics.Paint;
import android.graphics.MaskFilter;
import android.text.TextPaint;
-public class MaskFilterSpan extends CharacterStyle {
+public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance {
private MaskFilter mFilter;
diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java
index 193c700..75b5bcc 100644
--- a/core/java/android/text/style/RasterizerSpan.java
+++ b/core/java/android/text/style/RasterizerSpan.java
@@ -16,11 +16,10 @@
package android.text.style;
-import android.graphics.Paint;
import android.graphics.Rasterizer;
import android.text.TextPaint;
-public class RasterizerSpan extends CharacterStyle {
+public class RasterizerSpan extends CharacterStyle implements UpdateAppearance {
private Rasterizer mRasterizer;
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
index 01ae38c..dd430e5 100644
--- a/core/java/android/text/style/StrikethroughSpan.java
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -18,7 +18,7 @@ package android.text.style;
import android.text.TextPaint;
-public class StrikethroughSpan extends CharacterStyle {
+public class StrikethroughSpan extends CharacterStyle implements UpdateAppearance {
@Override
public void updateDrawState(TextPaint ds) {
diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
index 5dce0f2..ca6f10c 100644
--- a/core/java/android/text/style/UnderlineSpan.java
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -18,7 +18,7 @@ package android.text.style;
import android.text.TextPaint;
-public class UnderlineSpan extends CharacterStyle {
+public class UnderlineSpan extends CharacterStyle implements UpdateAppearance {
@Override
public void updateDrawState(TextPaint ds) {
diff --git a/core/java/android/text/style/UpdateAppearance.java b/core/java/android/text/style/UpdateAppearance.java
new file mode 100644
index 0000000..198e4fa
--- /dev/null
+++ b/core/java/android/text/style/UpdateAppearance.java
@@ -0,0 +1,10 @@
+package android.text.style;
+
+/**
+ * The classes that affect character-level text in a way that modifies their
+ * appearance when one is added or removed must implement this interface. Note
+ * that if the class also impacts size or other metrics, it should instead
+ * implement {@link UpdateLayout}.
+ */
+public interface UpdateAppearance {
+}
diff --git a/core/java/android/text/style/UpdateLayout.java b/core/java/android/text/style/UpdateLayout.java
index 211685a..591075e 100644
--- a/core/java/android/text/style/UpdateLayout.java
+++ b/core/java/android/text/style/UpdateLayout.java
@@ -18,7 +18,8 @@ package android.text.style;
/**
* The classes that affect character-level text formatting in a way that
- * triggers a text layout update when one is added or remove must implement
- * this interface.
+ * triggers a text layout update when one is added or removed must implement
+ * this interface. This interface also includes {@link UpdateAppearance}
+ * since such a change implicitly also impacts the appearance.
*/
-public interface UpdateLayout { }
+public interface UpdateLayout extends UpdateAppearance { }