Merge "Add camera fps range API." into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 61f5e16..06e9bfd 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -166341,6 +166341,29 @@
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
+<method name="setCursorController"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursorController" type="android.widget.TextView.CursorController">
+</parameter>
+</method>
+<field name="mCursorController"
+ type="android.widget.TextView.CursorController"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
</class>
<class name="BaseKeyListener"
extends="android.text.method.MetaKeyKeyListener"
@@ -222825,6 +222848,108 @@
>
</method>
</class>
+<interface name="TextView.CursorController"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="draw"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="getOffsetX"
+ return="float"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getOffsetY"
+ return="float"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hide"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onTouchEvent"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="updatePosition"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<field name="FADE_OUT_DURATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="400"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
<interface name="TextView.OnEditorActionListener"
abstract="true"
static="true"
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 78bbb4f..009671f 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -35,6 +35,9 @@
/**
* A simple dialog containing an {@link android.widget.DatePicker}.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date Picker
+ * tutorial</a>.</p>
*/
public class DatePickerDialog extends AlertDialog implements OnClickListener,
OnDateChangedListener {
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 002b01f..62d3f7d 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -32,6 +32,9 @@
/**
* A dialog that prompts the user for the time of day using a {@link TimePicker}.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-timepicker.html">Time Picker
+ * tutorial</a>.</p>
*/
public class TimePickerDialog extends AlertDialog implements OnClickListener,
OnTimeChangedListener {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index b0c149d..37fdeb6 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -708,9 +708,7 @@
outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
outValue.density = data[index+AssetManager.STYLE_DENSITY];
- if (type == TypedValue.TYPE_STRING) {
- outValue.string = loadStringValueAt(index);
- }
+ outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
return true;
}
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index bb98bce..13cb5e6 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -417,8 +417,8 @@
}
}
- private static final class START implements NoCopySpan { };
- private static final class END implements NoCopySpan { };
+ private static final class START implements NoCopySpan { }
+ private static final class END implements NoCopySpan { }
/*
* Public constants
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 9af42cc..79a0c37 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,30 +16,38 @@
package android.text.method;
-import android.util.Log;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
import android.view.KeyEvent;
-import android.graphics.Rect;
-import android.text.*;
-import android.widget.TextView;
-import android.view.View;
-import android.view.ViewConfiguration;
import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.TextView.CursorController;
// XXX this doesn't extend MetaKeyKeyListener because the signatures
// don't match. Need to figure that out. Meanwhile the meta keys
// won't work in fields that don't take input.
-public class
-ArrowKeyMovementMethod
-implements MovementMethod
-{
+public class ArrowKeyMovementMethod implements MovementMethod {
+ /**
+ * An optional controller for the cursor.
+ * Use {@link #setCursorController(CursorController)} to set this field.
+ */
+ protected CursorController mCursorController;
+
+ private boolean isCap(Spannable buffer) {
+ return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
+ }
+
+ private boolean isAlt(Spannable buffer) {
+ return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
+ }
+
private boolean up(TextView widget, Spannable buffer) {
- 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;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -60,12 +68,8 @@
}
private boolean down(TextView widget, Spannable buffer) {
- 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;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -86,12 +90,8 @@
}
private boolean left(TextView widget, Spannable buffer) {
- 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;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -110,12 +110,8 @@
}
private boolean right(TextView widget, Spannable buffer) {
- 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;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -133,35 +129,6 @@
}
}
- private int getOffset(int x, int y, TextView widget){
- // Converts the absolute X,Y coordinates to the character offset for the
- // character whose position is closest to the specified
- // horizontal position.
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- // Clamp the position to inside of the view.
- if (x < 0) {
- x = 0;
- } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
- x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
- }
- if (y < 0) {
- y = 0;
- } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
- y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
- }
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
-
- int offset = layout.getOffsetForHorizontal(line, x);
- return offset;
- }
-
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
if (executeDown(widget, buffer, keyCode)) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -193,10 +160,9 @@
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
- if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
- if (widget.showContextMenu()) {
+ if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
+ (widget.showContextMenu())) {
handled = true;
- }
}
}
@@ -214,8 +180,7 @@
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
int code = event.getKeyCode();
- if (code != KeyEvent.KEYCODE_UNKNOWN
- && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
int repeat = event.getRepeatCount();
boolean handled = false;
while ((--repeat) > 0) {
@@ -226,13 +191,22 @@
return false;
}
- public boolean onTrackballEvent(TextView widget, Spannable text,
- MotionEvent event) {
+ public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
+ if (mCursorController != null) {
+ mCursorController.hide();
+ }
return false;
}
- public boolean onTouchEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
+ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+ if (mCursorController != null) {
+ return onTouchEventCursor(widget, buffer, event);
+ } else {
+ return onTouchEventStandard(widget, buffer, event);
+ }
+ }
+
+ private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
if (event.getAction() == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
@@ -243,53 +217,20 @@
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
- int x = (int) event.getX();
- int y = (int) event.getY();
- int offset = getOffset(x, y, widget);
-
+ boolean cap = isCap(buffer);
if (cap) {
- buffer.setSpan(LAST_TAP_DOWN, offset, offset,
- Spannable.SPAN_POINT_POINT);
+ int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+
+ buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
// users can scroll and select at the same time.
// without this, users would get booted out of select
// mode once the view detected it needed to scroll.
widget.getParent().requestDisallowInterceptTouchEvent(true);
- } else {
- OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
- OnePointFiveTapState.class);
-
- if (tap.length > 0) {
- if (event.getEventTime() - tap[0].mWhen <=
- ViewConfiguration.getDoubleTapTimeout() &&
- sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {
-
- tap[0].active = true;
- MetaKeyKeyListener.startSelecting(widget, buffer);
- widget.getParent().requestDisallowInterceptTouchEvent(true);
- buffer.setSpan(LAST_TAP_DOWN, offset, offset,
- Spannable.SPAN_POINT_POINT);
- }
-
- tap[0].mWhen = event.getEventTime();
- } else {
- OnePointFiveTapState newtap = new OnePointFiveTapState();
- newtap.mWhen = event.getEventTime();
- newtap.active = false;
- buffer.setSpan(newtap, 0, buffer.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- }
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
+ boolean cap = isCap(buffer);
if (cap && handled) {
// Before selecting, make sure we've moved out of the "slop".
@@ -297,45 +238,15 @@
// OUT of the slop
// Turn long press off while we're selecting. User needs to
- // re-tap on the selection to enable longpress
+ // re-tap on the selection to enable long press
widget.cancelLongPress();
// Update selection as we're moving the selection area.
// Get the current touch position
- int x = (int) event.getX();
- int y = (int) event.getY();
- int offset = getOffset(x, y, widget);
+ int offset = widget.getOffset((int) event.getX(), (int) event.getY());
- final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
- OnePointFiveTapState.class);
-
- if (tap.length > 0 && tap[0].active) {
- // Get the last down touch position (the position at which the
- // user started the selection)
- int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);
-
- // Compute the selection boundaries
- int spanstart;
- int spanend;
- if (offset >= lastDownOffset) {
- // Expand from word start of the original tap to new word
- // end, since we are selecting "forwards"
- spanstart = findWordStart(buffer, lastDownOffset);
- spanend = findWordEnd(buffer, offset);
- } else {
- // Expand to from new word start to word end of the original
- // tap since we are selecting "backwards".
- // The spanend will always need to be associated with the touch
- // up position, so that refining the selection with the
- // trackball will work as expected.
- spanstart = findWordEnd(buffer, lastDownOffset);
- spanend = findWordStart(buffer, offset);
- }
- Selection.setSelection(buffer, spanstart, spanend);
- } else {
- Selection.extendSelection(buffer, offset);
- }
+ Selection.extendSelection(buffer, offset);
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
@@ -344,70 +255,17 @@
// the current scroll offset to avoid the scroll jumping later
// to show it.
if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
- (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
+ (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
widget.moveCursorToVisibleOffset();
return true;
}
- int x = (int) event.getX();
- int y = (int) event.getY();
- int off = getOffset(x, y, widget);
-
- // XXX should do the same adjust for x as we do for the line.
-
- OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
- OnePointFiveTapState.class);
- if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
- Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
- // If we've set select mode, because there was a onepointfivetap,
- // but there was no ensuing swipe gesture, undo the select mode
- // and remove reference to the last onepointfivetap.
- MetaKeyKeyListener.stopSelecting(widget, buffer);
- for (int i=0; i < onepointfivetap.length; i++) {
- buffer.removeSpan(onepointfivetap[i]);
- }
+ int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ if (isCap(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
- }
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (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() &&
- sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
-
- doubletap = true;
- }
-
- tap[0].mWhen = event.getEventTime();
+ Selection.extendSelection(buffer, offset);
} else {
- DoubleTapState newtap = new DoubleTapState();
- newtap.mWhen = event.getEventTime();
- buffer.setSpan(newtap, 0, buffer.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- }
-
- if (cap) {
- buffer.removeSpan(LAST_TAP_DOWN);
- if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
- // If we selecting something with the onepointfivetap-and
- // swipe gesture, stop it on finger up.
- MetaKeyKeyListener.stopSelecting(widget, buffer);
- } else {
- Selection.extendSelection(buffer, off);
- }
- } else if (doubletap) {
- Selection.setSelection(buffer,
- findWordStart(buffer, off),
- findWordEnd(buffer, off));
- } else {
- Selection.setSelection(buffer, off);
+ Selection.setSelection(buffer, offset);
}
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -420,73 +278,36 @@
return handled;
}
- private static class DoubleTapState implements NoCopySpan {
- long mWhen;
- }
+ private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
+ if (widget.isFocused() && !widget.didTouchFocusSelect()) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ widget.cancelLongPress();
- /* We check for a onepointfive tap. This is similar to
- * doubletap gesture (where a finger goes down, up, down, up, in a short
- * time period), except in the onepointfive tap, a users finger only needs
- * to go down, up, down in a short time period. We detect this type of tap
- * to implement the onepointfivetap-and-swipe selection gesture.
- * This gesture allows users to select a segment of text without going
- * through the "select text" option in the context menu.
- */
- private static class OnePointFiveTapState implements NoCopySpan {
- long mWhen;
- boolean active;
- }
+ // Offset the current touch position (from controller to cursor)
+ final float x = event.getX() + mCursorController.getOffsetX();
+ final float y = event.getY() + mCursorController.getOffsetY();
+ int offset = widget.getOffset((int) x, (int) y);
+ mCursorController.updatePosition(offset);
+ return true;
- 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;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mCursorController = null;
+ return true;
}
}
-
- return start;
+ return false;
}
- // 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;
+ /**
+ * Defines the cursor controller.
+ *
+ * When set, this object can be used to handle events, that can be translated in cursor updates.
+ * @param cursorController A cursor controller implementation
+ */
+ public void setCursorController(CursorController cursorController) {
+ mCursorController = cursorController;
}
public boolean canSelectArbitrarily() {
@@ -525,8 +346,9 @@
}
public static MovementMethod getInstance() {
- if (sInstance == null)
+ if (sInstance == null) {
sInstance = new ArrowKeyMovementMethod();
+ }
return sInstance;
}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 42ad10e..a19a78e 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -17,14 +17,13 @@
package android.text.method;
import android.text.Layout;
-import android.text.NoCopySpan;
import android.text.Layout.Alignment;
+import android.text.NoCopySpan;
import android.text.Spannable;
-import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
-import android.view.KeyEvent;
public class Touch {
private Touch() { }
@@ -99,7 +98,7 @@
MotionEvent event) {
DragState[] ds;
- switch (event.getAction()) {
+ switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java
index 840d7c1..6ad33dd 100644
--- a/core/java/android/view/AbsSavedState.java
+++ b/core/java/android/view/AbsSavedState.java
@@ -54,7 +54,7 @@
*/
protected AbsSavedState(Parcel source) {
// FIXME need class loader
- Parcelable superState = (Parcelable) source.readParcelable(null);
+ Parcelable superState = source.readParcelable(null);
mSuperState = superState != null ? superState : EMPTY_STATE;
}
@@ -75,7 +75,7 @@
= new Parcelable.Creator<AbsSavedState>() {
public AbsSavedState createFromParcel(Parcel in) {
- Parcelable superState = (Parcelable) in.readParcelable(null);
+ Parcelable superState = in.readParcelable(null);
if (superState != null) {
throw new IllegalStateException("superState must be null");
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 47061c1..74318ba 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -772,7 +772,7 @@
*
* @param pointerId The identifier of the pointer to be found.
* @return Returns either the index of the pointer (for use with
- * {@link #getX(int) et al.), or -1 if there is no data available for
+ * {@link #getX(int)} et al.), or -1 if there is no data available for
* that pointer identifier.
*/
public final int findPointerIndex(int pointerId) {
@@ -1319,21 +1319,24 @@
* @param y New absolute Y location.
*/
public final void setLocation(float x, float y) {
- mXOffset = x - mDataSamples[mLastDataSampleIndex + SAMPLE_X];
- mYOffset = y - mDataSamples[mLastDataSampleIndex + SAMPLE_Y];
+ final float[] dataSamples = mDataSamples;
+ final int lastDataSampleIndex = mLastDataSampleIndex;
+ mXOffset = x - dataSamples[lastDataSampleIndex + SAMPLE_X];
+ mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y];
}
private final void getPointerCoordsAtSampleIndex(int sampleIndex,
PointerCoords outPointerCoords) {
- outPointerCoords.x = mDataSamples[sampleIndex + SAMPLE_X] + mXOffset;
- outPointerCoords.y = mDataSamples[sampleIndex + SAMPLE_Y] + mYOffset;
- outPointerCoords.pressure = mDataSamples[sampleIndex + SAMPLE_PRESSURE];
- outPointerCoords.size = mDataSamples[sampleIndex + SAMPLE_SIZE];
- outPointerCoords.touchMajor = mDataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR];
- outPointerCoords.touchMinor = mDataSamples[sampleIndex + SAMPLE_TOUCH_MINOR];
- outPointerCoords.toolMajor = mDataSamples[sampleIndex + SAMPLE_TOOL_MAJOR];
- outPointerCoords.toolMinor = mDataSamples[sampleIndex + SAMPLE_TOOL_MINOR];
- outPointerCoords.orientation = mDataSamples[sampleIndex + SAMPLE_ORIENTATION];
+ final float[] dataSamples = mDataSamples;
+ outPointerCoords.x = dataSamples[sampleIndex + SAMPLE_X] + mXOffset;
+ outPointerCoords.y = dataSamples[sampleIndex + SAMPLE_Y] + mYOffset;
+ outPointerCoords.pressure = dataSamples[sampleIndex + SAMPLE_PRESSURE];
+ outPointerCoords.size = dataSamples[sampleIndex + SAMPLE_SIZE];
+ outPointerCoords.touchMajor = dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR];
+ outPointerCoords.touchMinor = dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR];
+ outPointerCoords.toolMajor = dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR];
+ outPointerCoords.toolMinor = dataSamples[sampleIndex + SAMPLE_TOOL_MINOR];
+ outPointerCoords.orientation = dataSamples[sampleIndex + SAMPLE_ORIENTATION];
}
private final void setPointerCoordsAtSampleIndex(int sampleIndex,
@@ -1347,28 +1350,30 @@
private final void setPointerCoordsAtSampleIndex(int sampleIndex,
PointerCoords pointerCoords) {
- mDataSamples[sampleIndex + SAMPLE_X] = pointerCoords.x - mXOffset;
- mDataSamples[sampleIndex + SAMPLE_Y] = pointerCoords.y - mYOffset;
- mDataSamples[sampleIndex + SAMPLE_PRESSURE] = pointerCoords.pressure;
- mDataSamples[sampleIndex + SAMPLE_SIZE] = pointerCoords.size;
- mDataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pointerCoords.touchMajor;
- mDataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pointerCoords.touchMinor;
- mDataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = pointerCoords.toolMajor;
- mDataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = pointerCoords.toolMinor;
- mDataSamples[sampleIndex + SAMPLE_ORIENTATION] = pointerCoords.orientation;
+ final float[] dataSamples = mDataSamples;
+ dataSamples[sampleIndex + SAMPLE_X] = pointerCoords.x - mXOffset;
+ dataSamples[sampleIndex + SAMPLE_Y] = pointerCoords.y - mYOffset;
+ dataSamples[sampleIndex + SAMPLE_PRESSURE] = pointerCoords.pressure;
+ dataSamples[sampleIndex + SAMPLE_SIZE] = pointerCoords.size;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pointerCoords.touchMajor;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pointerCoords.touchMinor;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = pointerCoords.toolMajor;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = pointerCoords.toolMinor;
+ dataSamples[sampleIndex + SAMPLE_ORIENTATION] = pointerCoords.orientation;
}
private final void setPointerCoordsAtSampleIndex(int sampleIndex,
float x, float y, float pressure, float size) {
- mDataSamples[sampleIndex + SAMPLE_X] = x - mXOffset;
- mDataSamples[sampleIndex + SAMPLE_Y] = y - mYOffset;
- mDataSamples[sampleIndex + SAMPLE_PRESSURE] = pressure;
- mDataSamples[sampleIndex + SAMPLE_SIZE] = size;
- mDataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pressure;
- mDataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pressure;
- mDataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = size;
- mDataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = size;
- mDataSamples[sampleIndex + SAMPLE_ORIENTATION] = 0;
+ final float[] dataSamples = mDataSamples;
+ dataSamples[sampleIndex + SAMPLE_X] = x - mXOffset;
+ dataSamples[sampleIndex + SAMPLE_Y] = y - mYOffset;
+ dataSamples[sampleIndex + SAMPLE_PRESSURE] = pressure;
+ dataSamples[sampleIndex + SAMPLE_SIZE] = size;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pressure;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pressure;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = size;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = size;
+ dataSamples[sampleIndex + SAMPLE_ORIENTATION] = 0;
}
private final void incrementNumSamplesAndReserveStorage(int dataSampleStride) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b8623e7..c9662ff 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2413,11 +2413,10 @@
}
/**
- * Call this view's OnLongClickListener, if it is defined. Invokes the context menu
- * if the OnLongClickListener did not consume the event.
+ * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
+ * OnLongClickListener did not consume the event.
*
- * @return True there was an assigned OnLongClickListener that was called, false
- * otherwise is returned.
+ * @return True if one of the above receivers consumed the event, false otherwise.
*/
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
@@ -4218,6 +4217,10 @@
* Show the context menu for this view. It is not safe to hold on to the
* menu after returning from this method.
*
+ * You should normally not overload this method. Overload
+ * {@link #onCreateContextMenu(ContextMenu)} or define an
+ * {@link OnCreateContextMenuListener} to add items to the context menu.
+ *
* @param menu The context menu to populate
*/
public void createContextMenu(ContextMenu menu) {
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 1d5aac7..9f642c0 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -320,4 +320,22 @@
public void openFileChooser(ValueCallback<Uri> uploadFile) {
uploadFile.onReceiveValue(null);
}
+
+ /**
+ * Tell the client that the selection has been initiated.
+ * @hide
+ */
+ public void onSelectionStart(WebView view) {
+ // By default we cancel the selection again, thus disabling
+ // text selection unless the chrome client supports it.
+ view.notifySelectDialogDismissed();
+ }
+
+ /**
+ * Tell the client that the selection has been copied or canceled.
+ * @hide
+ */
+ public void onSelectionDone(WebView view) {
+ }
+
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index bf751f5..3e0187d 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -117,6 +117,9 @@
* href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
* element.</p>
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View
+ * tutorial</a>.</p>
+ *
* <h3>Basic usage</h3>
*
* <p>By default, a WebView provides no browser-like widgets, does not
@@ -456,8 +459,7 @@
private static final int TOUCH_SHORTPRESS_MODE = 5;
private static final int TOUCH_DOUBLE_TAP_MODE = 6;
private static final int TOUCH_DONE_MODE = 7;
- private static final int TOUCH_SELECT_MODE = 8;
- private static final int TOUCH_PINCH_DRAG = 9;
+ private static final int TOUCH_PINCH_DRAG = 8;
// Whether to forward the touch events to WebCore
private boolean mForwardTouchEvents = false;
@@ -2690,6 +2692,14 @@
nativeSetFindIsUp(isUp);
}
+ /**
+ * @hide
+ */
+ public int findIndex() {
+ if (0 == mNativeClass) return -1;
+ return nativeFindIndex();
+ }
+
// Used to know whether the find dialog is open. Affects whether
// or not we draw the highlights for matches.
private boolean mFindIsUp;
@@ -3306,12 +3316,33 @@
// Send the click so that the textfield is in focus
centerKeyPressOnTextField();
rebuildWebTextView();
+ } else {
+ clearTextEntry(true);
}
if (inEditingMode()) {
return mWebTextView.performLongClick();
- } else {
- return super.performLongClick();
}
+ /* if long click brings up a context menu, the super function
+ * returns true and we're done. Otherwise, nothing happened when
+ * the user clicked. */
+ if (super.performLongClick()) {
+ return true;
+ }
+ /* In the case where the application hasn't already handled the long
+ * click action, look for a word under the click. If one is found,
+ * animate the text selection into view.
+ * FIXME: no animation code yet */
+ if (mSelectingText) return false; // long click does nothing on selection
+ int x = viewToContentX((int) mLastTouchX + mScrollX);
+ int y = viewToContentY((int) mLastTouchY + mScrollY);
+ setUpSelect();
+ if (mNativeClass != 0 && nativeWordSelection(x, y)) {
+ nativeSetExtendSelection();
+ getWebChromeClient().onSelectionStart(this);
+ return true;
+ }
+ notifySelectDialogDismissed();
+ return false;
}
boolean inAnimateZoom() {
@@ -3462,19 +3493,12 @@
// decide which adornments to draw
int extras = DRAW_EXTRAS_NONE;
if (mFindIsUp) {
- // When the FindDialog is up, only draw the matches if we are not in
- // the process of scrolling them into view.
- if (!animateScroll) {
extras = DRAW_EXTRAS_FIND;
- }
- } else if (mShiftIsPressed && !nativeFocusIsPlugin()) {
- if (!animateZoom && !mPreviewZoomOnly) {
- extras = DRAW_EXTRAS_SELECTION;
- nativeSetSelectionRegion(mTouchSelection || mExtendSelection);
- nativeSetSelectionPointer(!mTouchSelection, mInvActualScale,
- mSelectX, mSelectY - getTitleHeight(),
- mExtendSelection);
- }
+ } else if (mSelectingText) {
+ extras = DRAW_EXTRAS_SELECTION;
+ nativeSetSelectionPointer(mDrawSelectionPointer,
+ mInvActualScale,
+ mSelectX, mSelectY - getTitleHeight());
} else if (drawCursorRing) {
extras = DRAW_EXTRAS_CURSOR_RING;
}
@@ -3483,11 +3507,6 @@
if (extras == DRAW_EXTRAS_CURSOR_RING) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
- HitTestResult hitTest = getHitTestResult();
- if (hitTest == null
- || hitTest.mType == HitTestResult.UNKNOWN_TYPE) {
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- }
}
}
if (mFocusSizeChanged) {
@@ -3826,8 +3845,8 @@
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
if (nativeFocusIsPlugin()) {
mShiftIsPressed = true;
- } else if (!nativeCursorWantsKeyEvents() && !mShiftIsPressed) {
- setUpSelectXY();
+ } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) {
+ setUpSelect();
}
}
@@ -3838,7 +3857,7 @@
letPluginHandleNavKey(keyCode, event.getEventTime(), true);
return true;
}
- if (mShiftIsPressed) {
+ if (mSelectingText) {
int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
@@ -3858,7 +3877,7 @@
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
switchOutDrawHistory();
if (event.getRepeatCount() == 0) {
- if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ if (mSelectingText) {
return true; // discard press if copy in progress
}
mGotCenterDown = true;
@@ -3876,10 +3895,8 @@
if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
&& keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
// turn off copy select if a shift-key combo is pressed
- mExtendSelection = mShiftIsPressed = false;
- if (mTouchMode == TOUCH_SELECT_MODE) {
- mTouchMode = TOUCH_INIT_MODE;
- }
+ selectionDone();
+ mShiftIsPressed = false;
}
if (getSettings().getNavDump()) {
@@ -3969,7 +3986,8 @@
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
if (nativeFocusIsPlugin()) {
mShiftIsPressed = false;
- } else if (commitCopy()) {
+ } else if (copySelection()) {
+ selectionDone();
return true;
}
}
@@ -3990,11 +4008,13 @@
mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
mGotCenterDown = false;
- if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ if (mSelectingText) {
if (mExtendSelection) {
- commitCopy();
+ copySelection();
+ selectionDone();
} else {
mExtendSelection = true;
+ nativeSetExtendSelection();
invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
@@ -4039,9 +4059,18 @@
return false;
}
- private void setUpSelectXY() {
+ /**
+ * @hide pending API council approval.
+ */
+ public void setUpSelect() {
+ if (0 == mNativeClass) return; // client isn't initialized
+ if (inFullScreenMode()) return;
+ if (mSelectingText) return;
mExtendSelection = false;
- mShiftIsPressed = true;
+ mSelectingText = mDrawSelectionPointer = true;
+ // don't let the picture change during text selection
+ WebViewCore.pauseUpdatePicture(mWebViewCore);
+ nativeResetSelection();
if (nativeHasCursorNode()) {
Rect rect = nativeCursorNodeBounds();
mSelectX = contentToViewX(rect.left);
@@ -4061,40 +4090,82 @@
* Do not rely on this functionality; it will be deprecated in the future.
*/
public void emulateShiftHeld() {
- if (0 == mNativeClass) return; // client isn't initialized
- setUpSelectXY();
+ setUpSelect();
}
- private boolean commitCopy() {
+ /**
+ * @hide pending API council approval.
+ */
+ public void selectAll() {
+ if (0 == mNativeClass) return; // client isn't initialized
+ if (inFullScreenMode()) return;
+ if (!mSelectingText) setUpSelect();
+ nativeSelectAll();
+ mDrawSelectionPointer = false;
+ mExtendSelection = true;
+ invalidate();
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public boolean selectDialogIsUp() {
+ return mSelectingText;
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public void notifySelectDialogDismissed() {
+ mSelectingText = false;
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public void selectionDone() {
+ if (mSelectingText) {
+ getWebChromeClient().onSelectionDone(this);
+ invalidate(); // redraw without selection
+ notifySelectDialogDismissed();
+ }
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public boolean copySelection() {
boolean copiedSomething = false;
- if (mExtendSelection) {
- String selection = nativeGetSelection();
- if (selection != "") {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "commitCopy \"" + selection + "\"");
- }
- Toast.makeText(mContext
- , com.android.internal.R.string.text_copied
- , Toast.LENGTH_SHORT).show();
- copiedSomething = true;
- try {
- IClipboard clip = IClipboard.Stub.asInterface(
- ServiceManager.getService("clipboard"));
- clip.setClipboardText(selection);
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Clipboard failed", e);
- }
+ String selection = getSelection();
+ if (selection != "") {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "copySelection \"" + selection + "\"");
}
- mExtendSelection = false;
+ Toast.makeText(mContext
+ , com.android.internal.R.string.text_copied
+ , Toast.LENGTH_SHORT).show();
+ copiedSomething = true;
+ try {
+ IClipboard clip = IClipboard.Stub.asInterface(
+ ServiceManager.getService("clipboard"));
+ clip.setClipboardText(selection);
+ } catch (android.os.RemoteException e) {
+ Log.e(LOGTAG, "Clipboard failed", e);
+ }
}
- mShiftIsPressed = false;
invalidate(); // remove selection region and pointer
- if (mTouchMode == TOUCH_SELECT_MODE) {
- mTouchMode = TOUCH_INIT_MODE;
- }
return copiedSomething;
}
+ /**
+ * @hide pending API council approval.
+ */
+ public String getSelection() {
+ if (mNativeClass == 0) return "";
+ return nativeGetSelection();
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -4669,7 +4740,7 @@
private boolean shouldForwardTouchEvent() {
return mFullScreenHolder != null || (mForwardTouchEvents
- && mTouchMode != TOUCH_SELECT_MODE
+ && !mSelectingText
&& mPreventDefault != PREVENT_DEFAULT_IGNORE);
}
@@ -4757,16 +4828,6 @@
mTouchMode = TOUCH_DRAG_START_MODE;
mConfirmMove = true;
mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
- } else if (!inFullScreenMode() && mShiftIsPressed) {
- mSelectX = mScrollX + (int) x;
- mSelectY = mScrollY + (int) y;
- mTouchMode = TOUCH_SELECT_MODE;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
- }
- nativeMoveSelection(contentX, contentY, false);
- mTouchSelection = mExtendSelection = true;
- invalidate(); // draw the i-beam instead of the arrow
} else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
@@ -4791,6 +4852,14 @@
EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
(eventTime - mLastTouchUpTime), eventTime);
}
+ if (mSelectingText) {
+ mDrawSelectionPointer = false;
+ mSelectionStarted = nativeStartSelection(contentX, contentY);
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "select=" + contentX + "," + contentY);
+ }
+ invalidate();
+ }
}
// Trigger the link
if (mTouchMode == TOUCH_INIT_MODE
@@ -4882,17 +4951,17 @@
+ " mTouchMode = " + mTouchMode);
}
mVelocityTracker.addMovement(ev);
- if (mTouchMode != TOUCH_DRAG_MODE) {
- if (mTouchMode == TOUCH_SELECT_MODE) {
- mSelectX = mScrollX + (int) x;
- mSelectY = mScrollY + (int) y;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
- }
- nativeMoveSelection(contentX, contentY, true);
- invalidate();
- break;
+ if (mSelectingText && mSelectionStarted) {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
}
+ nativeExtendSelection(contentX, contentY);
+ invalidate();
+ break;
+ }
+
+ if (mTouchMode != TOUCH_DRAG_MODE) {
+
if (!mConfirmMove) {
break;
}
@@ -5059,10 +5128,6 @@
mTouchMode = TOUCH_DONE_MODE;
}
break;
- case TOUCH_SELECT_MODE:
- commitCopy();
- mTouchSelection = false;
- break;
case TOUCH_INIT_MODE: // tap
case TOUCH_SHORTPRESS_START_MODE:
case TOUCH_SHORTPRESS_MODE:
@@ -5093,6 +5158,13 @@
break;
}
} else {
+ if (mSelectingText) {
+ // tapping on selection or controls does nothing
+ if (!nativeHitSelection(contentX, contentY)) {
+ selectionDone();
+ }
+ break;
+ }
if (mTouchMode == TOUCH_INIT_MODE) {
mPrivateHandler.sendEmptyMessageDelayed(
RELEASE_SINGLE_TAP, ViewConfiguration
@@ -5263,8 +5335,10 @@
private float mTrackballRemainsY = 0.0f;
private int mTrackballXMove = 0;
private int mTrackballYMove = 0;
+ private boolean mSelectingText = false;
+ private boolean mSelectionStarted = false;
private boolean mExtendSelection = false;
- private boolean mTouchSelection = false;
+ private boolean mDrawSelectionPointer = false;
private static final int TRACKBALL_KEY_TIMEOUT = 1000;
private static final int TRACKBALL_TIMEOUT = 200;
private static final int TRACKBALL_WAIT = 100;
@@ -5303,10 +5377,8 @@
if (ev.getY() < 0) pageUp(true);
return true;
}
- boolean shiftPressed = mShiftIsPressed && (mNativeClass == 0
- || !nativeFocusIsPlugin());
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (shiftPressed) {
+ if (mSelectingText) {
return true; // discard press if copy in progress
}
mTrackballDown = true;
@@ -5331,11 +5403,13 @@
mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
mTrackballDown = false;
mTrackballUpTime = time;
- if (shiftPressed) {
+ if (mSelectingText) {
if (mExtendSelection) {
- commitCopy();
+ copySelection();
+ selectionDone();
} else {
mExtendSelection = true;
+ nativeSetExtendSelection();
invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
@@ -5402,8 +5476,7 @@
+ " yRate=" + yRate
);
}
- nativeMoveSelection(viewToContentX(mSelectX),
- viewToContentY(mSelectY), mExtendSelection);
+ nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY));
int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
: mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
: 0;
@@ -5469,7 +5542,16 @@
float yRate = mTrackballRemainsY * 1000 / elapsed;
int viewWidth = getViewWidth();
int viewHeight = getViewHeight();
- if (mShiftIsPressed && (mNativeClass == 0 || !nativeFocusIsPlugin())) {
+ if (mSelectingText) {
+ if (!mDrawSelectionPointer) {
+ // The last selection was made by touch, disabling drawing the
+ // selection pointer. Allow the trackball to adjust the
+ // position of the touch control.
+ mSelectX = contentToViewX(nativeSelectionX());
+ mSelectY = contentToViewY(nativeSelectionY());
+ mDrawSelectionPointer = mExtendSelection = true;
+ nativeSetExtendSelection();
+ }
moveSelection(scaleTrackballX(xRate, viewWidth),
scaleTrackballY(yRate, viewHeight));
mTrackballRemainsX = mTrackballRemainsY = 0;
@@ -7389,6 +7471,7 @@
private native void nativeDebugDump();
private native void nativeDestroy();
private native boolean nativeEvaluateLayersAnimations();
+ private native void nativeExtendSelection(int x, int y);
private native void nativeDrawExtras(Canvas canvas, int extra);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
@@ -7417,6 +7500,7 @@
private native boolean nativeHasCursorNode();
private native boolean nativeHasFocusNode();
private native void nativeHideCursor();
+ private native boolean nativeHitSelection(int x, int y);
private native String nativeImageURI(int x, int y);
private native void nativeInstrumentReport();
/* package */ native boolean nativeMoveCursorToNextTextInput();
@@ -7426,21 +7510,27 @@
private native boolean nativeMoveCursor(int keyCode, int count,
boolean noScroll);
private native int nativeMoveGeneration();
- private native void nativeMoveSelection(int x, int y,
- boolean extendSelection);
+ private native void nativeMoveSelection(int x, int y);
private native boolean nativePointInNavCache(int x, int y, int slop);
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
private native void nativeRecordButtons(boolean focused,
boolean pressed, boolean invalidate);
+ private native void nativeResetSelection();
+ private native void nativeSelectAll();
private native void nativeSelectBestAt(Rect rect);
+ private native int nativeSelectionX();
+ private native int nativeSelectionY();
+ private native int nativeFindIndex();
+ private native void nativeSetExtendSelection();
private native void nativeSetFindIsEmpty();
private native void nativeSetFindIsUp(boolean isUp);
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetRootLayer(int layer);
private native void nativeSetSelectionPointer(boolean set,
- float scale, int x, int y, boolean extendSelection);
+ float scale, int x, int y);
+ private native boolean nativeStartSelection(int x, int y);
private native void nativeSetSelectionRegion(boolean set);
private native Rect nativeSubtractLayers(Rect content);
private native int nativeTextGeneration();
@@ -7448,6 +7538,7 @@
// we always want to pass in our generation number.
private native void nativeUpdateCachedTextfield(String updatedText,
int generation);
+ private native boolean nativeWordSelection(int x, int y);
// return NO_LEFTEDGE means failure.
private static final int NO_LEFTEDGE = -1;
private native int nativeGetBlockLeftEdge(int x, int y, float scale);
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e15a520..3971487 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -76,6 +76,9 @@
* }
* </pre>
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-autocomplete.html">Auto Complete
+ * tutorial</a>.</p>
+ *
* @attr ref android.R.styleable#AutoCompleteTextView_completionHint
* @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
* @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 5e692d4..176233e 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -48,6 +48,9 @@
* }
* </pre>
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
+ *
* <p><strong>XML attributes</strong></p>
* <p>
* See {@link android.R.styleable#Button Button Attributes},
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index ff63a24..b89c2a9 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -41,6 +41,9 @@
* }
* }
* </pre>
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
*
* <p><strong>XML attributes</strong></p>
* <p>
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 1fc23ab..8aed454 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -37,6 +37,9 @@
/**
* A view for selecting a month / year / day based on a calendar like layout.
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date Picker
+ * tutorial</a>.</p>
+ *
* For a dialog using this view, see {@link android.app.DatePickerDialog}.
*/
@Widget
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 1532db1..0da68a4 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -35,6 +35,9 @@
/**
* EditText is a thin veneer over TextView that configures itself
* to be editable.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
* <p>
* <b>XML attributes</b>
* <p>
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index 1ed6b16..ccd37d3 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -46,6 +46,9 @@
* <p>
* Views given to the Gallery should use {@link Gallery.LayoutParams} as their
* layout parameters type.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-gallery.html">Gallery
+ * tutorial</a>.</p>
*
* @attr ref android.R.styleable#Gallery_animationDuration
* @attr ref android.R.styleable#Gallery_spacing
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index d2829db..2f86d75 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -31,6 +31,9 @@
/**
* A view that shows items in two-dimensional scrolling grid. The items in the
* grid come from the {@link ListAdapter} associated with this view.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Grid
+ * View tutorial</a>.</p>
*/
public class GridView extends AbsListView {
public static final int NO_STRETCH = 0;
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 5c05170..12a68db 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -62,6 +62,9 @@
* it will only be applied after {@code android:state_pressed} and {@code
* android:state_focused} have both evaluated false.</p>
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
+ *
* <p><strong>XML attributes</strong></p>
* <p>
* See {@link android.R.styleable#ImageView Button Attributes},
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 0525891..faf082d 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -37,6 +37,9 @@
* {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}.
* The default orientation is horizontal.
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-linearlayout.html">Linear Layout
+ * tutorial</a>.</p>
+ *
* <p>
* Also see {@link LinearLayout.LayoutParams android.widget.LinearLayout.LayoutParams}
* for layout attributes </p>
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index ec6dbb7..a3f8624 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -58,6 +58,9 @@
* A view that shows items in a vertically scrolling list. The items
* come from the {@link ListAdapter} associated with this view.
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-listview.html">List View
+ * tutorial</a>.</p>
+ *
* @attr ref android.R.styleable#ListView_entries
* @attr ref android.R.styleable#ListView_divider
* @attr ref android.R.styleable#ListView_dividerHeight
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index 14ec8c6..ebbe1cd 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -34,6 +34,9 @@
* a radio group, checking one radio button unchecks all the others.</p>
* </p>
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
+ *
* <p><strong>XML attributes</strong></p>
* <p>
* See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 1800c5a..28499d0 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -41,6 +41,9 @@
* <p>
* The secondary progress should not be modified by the client as it is used
* internally as the background for a fractionally filled star.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
*
* @attr ref android.R.styleable#RatingBar_numStars
* @attr ref android.R.styleable#RatingBar_rating
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 64cda49..a47359f 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -54,6 +54,9 @@
* {@link #ALIGN_PARENT_BOTTOM}.
* </p>
*
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-relativelayout.html">Relative
+ * Layout tutorial</a>.</p>
+ *
* <p>
* Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for
* layout attributes
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 2f6dd1e..8ddb06c 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -32,6 +32,9 @@
* A view that displays one child at a time and lets the user pick among them.
* The items in the Spinner come from the {@link Adapter} associated with
* this view.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
+ * tutorial</a>.</p>
*
* @attr ref android.R.styleable#Spinner_prompt
*/
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 02cd6a8..f720cee 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -40,6 +40,9 @@
* user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
* page. The individual elements are typically controlled using this container object, rather than
* setting values on the child elements themselves.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Tab Layout
+ * tutorial</a>.</p>
*/
public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 4e1b585..afae7ef 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -40,6 +40,9 @@
* handler, and manage callbacks. You might call this object to iterate the list
* of tabs, or to tweak the layout of the tab list, but most methods should be
* called on the containing TabHost object.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Tab Layout
+ * tutorial</a>.</p>
*
* @attr ref android.R.styleable#TabWidget_divider
* @attr ref android.R.styleable#TabWidget_tabStripEnabled
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index 73760ac..7f26e28 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -67,6 +67,9 @@
* <p>Although the typical child of a TableLayout is a TableRow, you can
* actually use any View subclass as a direct child of TableLayout. The View
* will be displayed as a single row that spans all the table columns.</p>
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-tablelayout.html">Table
+ * Layout tutorial</a>.</p>
*/
public class TableLayout extends LinearLayout {
private int[] mMaxWidths;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 27e0e94..73b1d6d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -61,6 +62,7 @@
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.method.ArrowKeyMovementMethod;
import android.text.method.DateKeyListener;
import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
@@ -89,10 +91,11 @@
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewDebug;
+import android.view.ViewGroup.LayoutParams;
import android.view.ViewRoot;
import android.view.ViewTreeObserver;
-import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
@@ -185,7 +188,7 @@
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
- static final String TAG = "TextView";
+ static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
private static int PRIORITY = 100;
@@ -321,6 +324,7 @@
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
+ @SuppressWarnings("deprecation")
public TextView(Context context,
AttributeSet attrs,
int defStyle) {
@@ -695,9 +699,9 @@
try {
setInputExtras(a.getResourceId(attr, 0));
} catch (XmlPullParserException e) {
- Log.w("TextView", "Failure reading input extras", e);
+ Log.w(LOG_TAG, "Failure reading input extras", e);
} catch (IOException e) {
- Log.w("TextView", "Failure reading input extras", e);
+ Log.w(LOG_TAG, "Failure reading input extras", e);
}
break;
}
@@ -714,7 +718,7 @@
}
if (inputMethod != null) {
- Class c;
+ Class<?> c;
try {
c = Class.forName(inputMethod.toString());
@@ -923,6 +927,8 @@
setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
+
+ prepareCursorControllers();
}
private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -1128,6 +1134,9 @@
setText(mText);
fixFocusableAndClickableSettings();
+
+ // SelectionModifierCursorController depends on canSelectText, which depends on mMovement
+ prepareCursorControllers();
}
private void fixFocusableAndClickableSettings() {
@@ -2335,6 +2344,7 @@
return str + "}";
}
+ @SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
@@ -2369,8 +2379,8 @@
int end = 0;
if (mText != null) {
- start = Selection.getSelectionStart(mText);
- end = Selection.getSelectionEnd(mText);
+ start = getSelectionStart();
+ end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
save = true;
@@ -2442,7 +2452,7 @@
restored = "(restored) ";
}
- Log.e("TextView", "Saved cursor position " + ss.selStart +
+ Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
"/" + ss.selEnd + " out of range for " + restored +
"text " + mText);
} else {
@@ -2694,6 +2704,9 @@
if (needEditableForNotification) {
sendAfterTextChanged((Editable) text);
}
+
+ // SelectionModifierCursorController depends on canSelectText, which depends on text
+ prepareCursorControllers();
}
/**
@@ -2756,6 +2769,7 @@
return mChars[off + mStart];
}
+ @Override
public String toString() {
return new String(mChars, mStart, mLength);
}
@@ -2981,7 +2995,7 @@
} else {
input = TextKeyListener.getInstance();
}
- mInputType = type;
+ setRawInputType(type);
if (direct) mInput = input;
else {
setKeyListenerOnly(input);
@@ -3198,7 +3212,7 @@
*
* @param create If true, the extras will be created if they don't already
* exist. Otherwise, null will be returned if none have been created.
- * @see #setInputExtras(int)View
+ * @see #setInputExtras(int)
* @see EditorInfo#extras
* @attr ref android.R.styleable#TextView_editorExtras
*/
@@ -3312,7 +3326,7 @@
private static class ErrorPopup extends PopupWindow {
private boolean mAbove = false;
- private TextView mView;
+ private final TextView mView;
ErrorPopup(TextView v, int width, int height) {
super(v, width, height);
@@ -3585,7 +3599,7 @@
}
private void invalidateCursor() {
- int where = Selection.getSelectionEnd(mText);
+ int where = getSelectionEnd();
invalidateCursor(where, where, where);
}
@@ -3661,7 +3675,18 @@
boolean changed = false;
if (mMovement != null) {
- int curs = Selection.getSelectionEnd(mText);
+ /* This code also provides auto-scrolling when a cursor is moved using a
+ * CursorController (insertion point or selection limits).
+ * For selection, ensure start or end is visible depending on controller's state.
+ */
+ int curs = getSelectionEnd();
+ if (mSelectionModifierCursorController != null) {
+ SelectionModifierCursorController selectionController =
+ (SelectionModifierCursorController) mSelectionModifierCursorController;
+ if (selectionController.isSelectionStartDragged()) {
+ curs = getSelectionStart();
+ }
+ }
/*
* TODO: This should really only keep the end in view if
@@ -3680,6 +3705,10 @@
changed = bringTextIntoView();
}
+ if (mShouldStartTextSelectionMode) {
+ startTextSelectionMode();
+ mShouldStartTextSelectionMode = false;
+ }
mPreDrawState = PREDRAW_DONE;
return !changed;
}
@@ -3954,8 +3983,8 @@
// XXX This is not strictly true -- a program could set the
// selection manually if it really wanted to.
if (mMovement != null && (isFocused() || isPressed())) {
- selStart = Selection.getSelectionStart(mText);
- selEnd = Selection.getSelectionEnd(mText);
+ selStart = getSelectionStart();
+ selEnd = getSelectionEnd();
if (mCursorVisible && selStart >= 0 && isEnabled()) {
if (mHighlightPath == null)
@@ -4061,6 +4090,13 @@
*/
canvas.restore();
+
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.draw(canvas);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.draw(canvas);
+ }
}
@Override
@@ -4345,6 +4381,8 @@
return super.onKeyUp(keyCode, event);
}
+ hideControllers();
+
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
/*
@@ -4475,8 +4513,8 @@
outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
- outAttrs.initialSelStart = Selection.getSelectionStart(mText);
- outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
+ outAttrs.initialSelStart = getSelectionStart();
+ outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
return ic;
}
@@ -4560,8 +4598,8 @@
outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
}
outText.startOffset = 0;
- outText.selectionStart = Selection.getSelectionStart(content);
- outText.selectionEnd = Selection.getSelectionEnd(content);
+ outText.selectionStart = getSelectionStart();
+ outText.selectionEnd = getSelectionEnd();
return true;
}
return false;
@@ -4578,7 +4616,7 @@
if (req != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
- if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
+ ims.mChangedStart + " end=" + ims.mChangedEnd
+ " delta=" + ims.mChangedDelta);
if (ims.mChangedStart < 0 && !contentChanged) {
@@ -4586,7 +4624,7 @@
}
if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
ims.mChangedDelta, ims.mTmpExtracted)) {
- if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
+ ims.mTmpExtracted.partialStartOffset
+ " end=" + ims.mTmpExtracted.partialEndOffset
+ ": " + ims.mTmpExtracted.text);
@@ -4736,7 +4774,7 @@
void updateAfterEdit() {
invalidate();
- int curs = Selection.getSelectionStart(mText);
+ int curs = getSelectionStart();
if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
Gravity.BOTTOM) {
@@ -4878,7 +4916,6 @@
w, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad);
}
- // Log.e("aaa", "Boring: " + mTransformed);
mSavedLayout = (BoringLayout) mLayout;
} else if (shouldEllipsize && boring.width <= w) {
@@ -4904,7 +4941,6 @@
mLayout = new StaticLayout(mTransformed, mTextPaint,
w, alignment, mSpacingMult, mSpacingAdd,
mIncludePad);
- // Log.e("aaa", "Boring but wide: " + mTransformed);
}
} else if (shouldEllipsize) {
mLayout = new StaticLayout(mTransformed,
@@ -5002,6 +5038,9 @@
}
}
}
+
+ // CursorControllers need a non-null mLayout
+ prepareCursorControllers();
}
private boolean compressText(float width) {
@@ -5473,7 +5512,7 @@
// FIXME: Is it okay to truncate this, or should we round?
final int x = (int)mLayout.getPrimaryHorizontal(offset);
final int top = mLayout.getLineTop(line);
- final int bottom = mLayout.getLineTop(line+1);
+ final int bottom = mLayout.getLineTop(line + 1);
int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
@@ -5610,8 +5649,8 @@
// viewport coordinates, but requestRectangleOnScreen()
// is in terms of content coordinates.
- Rect r = new Rect();
- getInterestingRect(r, x, top, bottom, line);
+ Rect r = new Rect(x, top, x + 1, bottom);
+ getInterestingRect(r, line);
r.offset(mScrollX, mScrollY);
if (requestRectangleOnScreen(r)) {
@@ -5627,13 +5666,15 @@
* to the user. This will not move the cursor if it represents more than
* one character (a selection range). This will only work if the
* TextView contains spannable text; otherwise it will do nothing.
+ *
+ * @return True if the cursor was actually moved, false otherwise.
*/
public boolean moveCursorToVisibleOffset() {
if (!(mText instanceof Spannable)) {
return false;
}
- int start = Selection.getSelectionStart(mText);
- int end = Selection.getSelectionEnd(mText);
+ int start = getSelectionStart();
+ int end = getSelectionEnd();
if (start != end) {
return false;
}
@@ -5643,7 +5684,7 @@
int line = mLayout.getLineForOffset(start);
final int top = mLayout.getLineTop(line);
- final int bottom = mLayout.getLineTop(line+1);
+ final int bottom = mLayout.getLineTop(line + 1);
final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
int vslack = (bottom - top) / 2;
if (vslack > vspace / 4)
@@ -5689,22 +5730,28 @@
}
}
- private void getInterestingRect(Rect r, int h, int top, int bottom,
- int line) {
+ private void getInterestingRect(Rect r, int line) {
+ convertFromViewportToContentCoordinates(r);
+
+ // Rectangle can can be expanded on first and last line to take
+ // padding into account.
+ // TODO Take left/right padding into account too?
+ if (line == 0) r.top -= getExtendedPaddingTop();
+ if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
+ }
+
+ private void convertFromViewportToContentCoordinates(Rect r) {
int paddingTop = getExtendedPaddingTop();
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
paddingTop += getVerticalOffset(false);
}
- top += paddingTop;
- bottom += paddingTop;
- h += getCompoundPaddingLeft();
+ r.top += paddingTop;
+ r.bottom += paddingTop;
- if (line == 0)
- top -= getExtendedPaddingTop();
- if (line == mLayout.getLineCount() - 1)
- bottom += getExtendedPaddingBottom();
+ int paddingLeft = getCompoundPaddingLeft();
+ r.left += paddingLeft;
+ r.right += paddingLeft;
- r.set(h, top, h+1, bottom);
r.offset(-mScrollX, -mScrollY);
}
@@ -5872,6 +5919,9 @@
} else if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
+
+ // InsertionPointCursorController depends on mCursorVisible
+ prepareCursorControllers();
}
private boolean canMarquee() {
@@ -5930,7 +5980,7 @@
private final WeakReference<TextView> mView;
private byte mStatus = MARQUEE_STOPPED;
- private float mScrollUnit;
+ private final float mScrollUnit;
private float mMaxScroll;
float mMaxFadeScroll;
private float mGhostStart;
@@ -5942,7 +5992,7 @@
Marquee(TextView v) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
- mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
+ mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
mView = new WeakReference<TextView>(v);
}
@@ -6175,6 +6225,7 @@
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
+ hideControllers();
}
/**
@@ -6286,7 +6337,7 @@
}
}
} else {
- if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
+ oldStart + "-" + oldEnd + ","
+ newStart + "-" + newEnd + what);
ims.mContentChanged = true;
@@ -6302,7 +6353,7 @@
public void beforeTextChanged(CharSequence buffer, int start,
int before, int after) {
- if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
if (AccessibilityManager.getInstance(mContext).isEnabled()
@@ -6315,7 +6366,7 @@
public void onTextChanged(CharSequence buffer, int start,
int before, int after) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
TextView.this.handleTextChanged(buffer, start, before, after);
@@ -6328,7 +6379,7 @@
}
public void afterTextChanged(Editable buffer) {
- if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
TextView.this.sendAfterTextChanged(buffer);
if (MetaKeyKeyListener.getMetaState(buffer,
@@ -6339,19 +6390,19 @@
public void onSpanChanged(Spannable buf,
Object what, int s, int e, int st, int en) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
+ " st=" + st + " en=" + en + " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, s, st, e, en);
}
public void onSpanAdded(Spannable buf, Object what, int s, int e) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
+ " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, -1, s, -1, e);
}
public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
+ " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, s, -1, e, -1);
}
@@ -6455,12 +6506,22 @@
if (mError != null) {
showError();
}
+
+ // We cannot start the selection mode immediately. The layout may be null here and is
+ // needed by the cursor controller. Layout creation is deferred up to drawing. The
+ // selection action mode will be started in onPreDraw().
+ if (selStart != selEnd) {
+ mShouldStartTextSelectionMode = true;
+ }
} else {
if (mError != null) {
hideError();
}
// Don't leave us in the middle of a batch edit.
onEndBatchEdit();
+
+ hideInsertionPointCursorController();
+ terminateTextSelectionMode();
}
startStopMarquee(focused);
@@ -6527,24 +6588,52 @@
}
class CommitSelectionReceiver extends ResultReceiver {
- int mNewStart;
- int mNewEnd;
+ private final int mPrevStart, mPrevEnd;
+ private final int mNewStart, mNewEnd;
- CommitSelectionReceiver() {
+ public CommitSelectionReceiver(int mPrevStart, int mPrevEnd, int mNewStart, int mNewEnd) {
super(getHandler());
+ this.mPrevStart = mPrevStart;
+ this.mPrevEnd = mPrevEnd;
+ this.mNewStart = mNewStart;
+ this.mNewEnd = mNewEnd;
}
+ @Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode != InputMethodManager.RESULT_SHOWN) {
- final int len = mText.length();
- if (mNewStart > len) {
- mNewStart = len;
+ int start = mNewStart;
+ int end = mNewEnd;
+
+ // Move the cursor to the new position, unless this tap was actually
+ // use to show the IMM. Leave cursor unchanged in that case.
+ if (resultCode == InputMethodManager.RESULT_SHOWN) {
+ start = mPrevStart;
+ end = mPrevEnd;
+ } else {
+ if ((mPrevStart != mPrevEnd) && (start == end)) {
+ if ((start >= mPrevStart) && (start <= mPrevEnd)) {
+ // Tapping inside the selection does nothing
+ Selection.setSelection((Spannable) mText, mPrevStart, mPrevEnd);
+ return;
+ } else {
+ // Tapping outside stops selection mode, if any
+ stopTextSelectionMode();
+ }
}
- if (mNewEnd > len) {
- mNewEnd = len;
+
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.show();
}
- Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
}
+
+ final int len = mText.length();
+ if (start > len) {
+ start = len;
+ }
+ if (end > len) {
+ end = len;
+ }
+ Selection.setSelection((Spannable)mText, start, end);
}
}
@@ -6557,7 +6646,7 @@
mTouchFocusSelected = false;
mScrolled = false;
}
-
+
final boolean superResult = super.onTouchEvent(event);
/*
@@ -6572,42 +6661,37 @@
if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
- boolean handled = false;
+ int oldSelStart = getSelectionStart();
+ int oldSelEnd = getSelectionEnd();
- int oldSelStart = Selection.getSelectionStart(mText);
- int oldSelEnd = Selection.getSelectionEnd(mText);
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.onTouchEvent(event);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.onTouchEvent(event);
+ }
+
+ boolean handled = false;
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- if (mText instanceof Editable && onCheckIsTextEditor()) {
+ if (isTextEditable()) {
if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- // This is going to be gross... if tapping on the text view
- // causes the IME to be displayed, we don't want the selection
- // to change. But the selection has already changed, and
- // we won't know right away whether the IME is getting
- // displayed, so...
+ final int newSelStart = getSelectionStart();
+ final int newSelEnd = getSelectionEnd();
- int newSelStart = Selection.getSelectionStart(mText);
- int newSelEnd = Selection.getSelectionEnd(mText);
CommitSelectionReceiver csr = null;
if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
- csr = new CommitSelectionReceiver();
- csr.mNewStart = newSelStart;
- csr.mNewEnd = newSelEnd;
+ csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd,
+ newSelStart, newSelEnd);
}
- if (imm.showSoftInput(this, 0, csr) && csr != null) {
- // The IME might get shown -- revert to the old
- // selection, and change to the new when we finally
- // find out of it is okay.
- Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
- handled = true;
- }
+ handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
}
}
@@ -6619,6 +6703,34 @@
return superResult;
}
+ private void prepareCursorControllers() {
+ // TODO Add an extra android:cursorController flag to disable the controller?
+ if (mCursorVisible && mLayout != null) {
+ if (mInsertionPointCursorController == null) {
+ mInsertionPointCursorController = new InsertionPointCursorController();
+ }
+ } else {
+ mInsertionPointCursorController = null;
+ }
+
+ if (canSelectText() && mLayout != null) {
+ if (mSelectionModifierCursorController == null) {
+ mSelectionModifierCursorController = new SelectionModifierCursorController();
+ }
+ } else {
+ // Stop selection mode if the controller becomes unavailable.
+ stopTextSelectionMode();
+ mSelectionModifierCursorController = null;
+ }
+ }
+
+ /**
+ * @return True iff this TextView contains a text that can be edited.
+ */
+ private boolean isTextEditable() {
+ return mText instanceof Editable && onCheckIsTextEditor();
+ }
+
/**
* Returns true, only while processing a touch gesture, if the initial
* touch down event caused focus to move to the text view and as a result
@@ -6652,7 +6764,7 @@
}
private static class Blink extends Handler implements Runnable {
- private WeakReference<TextView> mView;
+ private final WeakReference<TextView> mView;
private boolean mCancelled;
public Blink(TextView v) {
@@ -6669,8 +6781,8 @@
TextView tv = mView.get();
if (tv != null && tv.isFocused()) {
- int st = Selection.getSelectionStart(tv.mText);
- int en = Selection.getSelectionEnd(tv.mText);
+ int st = tv.getSelectionStart();
+ int en = tv.getSelectionEnd();
if (st == en && st >= 0 && en >= 0) {
if (tv.mLayout != null) {
@@ -6851,21 +6963,16 @@
}
private boolean canSelectAll() {
- if (mText instanceof Spannable && mText.length() != 0 &&
- mMovement != null && mMovement.canSelectArbitrarily()) {
- return true;
- }
-
- return false;
+ return canSelectText() && mText.length() != 0;
}
private boolean canSelectText() {
- if (mText instanceof Spannable && mText.length() != 0 &&
- mMovement != null && mMovement.canSelectArbitrarily()) {
- return true;
- }
-
- return false;
+ // prepareCursorController() relies on this method.
+ // If you change this condition, make sure prepareCursorController is called anywhere
+ // the value of this condition might be changed.
+ return (mText instanceof Spannable &&
+ mMovement != null &&
+ mMovement.canSelectArbitrarily());
}
private boolean canCut() {
@@ -6895,23 +7002,23 @@
}
private boolean canPaste() {
- if (mText instanceof Editable && mInput != null &&
- getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
- ClipboardManager clip = (ClipboardManager)getContext()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- if (clip.hasText()) {
- return true;
- }
- }
-
- return false;
+ return (mText instanceof Editable &&
+ mInput != null &&
+ getSelectionStart() >= 0 &&
+ getSelectionEnd() >= 0 &&
+ ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
+ hasText());
}
/**
- * Returns a word to add to the dictionary from the context menu,
- * or null if there is no cursor or no word at the cursor.
+ * Returns the offsets delimiting the 'word' located at position offset.
+ *
+ * @param offset An offset in the text.
+ * @return The offsets for the start and end of the word located at <code>offset</code>.
+ * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
+ * Returns a negative value if no valid word was found.
*/
- private String getWordForDictionary() {
+ private long getWordLimitsAt(int offset) {
/*
* Quick return if the input type is one where adding words
* to the dictionary doesn't make any sense.
@@ -6920,7 +7027,7 @@
if (klass == InputType.TYPE_CLASS_NUMBER ||
klass == InputType.TYPE_CLASS_PHONE ||
klass == InputType.TYPE_CLASS_DATETIME) {
- return null;
+ return -1;
}
int variation = mInputType & InputType.TYPE_MASK_VARIATION;
@@ -6929,13 +7036,13 @@
variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- return null;
+ return -1;
}
- int end = getSelectionEnd();
+ int end = offset;
if (end < 0) {
- return null;
+ return -1;
}
int start = end;
@@ -6969,6 +7076,14 @@
}
}
+ if (start == end) {
+ return -1;
+ }
+
+ if (end - start > 48) {
+ return -1;
+ }
+
boolean hasLetter = false;
for (int i = start; i < end; i++) {
if (Character.isLetter(mTransformed.charAt(i))) {
@@ -6976,21 +7091,61 @@
break;
}
}
+
if (!hasLetter) {
- return null;
+ return -1;
}
- if (start == end) {
- return null;
- }
-
- if (end - start > 48) {
- return null;
- }
-
- return TextUtils.substring(mTransformed, start, end);
+ // Two ints packed in a long
+ return (((long) start) << 32) | end;
}
+
+ private void selectCurrentWord() {
+ // In case selection mode is started after an orientation change or after a select all,
+ // use the current selection instead of creating one
+ if (hasSelection()) {
+ return;
+ }
+ int selectionStart, selectionEnd;
+
+ SelectionModifierCursorController selectionModifierCursorController =
+ ((SelectionModifierCursorController) mSelectionModifierCursorController);
+ int minOffset = selectionModifierCursorController.getMinTouchOffset();
+ int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+
+ long wordLimits = getWordLimitsAt(minOffset);
+ if (wordLimits >= 0) {
+ selectionStart = (int) (wordLimits >>> 32);
+ } else {
+ selectionStart = Math.max(minOffset - 5, 0);
+ }
+
+ wordLimits = getWordLimitsAt(maxOffset);
+ if (wordLimits >= 0) {
+ selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
+ } else {
+ selectionEnd = Math.min(maxOffset + 5, mText.length());
+ }
+
+ Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+ }
+
+ private String getWordForDictionary() {
+ int offset = ((SelectionModifierCursorController) mSelectionModifierCursorController).
+ getMinTouchOffset();
+
+ long wordLimits = getWordLimitsAt(offset);
+ if (wordLimits >= 0) {
+ int start = (int) (wordLimits >>> 32);
+ int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
+ return mTransformed.subSequence(start, end).toString();
+ } else {
+ return null;
+ }
+
+ }
+
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
if (!isShown()) {
@@ -7032,114 +7187,98 @@
super.onCreateContextMenu(menu);
boolean added = false;
- if (!isFocused()) {
- if (isFocusable() && mInput != null) {
- if (canCopy()) {
- MenuHandler handler = new MenuHandler();
- int name = com.android.internal.R.string.copyAll;
+ if (mIsInTextSelectionMode) {
+ MenuHandler handler = new MenuHandler();
+
+ if (canCut()) {
+ menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('x');
+ }
- menu.add(0, ID_COPY, 0, name).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('c');
- menu.setHeaderTitle(com.android.internal.R.string.
- editTextMenuTitle);
+ if (canCopy()) {
+ menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('c');
+ }
+
+ if (canPaste()) {
+ menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('v');
+ }
+
+ menu.add(0, ID_STOP_SELECTING_TEXT, 0, com.android.internal.R.string.stopSelectingText).
+ setOnMenuItemClickListener(handler);
+
+ added = true;
+ } else {
+ /*
+ if (!isFocused()) {
+ if (isFocusable() && mInput != null) {
+ if (canCopy()) {
+ MenuHandler handler = new MenuHandler();
+ menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('c');
+ menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
+ }
+ }
+
+ //return;
+ }
+ */
+ MenuHandler handler = new MenuHandler();
+
+ if (canSelectText()) {
+ menu.add(0, ID_START_SELECTING_TEXT, 0, com.android.internal.R.string.selectText).
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
+
+ if (canSelectAll()) {
+ menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('a');
+ added = true;
+ }
+
+ if (mText instanceof Spanned) {
+ int selStart = getSelectionStart();
+ int selEnd = getSelectionEnd();
+
+ int min = Math.min(selStart, selEnd);
+ int max = Math.max(selStart, selEnd);
+
+ URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+ URLSpan.class);
+ if (urls.length == 1) {
+ menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
+ setOnMenuItemClickListener(handler);
+ added = true;
}
}
+
+ if (canPaste()) {
+ menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('v');
+ }
- return;
- }
-
- MenuHandler handler = new MenuHandler();
-
- if (canSelectAll()) {
- menu.add(0, ID_SELECT_ALL, 0,
- com.android.internal.R.string.selectAll).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('a');
- added = true;
- }
-
- boolean selection = getSelectionStart() != getSelectionEnd();
-
- if (canSelectText()) {
- if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
- menu.add(0, ID_STOP_SELECTING_TEXT, 0,
- com.android.internal.R.string.stopSelectingText).
- setOnMenuItemClickListener(handler);
- added = true;
- } else {
- menu.add(0, ID_START_SELECTING_TEXT, 0,
- com.android.internal.R.string.selectText).
- setOnMenuItemClickListener(handler);
+ if (isInputMethodTarget()) {
+ menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
+ setOnMenuItemClickListener(handler);
added = true;
}
- }
- if (canCut()) {
- int name;
- if (selection) {
- name = com.android.internal.R.string.cut;
- } else {
- name = com.android.internal.R.string.cutAll;
- }
-
- menu.add(0, ID_CUT, 0, name).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('x');
- added = true;
- }
-
- if (canCopy()) {
- int name;
- if (selection) {
- name = com.android.internal.R.string.copy;
- } else {
- name = com.android.internal.R.string.copyAll;
- }
-
- menu.add(0, ID_COPY, 0, name).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('c');
- added = true;
- }
-
- if (canPaste()) {
- menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('v');
- added = true;
- }
-
- if (mText instanceof Spanned) {
- int selStart = getSelectionStart();
- int selEnd = getSelectionEnd();
-
- int min = Math.min(selStart, selEnd);
- int max = Math.max(selStart, selEnd);
-
- URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
- URLSpan.class);
- if (urls.length == 1) {
- menu.add(0, ID_COPY_URL, 0,
- com.android.internal.R.string.copyUrl).
- setOnMenuItemClickListener(handler);
- added = true;
- }
- }
-
- if (isInputMethodTarget()) {
- menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
- setOnMenuItemClickListener(handler);
- added = true;
- }
-
- String word = getWordForDictionary();
- if (word != null) {
- menu.add(1, ID_ADD_TO_DICTIONARY, 0,
+ String word = getWordForDictionary();
+ if (word != null) {
+ menu.add(1, ID_ADD_TO_DICTIONARY, 0,
getContext().getString(com.android.internal.R.string.addToDictionary, word)).
- setOnMenuItemClickListener(handler);
- added = true;
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
}
if (added) {
@@ -7156,6 +7295,7 @@
return imm != null && imm.isActive(this);
}
+ // Context menu entries
private static final int ID_SELECT_ALL = android.R.id.selectAll;
private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
@@ -7181,22 +7321,15 @@
* or {@link android.R.id#switchInputMethod}.
*/
public boolean onTextContextMenuItem(int id) {
- int selStart = getSelectionStart();
- int selEnd = getSelectionEnd();
+ int min = 0;
+ int max = mText.length();
- if (!isFocused()) {
- selStart = 0;
- selEnd = mText.length();
- }
+ if (isFocused()) {
+ final int selStart = getSelectionStart();
+ final int selEnd = getSelectionEnd();
- int min = Math.min(selStart, selEnd);
- int max = Math.max(selStart, selEnd);
-
- if (min < 0) {
- min = 0;
- }
- if (max < 0) {
- max = 0;
+ min = Math.max(0, Math.min(selStart, selEnd));
+ max = Math.max(0, Math.max(selStart, selEnd));
}
ClipboardManager clip = (ClipboardManager)getContext()
@@ -7204,63 +7337,44 @@
switch (id) {
case ID_SELECT_ALL:
- Selection.setSelection((Spannable) mText, 0,
- mText.length());
+ Selection.setSelection((Spannable) mText, 0, mText.length());
+ startTextSelectionMode();
return true;
case ID_START_SELECTING_TEXT:
- MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
+ startTextSelectionMode();
return true;
case ID_STOP_SELECTING_TEXT:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
- Selection.setSelection((Spannable) mText, getSelectionEnd());
+ stopTextSelectionMode();
return true;
- case ID_CUT:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
- if (min == max) {
- min = 0;
- max = mText.length();
- }
-
+ case ID_CUT:
clip.setText(mTransformed.subSequence(min, max));
((Editable) mText).delete(min, max);
+ stopTextSelectionMode();
return true;
case ID_COPY:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
- if (min == max) {
- min = 0;
- max = mText.length();
- }
-
clip.setText(mTransformed.subSequence(min, max));
+ stopTextSelectionMode();
return true;
case ID_PASTE:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
CharSequence paste = clip.getText();
if (paste != null) {
Selection.setSelection((Spannable) mText, max);
((Editable) mText).replace(min, max, paste);
+ stopTextSelectionMode();
}
-
return true;
case ID_COPY_URL:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
- URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
- URLSpan.class);
+ URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
clip.setText(urls[0].getURL());
}
-
return true;
case ID_SWITCH_INPUT_METHOD:
@@ -7279,13 +7393,13 @@
i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
}
-
return true;
}
return false;
}
+ @Override
public boolean performLongClick() {
if (super.performLongClick()) {
mEatTouchRelease = true;
@@ -7295,7 +7409,572 @@
return false;
}
- @ViewDebug.ExportedProperty(category = "text")
+ private void startTextSelectionMode() {
+ if (mSelectionModifierCursorController == null) {
+ Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
+ return;
+ }
+
+ if (!requestFocus()) {
+ return;
+ }
+
+ selectCurrentWord();
+ mSelectionModifierCursorController.show();
+ mIsInTextSelectionMode = true;
+ }
+
+ /**
+ * Same as {@link #stopTextSelectionMode()}, except that there is no cursor controller
+ * fade out animation. Needed since the drawable and their alpha values are shared by all
+ * TextViews. Switching from one TextView to another would fade the cursor controllers in the
+ * new one otherwise.
+ */
+ private void terminateTextSelectionMode() {
+ stopTextSelectionMode();
+ if (mSelectionModifierCursorController != null) {
+ SelectionModifierCursorController selectionModifierCursorController =
+ (SelectionModifierCursorController) mSelectionModifierCursorController;
+ selectionModifierCursorController.cancelFadeOutAnimation();
+ }
+ }
+
+ private void stopTextSelectionMode() {
+ if (mIsInTextSelectionMode) {
+ Selection.setSelection((Spannable) mText, getSelectionEnd());
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.hide();
+ }
+
+ mIsInTextSelectionMode = false;
+ }
+ }
+
+ /**
+ * A CursorController instance can be used to control a cursor in the text.
+ *
+ * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
+ * and send them to this object instead of the cursor.
+ */
+ public interface CursorController {
+ /* Cursor fade-out animation duration, in milliseconds. */
+ static final int FADE_OUT_DURATION = 400;
+
+ /**
+ * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
+ * See also {@link #hide()}.
+ */
+ public void show();
+
+ /**
+ * Hide the cursor controller from screen.
+ * See also {@link #show()}.
+ */
+ public void hide();
+
+ /**
+ * Update the controller's position.
+ */
+ public void updatePosition(int offset);
+
+ /**
+ * The controller and the cursor's positions can be link by a fixed offset,
+ * computed when the controller is touched, and then maintained as it moves
+ * @return Horizontal offset between the controller and the cursor.
+ */
+ public float getOffsetX();
+
+ /**
+ * @return Vertical offset between the controller and the cursor.
+ */
+ public float getOffsetY();
+
+ /**
+ * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
+ * a chance to become active and/or visible.
+ * @param event The touch event
+ */
+ public void onTouchEvent(MotionEvent event);
+
+ /**
+ * Draws a visual representation of the controller on the canvas.
+ *
+ * Called at the end of {@link #draw(Canvas)}, in the content coordinates system.
+ * @param canvas The Canvas used by this TextView.
+ */
+ public void draw(Canvas canvas);
+ }
+
+ private class Handle {
+ Drawable mDrawable;
+ // Vertical extension of the touch region
+ int mTopExtension, mBottomExtension;
+ // Position of the virtual finger position on screen
+ int mHopSpotVertcalPosition;
+
+ Handle(Drawable drawable) {
+ mDrawable = drawable;
+ }
+
+ void positionAtCursor(final int offset, boolean bottom) {
+ final int drawableWidth = mDrawable.getIntrinsicWidth();
+ final int drawableHeight = mDrawable.getIntrinsicHeight();
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineTop = mLayout.getLineTop(line);
+ final int lineBottom = mLayout.getLineBottom(line);
+
+ mHopSpotVertcalPosition = lineTop + (bottom ? (3 * (lineBottom - lineTop)) / 4 :
+ (lineBottom - lineTop) / 4);
+
+ final Rect bounds = sCursorControllerTempRect;
+ bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0);
+ bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2;
+
+ mTopExtension = bottom ? 0 : drawableHeight / 2;
+ mBottomExtension = drawableHeight;
+
+ // Extend touch region up when editing the last line of text (or a single line) so that
+ // it is easier to grab.
+ if (line == mLayout.getLineCount() - 1) {
+ mTopExtension = (lineBottom - lineTop) - drawableHeight / 2;
+ }
+
+ bounds.right = bounds.left + drawableWidth;
+ bounds.bottom = bounds.top + drawableHeight;
+
+ int boundTopBefore = bounds.top;
+ convertFromViewportToContentCoordinates(bounds);
+ mHopSpotVertcalPosition += bounds.top - boundTopBefore;
+ mDrawable.setBounds(bounds);
+ postInvalidate();
+ }
+
+ boolean hasFingerOn(float x, float y) {
+ // Simulate a 'fat finger' to ease grabbing of the controller.
+ // Expands according to controller image size instead of using dip distance.
+ // Assumes controller imager has a sensible size, proportionnal to screen density.
+ final int drawableWidth = mDrawable.getIntrinsicWidth();
+ final Rect fingerRect = sCursorControllerTempRect;
+ fingerRect.set((int) (x - drawableWidth / 2.0),
+ (int) (y - mBottomExtension),
+ (int) (x + drawableWidth / 2.0),
+ (int) (y + mTopExtension));
+ return Rect.intersects(mDrawable.getBounds(), fingerRect);
+ }
+
+ void postInvalidate() {
+ final Rect bounds = mDrawable.getBounds();
+ TextView.this.postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ void postInvalidateDelayed(long delay) {
+ final Rect bounds = mDrawable.getBounds();
+ TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top,
+ bounds.right, bounds.bottom);
+ }
+ }
+
+ class InsertionPointCursorController implements CursorController {
+ private static final int DELAY_BEFORE_FADE_OUT = 2100;
+
+ // Whether or not the cursor control is currently visible
+ private boolean mIsVisible = false;
+ // Starting time of the fade timer
+ private long mFadeOutTimerStart;
+ // The cursor controller image
+ private final Handle mHandle;
+ // Used to detect a tap (vs drag) on the controller
+ private long mOnDownTimerStart;
+ // Offset between finger hot point on cursor controller and actual cursor
+ private float mOffsetX, mOffsetY;
+
+ InsertionPointCursorController() {
+ Resources res = mContext.getResources();
+ mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+ }
+
+ public void show() {
+ updateDrawablePosition();
+ // Has to be done after updatePosition, so that previous position invalidate
+ // in only done if necessary.
+ mIsVisible = true;
+ }
+
+ public void hide() {
+ if (mIsVisible) {
+ long time = System.currentTimeMillis();
+ // Start fading out, only if not already in progress
+ if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) {
+ mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT;
+ mHandle.postInvalidate();
+ }
+ }
+ }
+
+ public void draw(Canvas canvas) {
+ if (mIsVisible) {
+ int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+ if (time <= DELAY_BEFORE_FADE_OUT) {
+ mHandle.postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time);
+ } else {
+ time -= DELAY_BEFORE_FADE_OUT;
+ if (time <= FADE_OUT_DURATION) {
+ final int alpha = (int)
+ ((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION);
+ mHandle.mDrawable.setAlpha(alpha);
+ mHandle.postInvalidateDelayed(30);
+ } else {
+ mHandle.mDrawable.setAlpha(0);
+ mIsVisible = false;
+ }
+ }
+ mHandle.mDrawable.draw(canvas);
+ }
+ }
+
+ public void updatePosition(int offset) {
+ if (offset == getSelectionStart()) {
+ return; // No change, no need to redraw
+ }
+ Selection.setSelection((Spannable) mText, offset);
+ updateDrawablePosition();
+ }
+
+ private void updateDrawablePosition() {
+ if (mIsVisible) {
+ // Clear previous cursor controller before bounds are updated
+ mHandle.postInvalidate();
+ }
+
+ final int offset = getSelectionStart();
+
+ if (offset < 0) {
+ // Should never happen, safety check.
+ Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
+ mIsVisible = false;
+ return;
+ }
+
+ mHandle.positionAtCursor(offset, true);
+
+ mFadeOutTimerStart = System.currentTimeMillis();
+ mHandle.mDrawable.setAlpha(255);
+ }
+
+ public void onTouchEvent(MotionEvent event) {
+ if (isFocused() && isTextEditable() && mIsVisible) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN : {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (mHandle.hasFingerOn(x, y)) {
+ show();
+
+ if (mMovement instanceof ArrowKeyMovementMethod) {
+ ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+ }
+
+ if (mParent != null) {
+ // Prevent possible scrollView parent from scrolling, so that
+ // we can use auto-scrolling.
+ mParent.requestDisallowInterceptTouchEvent(true);
+ }
+
+ final Rect bounds = mHandle.mDrawable.getBounds();
+ mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+ mOffsetY = mHandle.mHopSpotVertcalPosition - y;
+
+ mOnDownTimerStart = event.getEventTime();
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP : {
+ int time = (int) (event.getEventTime() - mOnDownTimerStart);
+
+ if (time <= ViewConfiguration.getTapTimeout()) {
+ // A tap on the controller (not a drag) will move the cursor
+ int offset = getOffset((int) event.getX(), (int) event.getY());
+ Selection.setSelection((Spannable) mText, offset);
+
+ // Modified by cancelLongPress and prevents the cursor from changing
+ mScrolled = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+ }
+
+ class SelectionModifierCursorController implements CursorController {
+ // Whether or not the selection controls are currently visible
+ private boolean mIsVisible = false;
+ // Whether that start or the end of selection controller is dragged
+ private boolean mStartIsDragged = false;
+ // Starting time of the fade timer
+ private long mFadeOutTimerStart;
+ // The cursor controller images
+ private final Handle mStartHandle, mEndHandle;
+ // Offset between finger hot point on active cursor controller and actual cursor
+ private float mOffsetX, mOffsetY;
+ // The offsets of that last touch down event. Remembered to start selection there.
+ private int mMinTouchOffset, mMaxTouchOffset;
+
+ SelectionModifierCursorController() {
+ Resources res = mContext.getResources();
+ mStartHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+ mEndHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+ }
+
+ public void show() {
+ updateDrawablesPositions();
+ // Has to be done after updatePosition, so that previous position invalidate
+ // in only done if necessary.
+ mIsVisible = true;
+ mFadeOutTimerStart = -1;
+ hideInsertionPointCursorController();
+ }
+
+ public void hide() {
+ if (mIsVisible && (mFadeOutTimerStart < 0)) {
+ mFadeOutTimerStart = System.currentTimeMillis();
+ mStartHandle.postInvalidate();
+ mEndHandle.postInvalidate();
+ }
+ }
+
+ public void cancelFadeOutAnimation() {
+ mIsVisible = false;
+ mStartHandle.postInvalidate();
+ mEndHandle.postInvalidate();
+ }
+
+ public void draw(Canvas canvas) {
+ if (mIsVisible) {
+ if (mFadeOutTimerStart >= 0) {
+ int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+ if (time <= FADE_OUT_DURATION) {
+ final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
+ mStartHandle.mDrawable.setAlpha(alpha);
+ mEndHandle.mDrawable.setAlpha(alpha);
+ mStartHandle.postInvalidateDelayed(30);
+ mEndHandle.postInvalidateDelayed(30);
+ } else {
+ mStartHandle.mDrawable.setAlpha(0);
+ mEndHandle.mDrawable.setAlpha(0);
+ mIsVisible = false;
+ }
+ }
+ mStartHandle.mDrawable.draw(canvas);
+ mEndHandle.mDrawable.draw(canvas);
+ }
+ }
+
+ public void updatePosition(int offset) {
+ int selectionStart = getSelectionStart();
+ int selectionEnd = getSelectionEnd();
+
+ // Handle the case where start and end are swapped, making sure start <= end
+ if (mStartIsDragged) {
+ if (offset <= selectionEnd) {
+ if (selectionStart == offset) {
+ return; // no change, no need to redraw;
+ }
+ selectionStart = offset;
+ } else {
+ selectionStart = selectionEnd;
+ selectionEnd = offset;
+ mStartIsDragged = false;
+ }
+ } else {
+ if (offset >= selectionStart) {
+ if (selectionEnd == offset) {
+ return; // no change, no need to redraw;
+ }
+ selectionEnd = offset;
+ } else {
+ selectionEnd = selectionStart;
+ selectionStart = offset;
+ mStartIsDragged = true;
+ }
+ }
+
+ Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+ updateDrawablesPositions();
+ }
+
+ private void updateDrawablesPositions() {
+ if (mIsVisible) {
+ // Clear previous cursor controller before bounds are updated
+ mStartHandle.postInvalidate();
+ mEndHandle.postInvalidate();
+ }
+
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ if ((selectionStart < 0) || (selectionEnd < 0)) {
+ // Should never happen, safety check.
+ Log.w(LOG_TAG, "Update selection controller position called with no cursor");
+ mIsVisible = false;
+ return;
+ }
+
+ boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) == mLayout.getLineForOffset(selectionEnd);
+ mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
+ mEndHandle.positionAtCursor(selectionEnd, true);
+
+ mStartHandle.mDrawable.setAlpha(255);
+ mEndHandle.mDrawable.setAlpha(255);
+ }
+
+ public void onTouchEvent(MotionEvent event) {
+ if (isTextEditable()) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+
+ // Remember finger down position, to be able to start selection from there
+ mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
+
+ if (mIsVisible) {
+ if (mMovement instanceof ArrowKeyMovementMethod) {
+ boolean isOnStart = mStartHandle.hasFingerOn(x, y);
+ boolean isOnEnd = mEndHandle.hasFingerOn(x, y);
+ if (isOnStart || isOnEnd) {
+ if (mParent != null) {
+ // Prevent possible scrollView parent from scrolling, so
+ // that we can use auto-scrolling.
+ mParent.requestDisallowInterceptTouchEvent(true);
+ }
+
+ // In case both controllers are under finger (very small
+ // selection region), arbitrarily pick end controller.
+ mStartIsDragged = !isOnEnd;
+ final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle;
+ final Rect bounds = draggedHandle.mDrawable.getBounds();
+ mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+ mOffsetY = draggedHandle.mHopSpotVertcalPosition - y;
+
+ ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+ }
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ // Handle multi-point gestures. Keep min and max offset positions.
+ // Only activated for devices that correctly handle multi-touch.
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
+ updateMinAndMaxOffsets(event);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param event
+ */
+ private void updateMinAndMaxOffsets(MotionEvent event) {
+ int pointerCount = event.getPointerCount();
+ for (int index = 0; index < pointerCount; index++) {
+ final int x = (int) event.getX(index);
+ final int y = (int) event.getY(index);
+ int offset = getOffset(x, y);
+ if (offset < mMinTouchOffset) mMinTouchOffset = offset;
+ if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
+ }
+ }
+
+ public int getMinTouchOffset() {
+ return mMinTouchOffset;
+ }
+
+ public int getMaxTouchOffset() {
+ return mMaxTouchOffset;
+ }
+
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
+ /**
+ * @return true iff this controller is currently used to move the selection start.
+ */
+ public boolean isSelectionStartDragged() {
+ return mIsVisible && mStartIsDragged;
+ }
+ }
+
+ private void hideInsertionPointCursorController() {
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.hide();
+ }
+ }
+
+ private void hideControllers() {
+ hideInsertionPointCursorController();
+ stopTextSelectionMode();
+ }
+
+ /**
+ * Get the offset character closest to the specified absolute position.
+ *
+ * @param x The horizontal absolute position of a point on screen
+ * @param y The vertical absolute position of a point on screen
+ * @return the character offset for the character whose position is closest to the specified
+ * position. Returns -1 if there is no layout.
+ *
+ * @hide
+ */
+ public int getOffset(int x, int y) {
+ x -= getTotalPaddingLeft();
+ y -= getTotalPaddingTop();
+
+ // Clamp the position to inside of the view.
+ if (x < 0) {
+ x = 0;
+ } else if (x >= (getWidth() - getTotalPaddingRight())) {
+ x = getWidth()-getTotalPaddingRight() - 1;
+ }
+ if (y < 0) {
+ y = 0;
+ } else if (y >= (getHeight() - getTotalPaddingBottom())) {
+ y = getHeight()-getTotalPaddingBottom() - 1;
+ }
+
+ x += getScrollX();
+ y += getScrollY();
+
+ Layout layout = getLayout();
+ if (layout != null) {
+ final int line = layout.getLineForVertical(y);
+ final int offset = layout.getOffsetForHorizontal(line, x);
+ return offset;
+ } else {
+ return -1;
+ }
+ }
+
+ @ViewDebug.ExportedProperty
private CharSequence mText;
private CharSequence mTransformed;
private BufferType mBufferType = BufferType.NORMAL;
@@ -7313,16 +7992,25 @@
private ArrayList<TextWatcher> mListeners = null;
// display attributes
- private TextPaint mTextPaint;
+ private final TextPaint mTextPaint;
private boolean mUserSetTextScaleX;
- private Paint mHighlightPaint;
- private int mHighlightColor = 0xFFBBDDFF;
+ private final Paint mHighlightPaint;
+ private int mHighlightColor = 0xCC475925;
private Layout mLayout;
private long mShowCursor;
private Blink mBlink;
private boolean mCursorVisible = true;
+ // Cursor Controllers. Null when disabled.
+ private CursorController mInsertionPointCursorController;
+ private CursorController mSelectionModifierCursorController;
+ private boolean mShouldStartTextSelectionMode = false;
+ private boolean mIsInTextSelectionMode = false;
+ // Created once and shared by different CursorController helper methods.
+ // Only one cursor controller is active at any time which prevent race conditions.
+ private static Rect sCursorControllerTempRect = new Rect();
+
private boolean mSelectAllOnFocus = false;
private int mGravity = Gravity.TOP | Gravity.LEFT;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index caed308..e61fac3 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -45,6 +45,9 @@
* Under AM/PM mode, the user can hit 'a', 'A", 'p' or 'P' to pick.
*
* For a dialog using this view, see {@link android.app.TimePickerDialog}.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-timepicker.html">Time Picker
+ * tutorial</a>.</p>
*/
@Widget
public class TimePicker extends FrameLayout {
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index dc791e3..3b680e8 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -26,6 +26,9 @@
/**
* Displays checked/unchecked states as a button
* with a "light" indicator and by default accompanied with the text "ON" or "OFF".
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff
+ * tutorial</a>.</p>
*
* @attr ref android.R.styleable#ToggleButton_textOn
* @attr ref android.R.styleable#ToggleButton_textOff
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 21b2e3b..254d9a4 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -218,6 +218,7 @@
}
pr->setDitherImage(ditherImage);
bitmap->setPixelRef(pr)->unref();
+ pr->isOpaque(bitmap);
return pr;
}
@@ -464,10 +465,8 @@
jobject options) { // BitmapFactory$Options
SkStream* stream;
Asset* asset = reinterpret_cast<Asset*>(native_asset);
- // assets can always be rebuilt, so force this
- bool forcePurgeable = true;
-
- if (forcePurgeable || optionsPurgeable(env, options)) {
+ bool forcePurgeable = optionsPurgeable(env, options);
+ if (forcePurgeable) {
// if we could "ref/reopen" the asset, we may not need to copy it here
// and we could assume optionsShareable, since assets are always RO
stream = copyAssetToStream(asset);
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 17f5daf..f78f83c 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -453,30 +453,23 @@
// return -1 if there was an error querying the buffer size.
static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env, jobject thiz,
jint sampleRateInHertz, jint nbChannels, jint audioFormat) {
-
- size_t inputBuffSize = 0;
+
LOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)", sampleRateInHertz, nbChannels, audioFormat);
-
- status_t result = AudioSystem::getInputBufferSize(
- sampleRateInHertz,
- (audioFormat == javaAudioRecordFields.PCM16 ?
- AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT),
- nbChannels, &inputBuffSize);
- switch(result) {
- case(NO_ERROR):
- if(inputBuffSize == 0) {
- LOGV("Recording parameters are not supported: %dHz, %d channel(s), (java) format %d",
- sampleRateInHertz, nbChannels, audioFormat);
- return 0;
- } else {
- // the minimum buffer size is twice the hardware input buffer size
- return 2*inputBuffSize;
- }
- break;
- case(PERMISSION_DENIED):
- default:
- return -1;
+
+ int frameCount = 0;
+ status_t result = AudioRecord::getMinFrameCount(&frameCount,
+ sampleRateInHertz,
+ (audioFormat == javaAudioRecordFields.PCM16 ?
+ AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT),
+ nbChannels);
+
+ if (result == BAD_VALUE) {
+ return 0;
}
+ if (result != NO_ERROR) {
+ return -1;
+ }
+ return frameCount * nbChannels * (audioFormat == javaAudioRecordFields.PCM16 ? 2 : 1);
}
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index c559670..9d215b7 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -782,29 +782,13 @@
// returns -1 if there was an error querying the hardware.
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz,
jint sampleRateInHertz, jint nbChannels, jint audioFormat) {
- int afSamplingRate;
- int afFrameCount;
- uint32_t afLatency;
-
- if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {
+
+ int frameCount = 0;
+ if (AudioTrack::getMinFrameCount(&frameCount, AudioSystem::DEFAULT,
+ sampleRateInHertz) != NO_ERROR) {
return -1;
}
- if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {
- return -1;
- }
-
- if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {
- return -1;
- }
-
- // Ensure that buffer depth covers at least audio hardware latency
- uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);
- if (minBufCount < 2) minBufCount = 2;
- uint32_t minFrameCount = (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
- int minBuffSize = minFrameCount
- * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
- * nbChannels;
- return minBuffSize;
+ return frameCount * nbChannels * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1);
}
// ----------------------------------------------------------------------------
diff --git a/core/res/res/drawable-hdpi/text_select_handle.png b/core/res/res/drawable-hdpi/text_select_handle.png
new file mode 100644
index 0000000..80d48ab
--- /dev/null
+++ b/core/res/res/drawable-hdpi/text_select_handle.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle.png b/core/res/res/drawable-mdpi/text_select_handle.png
new file mode 100644
index 0000000..93a5a15
--- /dev/null
+++ b/core/res/res/drawable-mdpi/text_select_handle.png
Binary files differ
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index c442fee..245643b 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Označit text"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Zastavit označování textu"</string>
<string name="cut" msgid="3092569408438626261">"Vyjmout"</string>
- <string name="cutAll" msgid="2436383270024931639">"Vyjmout vše"</string>
<string name="copy" msgid="2681946229533511987">"Kopírovat"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopírovat vše"</string>
<string name="paste" msgid="5629880836805036433">"Vložit"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopírovat adresu URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 3ea8266..e7e8019 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Marker tekst"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Hold op med at markere tekst"</string>
<string name="cut" msgid="3092569408438626261">"Klip"</string>
- <string name="cutAll" msgid="2436383270024931639">"Klip alle"</string>
<string name="copy" msgid="2681946229533511987">"Kopier"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopier alle"</string>
<string name="paste" msgid="5629880836805036433">"Indsæt"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string>
<string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index bf3efd6..edbe4c4 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Text auswählen"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Textauswahl beenden"</string>
<string name="cut" msgid="3092569408438626261">"Ausschneiden"</string>
- <string name="cutAll" msgid="2436383270024931639">"Alles ausschneiden"</string>
<string name="copy" msgid="2681946229533511987">"Kopieren"</string>
- <string name="copyAll" msgid="2590829068100113057">"Alles kopieren"</string>
<string name="paste" msgid="5629880836805036433">"Einfügen"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string>
<string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 2c61e1f..cfa36d2 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Επιλογή κειμένου"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Διακοπή επιλογής κειμένου"</string>
<string name="cut" msgid="3092569408438626261">"Αποκοπή"</string>
- <string name="cutAll" msgid="2436383270024931639">"Αποκοπή όλων"</string>
<string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string>
- <string name="copyAll" msgid="2590829068100113057">"Αντιγραφή όλων"</string>
<string name="paste" msgid="5629880836805036433">"Επικόλληση"</string>
<string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 3de378b..1cbfbba 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1079,11 +1079,9 @@
<skip />
<!-- no translation found for cut (5845613239192595662) -->
<skip />
- <!-- no translation found for cutAll (4474519683293791451) -->
<skip />
<!-- no translation found for copy (8603721575469529820) -->
<skip />
- <!-- no translation found for copyAll (4777548804630476932) -->
<skip />
<!-- no translation found for paste (6458036735811828538) -->
<skip />
diff --git a/core/res/res/values-en-rSG/strings.xml b/core/res/res/values-en-rSG/strings.xml
index 2ec6b0b..09a8490 100644
--- a/core/res/res/values-en-rSG/strings.xml
+++ b/core/res/res/values-en-rSG/strings.xml
@@ -1074,11 +1074,9 @@
<skip />
<!-- no translation found for cut (5845613239192595662) -->
<skip />
- <!-- no translation found for cutAll (4474519683293791451) -->
<skip />
<!-- no translation found for copy (8603721575469529820) -->
<skip />
- <!-- no translation found for copyAll (4777548804630476932) -->
<skip />
<!-- no translation found for paste (6458036735811828538) -->
<skip />
diff --git a/core/res/res/values-en-rUS/strings.xml b/core/res/res/values-en-rUS/strings.xml
index 05f30fc..fdc0d69 100644
--- a/core/res/res/values-en-rUS/strings.xml
+++ b/core/res/res/values-en-rUS/strings.xml
@@ -1115,11 +1115,9 @@
<skip />
<!-- no translation found for cut (5845613239192595662) -->
<skip />
- <!-- no translation found for cutAll (4474519683293791451) -->
<skip />
<!-- no translation found for copy (8603721575469529820) -->
<skip />
- <!-- no translation found for copyAll (4777548804630476932) -->
<skip />
<!-- no translation found for paste (6458036735811828538) -->
<skip />
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index afc2862..dbf32b6 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Detener la selección de texto"</string>
<string name="cut" msgid="3092569408438626261">"Cortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Cortar llamada"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string>
<string name="paste" msgid="5629880836805036433">"Pegar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 045b6a1..425fca3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Detener selección de texto"</string>
<string name="cut" msgid="3092569408438626261">"Cortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Cortar todo"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string>
<string name="paste" msgid="5629880836805036433">"Pegar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3ab7814..2dacb30 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Sélectionner le texte"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Arrêter sélection de texte"</string>
<string name="cut" msgid="3092569408438626261">"Couper"</string>
- <string name="cutAll" msgid="2436383270024931639">"Tout couper"</string>
<string name="copy" msgid="2681946229533511987">"Copier"</string>
- <string name="copyAll" msgid="2590829068100113057">"Tout copier"</string>
<string name="paste" msgid="5629880836805036433">"Coller"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 6e50d21..c7aac82 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Seleziona testo"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Termina selezione testo"</string>
<string name="cut" msgid="3092569408438626261">"Taglia"</string>
- <string name="cutAll" msgid="2436383270024931639">"Taglia tutto"</string>
<string name="copy" msgid="2681946229533511987">"Copia"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copia tutto"</string>
<string name="paste" msgid="5629880836805036433">"Incolla"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f1e98da..25e977d 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"テキストを選択"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"テキストの選択を終了"</string>
<string name="cut" msgid="3092569408438626261">"切り取り"</string>
- <string name="cutAll" msgid="2436383270024931639">"すべて切り取り"</string>
<string name="copy" msgid="2681946229533511987">"コピー"</string>
- <string name="copyAll" msgid="2590829068100113057">"すべてコピー"</string>
<string name="paste" msgid="5629880836805036433">"貼り付け"</string>
<string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string>
<string name="inputMethod" msgid="1653630062304567879">"入力方法"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 712100e..faf589e 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"텍스트 선택"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"텍스트 선택 중지"</string>
<string name="cut" msgid="3092569408438626261">"잘라내기"</string>
- <string name="cutAll" msgid="2436383270024931639">"모두 잘라내기"</string>
<string name="copy" msgid="2681946229533511987">"복사"</string>
- <string name="copyAll" msgid="2590829068100113057">"모두 복사"</string>
<string name="paste" msgid="5629880836805036433">"붙여넣기"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string>
<string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index f09039d..2c23d53 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Merk tekst"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Slutt å merke tekst"</string>
<string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
- <string name="cutAll" msgid="2436383270024931639">"Klipp ut alt"</string>
<string name="copy" msgid="2681946229533511987">"Kopier"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopier alt"</string>
<string name="paste" msgid="5629880836805036433">"Lim inn"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 17a2fe4..10a8e74 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Tekst selecteren"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Stoppen met tekst selecteren"</string>
<string name="cut" msgid="3092569408438626261">"Knippen"</string>
- <string name="cutAll" msgid="2436383270024931639">"Alles knippen"</string>
<string name="copy" msgid="2681946229533511987">"Kopiëren"</string>
- <string name="copyAll" msgid="2590829068100113057">"Alles kopiëren"</string>
<string name="paste" msgid="5629880836805036433">"Plakken"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string>
<string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 87e045df3..70c3170 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Zaznacz tekst"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Zatrzymaj wybieranie tekstu"</string>
<string name="cut" msgid="3092569408438626261">"Wytnij"</string>
- <string name="cutAll" msgid="2436383270024931639">"Wytnij wszystko"</string>
<string name="copy" msgid="2681946229533511987">"Kopiuj"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopiuj wszystko"</string>
<string name="paste" msgid="5629880836805036433">"Wklej"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 3fb8d55..9a8c6e9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Parar selecção de texto"</string>
<string name="cut" msgid="3092569408438626261">"Cortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Cortar tudo"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string>
<string name="paste" msgid="5629880836805036433">"Colar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 81ceaf3..2119539 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Selecionar texto"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Parar seleção de texto"</string>
<string name="cut" msgid="3092569408438626261">"Recortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Recortar tudo"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string>
<string name="paste" msgid="5629880836805036433">"Colar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 4176886..a99107e 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Выбрать текст"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Остановить выделение текста"</string>
<string name="cut" msgid="3092569408438626261">"Вырезать"</string>
- <string name="cutAll" msgid="2436383270024931639">"Вырезать все"</string>
<string name="copy" msgid="2681946229533511987">"Копировать"</string>
- <string name="copyAll" msgid="2590829068100113057">"Копировать все"</string>
<string name="paste" msgid="5629880836805036433">"Вставить"</string>
<string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string>
<string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 4c78f4f..b3e415d 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Markera text"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Sluta välja text"</string>
<string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
- <string name="cutAll" msgid="2436383270024931639">"Klipp ut alla"</string>
<string name="copy" msgid="2681946229533511987">"Kopiera"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopiera alla"</string>
<string name="paste" msgid="5629880836805036433">"Klistra in"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string>
<string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f2fd734..a6bc41e 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"Metin seç"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"Metin seçmeyi durdur"</string>
<string name="cut" msgid="3092569408438626261">"Kes"</string>
- <string name="cutAll" msgid="2436383270024931639">"Tümünü kes"</string>
<string name="copy" msgid="2681946229533511987">"Kopyala"</string>
- <string name="copyAll" msgid="2590829068100113057">"Tümünü kopyala"</string>
<string name="paste" msgid="5629880836805036433">"Yapıştır"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL\'yi kopyala"</string>
<string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index a0fb721..aa3f12d 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"选择文字"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"停止选择文字"</string>
<string name="cut" msgid="3092569408438626261">"剪切"</string>
- <string name="cutAll" msgid="2436383270024931639">"全部剪切"</string>
<string name="copy" msgid="2681946229533511987">"复制"</string>
- <string name="copyAll" msgid="2590829068100113057">"全部复制"</string>
<string name="paste" msgid="5629880836805036433">"粘贴"</string>
<string name="copyUrl" msgid="2538211579596067402">"复制网址"</string>
<string name="inputMethod" msgid="1653630062304567879">"输入法"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index d5dd857..5bf0342 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -706,9 +706,7 @@
<string name="selectText" msgid="3889149123626888637">"選取文字"</string>
<string name="stopSelectingText" msgid="4157931463872320996">"停止選取文字"</string>
<string name="cut" msgid="3092569408438626261">"剪下"</string>
- <string name="cutAll" msgid="2436383270024931639">"全部剪下"</string>
<string name="copy" msgid="2681946229533511987">"複製"</string>
- <string name="copyAll" msgid="2590829068100113057">"全部複製"</string>
<string name="paste" msgid="5629880836805036433">"貼上"</string>
<string name="copyUrl" msgid="2538211579596067402">"複製網址"</string>
<string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index bd65fee..1df424b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1245,7 +1245,7 @@
<public type="anim" name="cycle_interpolator" id="0x010a000c" />
<!-- ===============================================================
- Resources introduced in kraken.
+ Resources introduced in Gingerbread.
=============================================================== -->
<public type="attr" name="logo" id="0x010102be" />
@@ -1273,5 +1273,4 @@
<public-padding type="dimen" name="kraken_resource_pad" end="0x01050010" />
<public-padding type="color" name="kraken_resource_pad" end="0x01060020" />
<public-padding type="array" name="kraken_resource_pad" end="0x01070010" />
-
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 62fd169..52abe45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1854,23 +1854,17 @@
<string name="selectAll">Select all</string>
<!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
- <string name="selectText">Select text</string>
+ <string name="selectText">Select word</string>
- <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
+ <!-- Item on EditText context menu. This action is used to stop selecting text in the edit field. -->
<string name="stopSelectingText">Stop selecting text</string>
<!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. -->
<string name="cut">Cut</string>
- <!-- Item on EditText context menu. This action is used to cut all the text into the clipboard. -->
- <string name="cutAll">Cut all</string>
-
<!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. -->
<string name="copy">Copy</string>
- <!-- Item on EditText context menu. This action is used to copy all the text into the clipboard. -->
- <string name="copyAll">Copy all</string>
-
<!-- Item on EditText context menu. This action is used t o paste from the clipboard into the eidt field -->
<string name="paste">Paste</string>
diff --git a/docs/html/resources/tutorials/views/hello-formstuff.jd b/docs/html/resources/tutorials/views/hello-formstuff.jd
index 3dd5f21..b9f6c16 100644
--- a/docs/html/resources/tutorials/views/hello-formstuff.jd
+++ b/docs/html/resources/tutorials/views/hello-formstuff.jd
@@ -32,9 +32,19 @@
}
</pre>
+<p>Now select which kind of form widget you'd like to create:</p>
+<ul>
+ <li><a href="#CustomButton">Custom Button</a></li>
+ <li><a href="#EditText">Edit Text</a></li>
+ <li><a href="#Checkbox">Checkbox</a></li>
+ <li><a href="#RadioButtons">Radio Buttons</a></li>
+ <li><a href="#ToggleButton">Toggle Button</a></li>
+ <li><a href="#RatingBar">Rating Bar</a></li>
+</ul>
-<h2>Custom Button</h2>
+
+<h2 id="CustomButton">Custom Button</h2>
<p>In this section, you will create a button with a custom image instead of text, using the {@link
android.widget.Button} widget and an XML file that defines three different images to use for the
@@ -111,7 +121,8 @@
</ol>
-<h2>EditText</h2>
+
+<h2 id="EditText">Edit Text</h2>
<p>In this section, you will create a text field for user input, using the {@link
android.widget.EditText} widget. Once text has been entered into the field, the "Enter" key will
@@ -158,7 +169,8 @@
</ol>
-<h2>CheckBox</h2>
+
+<h2 id="Checkbox">Checkbox</h2>
<p>In this section, you will create a checkbox for selecting items, using the {@link
android.widget.CheckBox} widget. When the checkbox is pressed, a toast message will
@@ -209,7 +221,8 @@
android.widget.CompoundButton#toggle()} method.</p>
-<h2>RadioButton</h2>
+
+<h2 id="RadioButtons">Radio Buttons</h2>
<p>In this section, you will create two mutually-exclusive radio buttons (enabling one disables
the other), using the {@link android.widget.RadioGroup} and {@link android.widget.RadioButton}
@@ -274,7 +287,8 @@
android.widget.CompoundButton#toggle()} method.</p>
-<h2>ToggleButton</h2>
+
+<h2 id="ToggleButton">Toggle Button</h2>
<p>In this section, you'll create a button used specifically for toggling between two
states, using the {@link android.widget.ToggleButton} widget. This widget is an excellent
@@ -330,7 +344,7 @@
-<h2>RatingBar</h2>
+<h2 id="RatingBar">Rating Bar</h2>
<p>In this section, you'll create a widget that allows the user to provide a rating,
with the {@link android.widget.RatingBar} widget.</p>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 7ca3741..00860ae 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1002,6 +1002,7 @@
@Override
protected void finalize() throws Throwable {
try {
+ mRecycled = true;
nativeDestructor(mNativeBitmap);
} finally {
super.finalize();
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 8982388..02e16cd 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -27,7 +27,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.FileNotFoundException;
/**
* Creates Bitmap objects from various sources, including files, streams,
@@ -40,7 +39,7 @@
* the same result from the decoder as if null were passed.
*/
public Options() {
- inDither = true;
+ inDither = false;
inScaled = true;
}
@@ -70,8 +69,11 @@
* the decoder will try to pick the best matching config based on the
* system's screen depth, and characteristics of the original image such
* as if it has per-pixel alpha (requiring a config that also does).
+ *
+ * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
+ * default.
*/
- public Bitmap.Config inPreferredConfig;
+ public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
/**
* If dither is true, the decoder will attempt to dither the decoded
@@ -453,10 +455,8 @@
// into is.read(...) This number is not related to the value passed
// to mark(...) above.
byte [] tempStorage = null;
- if (opts != null)
- tempStorage = opts.inTempStorage;
- if (tempStorage == null)
- tempStorage = new byte[16 * 1024];
+ if (opts != null) tempStorage = opts.inTempStorage;
+ if (tempStorage == null) tempStorage = new byte[16 * 1024];
bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
@@ -475,8 +475,7 @@
bm.setDensity(density);
final int targetDensity = opts.inTargetDensity;
- if (targetDensity == 0 || density == targetDensity
- || density == opts.inScreenDensity) {
+ if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return bm;
}
@@ -673,8 +672,7 @@
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
// into is.read(...).
- byte [] tempStorage = null;
- tempStorage = new byte[16 * 1024];
+ byte [] tempStorage = new byte[16 * 1024];
return nativeCreateLargeBitmap(is, tempStorage, isShareable);
}
}
@@ -695,8 +693,8 @@
* @throws IOException if the image format is not supported or can not be decoded.
* @hide
*/
- public static LargeBitmap createLargeBitmap(String pathName,
- boolean isShareable) throws FileNotFoundException, IOException {
+ public static LargeBitmap createLargeBitmap(String pathName, boolean isShareable)
+ throws IOException {
LargeBitmap bm = null;
InputStream stream = null;
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 98ffb8b..7830224 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -75,6 +75,7 @@
bottom = r.bottom;
}
+ @Override
public boolean equals(Object obj) {
Rect r = (Rect) obj;
if (r != null) {
@@ -84,6 +85,7 @@
return false;
}
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder(32);
sb.append("Rect("); sb.append(left); sb.append(", ");
@@ -351,7 +353,7 @@
* rectangle, return true and set this rectangle to that intersection,
* otherwise return false and do not change this rectangle. No check is
* performed to see if either rectangle is empty. Note: To just test for
- * intersection, use intersects()
+ * intersection, use {@link #intersects(Rect, Rect)}.
*
* @param left The left side of the rectangle being intersected with this
* rectangle
@@ -445,7 +447,7 @@
/**
* Returns true iff the two specified rectangles intersect. In no event are
* either of the rectangles modified. To record the intersection,
- * use intersect() or setIntersect().
+ * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
*
* @param a The first rectangle being tested for intersection
* @param b The second rectangle being tested for intersection
diff --git a/include/camera/CameraHardwareInterface.h b/include/camera/CameraHardwareInterface.h
index 1529db7..6a66e3c 100644
--- a/include/camera/CameraHardwareInterface.h
+++ b/include/camera/CameraHardwareInterface.h
@@ -221,6 +221,7 @@
*/
extern "C" int HAL_getNumberOfCameras();
extern "C" void HAL_getCameraInfo(int cameraId, struct CameraInfo* cameraInfo);
+/* HAL should return NULL if it fails to open camera hardware. */
extern "C" sp<CameraHardwareInterface> HAL_openCameraHardware(int cameraId);
}; // namespace android
diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h
index 813dd43..aa965e1 100644
--- a/include/media/stagefright/AMRWriter.h
+++ b/include/media/stagefright/AMRWriter.h
@@ -37,8 +37,8 @@
virtual status_t addSource(const sp<MediaSource> &source);
virtual bool reachedEOS();
virtual status_t start(MetaData *params = NULL);
- virtual void stop();
- virtual void pause();
+ virtual status_t stop();
+ virtual status_t pause();
protected:
virtual ~AMRWriter();
@@ -57,7 +57,7 @@
int64_t mEstimatedDurationUs;
static void *ThreadWrapper(void *);
- void threadFunc();
+ status_t threadFunc();
bool exceedsFileSizeLimit();
bool exceedsFileDurationLimit();
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index be96935..de82b38 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -37,9 +37,9 @@
virtual status_t addSource(const sp<MediaSource> &source);
virtual status_t start(MetaData *param = NULL);
+ virtual status_t stop();
+ virtual status_t pause();
virtual bool reachedEOS();
- virtual void stop();
- virtual void pause();
void beginBox(const char *fourcc);
void writeInt8(int8_t x);
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
index 8d3a9df..151bf16 100644
--- a/include/media/stagefright/MediaWriter.h
+++ b/include/media/stagefright/MediaWriter.h
@@ -35,8 +35,9 @@
virtual status_t addSource(const sp<MediaSource> &source) = 0;
virtual bool reachedEOS() = 0;
virtual status_t start(MetaData *params = NULL) = 0;
- virtual void stop() = 0;
- virtual void pause() = 0;
+ virtual status_t stop() = 0;
+ virtual status_t pause() = 0;
+
virtual void setMaxFileSize(int64_t bytes) { mMaxFileSizeLimitBytes = bytes; }
virtual void setMaxFileDuration(int64_t durationUs) { mMaxFileDurationLimitUs = durationUs; }
virtual void setListener(const sp<IMediaRecorderClient>& listener) {
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index ce616a4..e35050c 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -201,7 +201,8 @@
}
#endif
- mThrottleState.lastEventTime = currentTime;
+ mThrottleState.lastEventTime = entry->eventTime < currentTime
+ ? entry->eventTime : currentTime;
mThrottleState.lastDeviceId = deviceId;
mThrottleState.lastSource = source;
}
diff --git a/libs/ui/PixelFormat.cpp b/libs/ui/PixelFormat.cpp
index edf1aed..ee186c8 100644
--- a/libs/ui/PixelFormat.cpp
+++ b/libs/ui/PixelFormat.cpp
@@ -63,7 +63,6 @@
info->bitsPerPixel = 16;
goto done;
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
- case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED:
case HAL_PIXEL_FORMAT_YV12:
info->bitsPerPixel = 12;
done:
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 94448c1..3c6d01b 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1067,8 +1067,9 @@
status_t StagefrightRecorder::stop() {
LOGV("stop");
+ status_t err = OK;
if (mWriter != NULL) {
- mWriter->stop();
+ err = mWriter->stop();
mWriter.clear();
}
@@ -1090,7 +1091,7 @@
mOutputFd = -1;
}
- return OK;
+ return err;
}
status_t StagefrightRecorder::close() {
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index c71743e..71d48b3 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -136,16 +136,17 @@
return OK;
}
-void AMRWriter::pause() {
+status_t AMRWriter::pause() {
if (!mStarted) {
- return;
+ return OK;
}
mPaused = true;
+ return OK;
}
-void AMRWriter::stop() {
+status_t AMRWriter::stop() {
if (!mStarted) {
- return;
+ return OK;
}
mDone = true;
@@ -153,9 +154,17 @@
void *dummy;
pthread_join(mThread, &dummy);
- mSource->stop();
+ status_t err = (status_t) dummy;
+ {
+ status_t status = mSource->stop();
+ if (err == OK &&
+ (status != OK && status != ERROR_END_OF_STREAM)) {
+ err = status;
+ }
+ }
mStarted = false;
+ return err;
}
bool AMRWriter::exceedsFileSizeLimit() {
@@ -174,21 +183,20 @@
// static
void *AMRWriter::ThreadWrapper(void *me) {
- static_cast<AMRWriter *>(me)->threadFunc();
-
- return NULL;
+ return (void *) static_cast<AMRWriter *>(me)->threadFunc();
}
-void AMRWriter::threadFunc() {
+status_t AMRWriter::threadFunc() {
mEstimatedDurationUs = 0;
mEstimatedSizeBytes = 0;
bool stoppedPrematurely = true;
int64_t previousPausedDurationUs = 0;
int64_t maxTimestampUs = 0;
+ status_t err = OK;
while (!mDone) {
MediaBuffer *buffer;
- status_t err = mSource->read(&buffer);
+ err = mSource->read(&buffer);
if (err != OK) {
break;
@@ -260,6 +268,10 @@
fclose(mFile);
mFile = NULL;
mReachedEOS = true;
+ if (err == ERROR_END_OF_STREAM) {
+ return OK;
+ }
+ return err;
}
bool AMRWriter::reachedEOS() {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index f52ec1a..568037e 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -48,8 +48,8 @@
~Track();
status_t start(MetaData *params);
- void stop();
- void pause();
+ status_t stop();
+ status_t pause();
bool reachedEOS();
int64_t getDurationUs() const;
@@ -144,7 +144,7 @@
int64_t mTrackEveryTimeDurationUs;
static void *ThreadWrapper(void *me);
- void threadEntry();
+ status_t threadEntry();
const uint8_t *parseParamSet(
const uint8_t *data, size_t length, int type, size_t *paramSetLen);
@@ -174,6 +174,9 @@
// value, the user-supplied time scale will be used.
void setTimeScale();
+ // Simple validation on the codec specific data
+ status_t checkCodecSpecificData() const;
+
Track(const Track &);
Track &operator=(const Track &);
};
@@ -378,15 +381,20 @@
return OK;
}
-void MPEG4Writer::pause() {
+status_t MPEG4Writer::pause() {
if (mFile == NULL) {
- return;
+ return OK;
}
mPaused = true;
+ status_t err = OK;
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
- (*it)->pause();
+ status_t status = (*it)->pause();
+ if (status != OK) {
+ err = status;
+ }
}
+ return err;
}
void MPEG4Writer::stopWriterThread() {
@@ -403,15 +411,19 @@
pthread_join(mThread, &dummy);
}
-void MPEG4Writer::stop() {
+status_t MPEG4Writer::stop() {
if (mFile == NULL) {
- return;
+ return OK;
}
+ status_t err = OK;
int64_t maxDurationUs = 0;
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
- (*it)->stop();
+ status_t status = (*it)->stop();
+ if (err == OK && status != OK) {
+ err = status;
+ }
int64_t durationUs = (*it)->getDurationUs();
if (durationUs > maxDurationUs) {
@@ -421,6 +433,15 @@
stopWriterThread();
+ // Do not write out movie header on error.
+ if (err != OK) {
+ fflush(mFile);
+ fclose(mFile);
+ mFile = NULL;
+ mStarted = false;
+ return err;
+ }
+
// Fix up the size of the 'mdat' chunk.
if (mUse32BitOffset) {
fseeko(mFile, mMdatOffset, SEEK_SET);
@@ -508,6 +529,7 @@
fclose(mFile);
mFile = NULL;
mStarted = false;
+ return err;
}
status_t MPEG4Writer::setInterleaveDuration(uint32_t durationUs) {
@@ -1030,13 +1052,14 @@
return OK;
}
-void MPEG4Writer::Track::pause() {
+status_t MPEG4Writer::Track::pause() {
mPaused = true;
+ return OK;
}
-void MPEG4Writer::Track::stop() {
+status_t MPEG4Writer::Track::stop() {
if (mDone) {
- return;
+ return OK;
}
mDone = true;
@@ -1044,7 +1067,16 @@
void *dummy;
pthread_join(mThread, &dummy);
- mSource->stop();
+ status_t err = (status_t) dummy;
+
+ {
+ status_t status = mSource->stop();
+ if (err == OK && status != OK && status != ERROR_END_OF_STREAM) {
+ err = status;
+ }
+ }
+
+ return err;
}
bool MPEG4Writer::Track::reachedEOS() {
@@ -1055,9 +1087,8 @@
void *MPEG4Writer::Track::ThreadWrapper(void *me) {
Track *track = static_cast<Track *>(me);
- track->threadEntry();
-
- return NULL;
+ status_t err = track->threadEntry();
+ return (void *) err;
}
#include <ctype.h>
@@ -1352,7 +1383,7 @@
return false;
}
-void MPEG4Writer::Track::threadEntry() {
+status_t MPEG4Writer::Track::threadEntry() {
int32_t count = 0;
const int64_t interleaveDurationUs = mOwner->interleaveDuration();
int64_t chunkTimestampUs = 0;
@@ -1493,10 +1524,24 @@
CHECK(timestampUs >= 0);
if (mNumSamples > 1) {
if (timestampUs <= lastTimestampUs) {
- LOGW("Drop a frame, since it arrives too late!");
+ LOGW("Frame arrives too late!");
+#if 0
+ // Drop the late frame.
copy->release();
copy = NULL;
continue;
+#else
+ // Don't drop the late frame, since dropping a frame may cause
+ // problems later during playback
+
+ // The idea here is to avoid having two or more samples with the
+ // same timestamp in the output file.
+ if (mTimeScale >= 1000000LL) {
+ timestampUs += 1;
+ } else {
+ timestampUs += (1000000LL + (mTimeScale >> 1)) / mTimeScale;
+ }
+#endif
}
}
@@ -1595,7 +1640,9 @@
}
if (mSampleSizes.empty()) {
- err = UNKNOWN_ERROR;
+ err = ERROR_MALFORMED;
+ } else if (OK != checkCodecSpecificData()) {
+ err = ERROR_MALFORMED;
}
mOwner->trackProgressStatus(this, -1, err);
@@ -1626,6 +1673,10 @@
count, nZeroLengthFrames, mNumSamples, mMaxWriteTimeUs, mIsAudio? "audio": "video");
logStatisticalData(mIsAudio);
+ if (err == ERROR_END_OF_STREAM) {
+ return OK;
+ }
+ return err;
}
void MPEG4Writer::Track::trackProgressStatus(int64_t timeUs, status_t err) {
@@ -1801,6 +1852,27 @@
return mEstimatedTrackSizeBytes;
}
+status_t MPEG4Writer::Track::checkCodecSpecificData() const {
+ const char *mime;
+ CHECK(mMeta->findCString(kKeyMIMEType, &mime));
+ if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime) ||
+ !strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime) ||
+ !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
+ if (!mCodecSpecificData ||
+ mCodecSpecificDataSize <= 0) {
+ // Missing codec specific data
+ return ERROR_MALFORMED;
+ }
+ } else {
+ if (mCodecSpecificData ||
+ mCodecSpecificDataSize > 0) {
+ // Unexepected codec specific data found
+ return ERROR_MALFORMED;
+ }
+ }
+ return OK;
+}
+
void MPEG4Writer::Track::writeTrackHeader(
int32_t trackID, bool use32BitOffset) {
const char *mime;
@@ -1973,7 +2045,6 @@
int32_t samplerate;
bool success = mMeta->findInt32(kKeySampleRate, &samplerate);
CHECK(success);
-
mOwner->writeInt32(samplerate << 16);
if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) {
mOwner->beginBox("esds");
diff --git a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
new file mode 100644
index 0000000..7e633d7
--- /dev/null
+++ b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include "AMPEG4ElementaryAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#include <stdint.h>
+
+#define BE_VERBOSE 0
+
+namespace android {
+
+// static
+AMPEG4ElementaryAssembler::AMPEG4ElementaryAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+}
+
+AMPEG4ElementaryAssembler::~AMPEG4ElementaryAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AMPEG4ElementaryAssembler::addPacket(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ mPackets.push_back(buffer);
+ // hexdump(buffer->data(), buffer->size());
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+}
+
+void AMPEG4ElementaryAssembler::submitAccessUnit() {
+ CHECK(!mPackets.empty());
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " nal units)";
+#endif
+
+ uint64_t ntpTime;
+ CHECK((*mPackets.begin())->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ size_t totalSize = 0;
+ for (List<sp<ABuffer> >::iterator it = mPackets.begin();
+ it != mPackets.end(); ++it) {
+ totalSize += (*it)->size();
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ for (List<sp<ABuffer> >::iterator it = mPackets.begin();
+ it != mPackets.end(); ++it) {
+ sp<ABuffer> nal = *it;
+ memcpy(accessUnit->data() + offset, nal->data(), nal->size());
+ offset += nal->size();
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+ printf(mAccessUnitDamaged ? "X" : ".");
+ fflush(stdout);
+#endif
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mPackets.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+}
+
+ARTPAssembler::AssemblyStatus AMPEG4ElementaryAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addPacket(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+void AMPEG4ElementaryAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ LOG(VERBOSE) << "packetLost (expected " << mNextExpectedSeqNo << ")";
+
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+void AMPEG4ElementaryAssembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.h b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.h
new file mode 100644
index 0000000..1566d00
--- /dev/null
+++ b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef A_MPEG4_ELEM_ASSEMBLER_H_
+
+#define A_MPEG4_ELEM_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+struct AMPEG4ElementaryAssembler : public ARTPAssembler {
+ AMPEG4ElementaryAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AMPEG4ElementaryAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mPackets;
+
+ AssemblyStatus addPacket(const sp<ARTPSource> &source);
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AMPEG4ElementaryAssembler);
+};
+
+} // namespace android
+
+#endif // A_MPEG4_ELEM_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index 353c746..8c56cb7 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -22,6 +22,7 @@
#include <ctype.h>
+#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
@@ -246,6 +247,161 @@
return csd;
}
+static size_t GetSizeWidth(size_t x) {
+ size_t n = 1;
+ while (x > 127) {
+ ++n;
+ x >>= 7;
+ }
+ return n;
+}
+
+static uint8_t *EncodeSize(uint8_t *dst, size_t x) {
+ while (x > 127) {
+ *dst++ = (x & 0x7f) | 0x80;
+ x >>= 7;
+ }
+ *dst++ = x;
+ return dst;
+}
+
+static bool ExtractDimensionsFromVOLHeader(
+ const sp<ABuffer> &config, int32_t *width, int32_t *height) {
+ *width = 0;
+ *height = 0;
+
+ const uint8_t *ptr = config->data();
+ size_t offset = 0;
+ bool foundVOL = false;
+ while (offset + 3 < config->size()) {
+ if (memcmp("\x00\x00\x01", &ptr[offset], 3)
+ || (ptr[offset + 3] & 0xf0) != 0x20) {
+ ++offset;
+ continue;
+ }
+
+ foundVOL = true;
+ break;
+ }
+
+ if (!foundVOL) {
+ return false;
+ }
+
+ ABitReader br(&ptr[offset + 4], config->size() - offset - 4);
+ br.skipBits(1); // random_accessible_vol
+ unsigned video_object_type_indication = br.getBits(8);
+
+ CHECK_NE(video_object_type_indication,
+ 0x21u /* Fine Granularity Scalable */);
+
+ unsigned video_object_layer_verid;
+ unsigned video_object_layer_priority;
+ if (br.getBits(1)) {
+ video_object_layer_verid = br.getBits(4);
+ video_object_layer_priority = br.getBits(3);
+ }
+ unsigned aspect_ratio_info = br.getBits(4);
+ if (aspect_ratio_info == 0x0f /* extended PAR */) {
+ br.skipBits(8); // par_width
+ br.skipBits(8); // par_height
+ }
+ if (br.getBits(1)) { // vol_control_parameters
+ br.skipBits(2); // chroma_format
+ br.skipBits(1); // low_delay
+ if (br.getBits(1)) { // vbv_parameters
+ TRESPASS();
+ }
+ }
+ unsigned video_object_layer_shape = br.getBits(2);
+ CHECK_EQ(video_object_layer_shape, 0x00u /* rectangular */);
+
+ CHECK(br.getBits(1)); // marker_bit
+ unsigned vop_time_increment_resolution = br.getBits(16);
+ CHECK(br.getBits(1)); // marker_bit
+
+ if (br.getBits(1)) { // fixed_vop_rate
+ // range [0..vop_time_increment_resolution)
+
+ // vop_time_increment_resolution
+ // 2 => 0..1, 1 bit
+ // 3 => 0..2, 2 bits
+ // 4 => 0..3, 2 bits
+ // 5 => 0..4, 3 bits
+ // ...
+
+ CHECK_GT(vop_time_increment_resolution, 0u);
+ --vop_time_increment_resolution;
+
+ unsigned numBits = 0;
+ while (vop_time_increment_resolution > 0) {
+ ++numBits;
+ vop_time_increment_resolution >>= 1;
+ }
+
+ br.skipBits(numBits); // fixed_vop_time_increment
+ }
+
+ CHECK(br.getBits(1)); // marker_bit
+ unsigned video_object_layer_width = br.getBits(13);
+ CHECK(br.getBits(1)); // marker_bit
+ unsigned video_object_layer_height = br.getBits(13);
+ CHECK(br.getBits(1)); // marker_bit
+
+ unsigned interlaced = br.getBits(1);
+
+ *width = video_object_layer_width;
+ *height = video_object_layer_height;
+
+ LOG(INFO) << "VOL dimensions = " << *width << "x" << *height;
+
+ return true;
+}
+
+sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
+ const char *params, int32_t *width, int32_t *height) {
+ *width = 0;
+ *height = 0;
+
+ AString val;
+ CHECK(GetAttribute(params, "config", &val));
+
+ sp<ABuffer> config = decodeHex(val);
+ CHECK(config != NULL);
+
+ if (!ExtractDimensionsFromVOLHeader(config, width, height)) {
+ return NULL;
+ }
+
+ size_t len1 = config->size() + GetSizeWidth(config->size()) + 1;
+ size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13;
+ size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3;
+
+ sp<ABuffer> csd = new ABuffer(len3);
+ uint8_t *dst = csd->data();
+ *dst++ = 0x03;
+ dst = EncodeSize(dst, len2 + 3);
+ *dst++ = 0x00; // ES_ID
+ *dst++ = 0x00;
+ *dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+ *dst++ = 0x04;
+ dst = EncodeSize(dst, len1 + 13);
+ *dst++ = 0x01; // Video ISO/IEC 14496-2 Simple Profile
+ for (size_t i = 0; i < 12; ++i) {
+ *dst++ = 0x00;
+ }
+
+ *dst++ = 0x05;
+ dst = EncodeSize(dst, config->size());
+ memcpy(dst, config->data(), config->size());
+ dst += config->size();
+
+ // hexdump(csd->data(), csd->size());
+
+ return csd;
+}
+
APacketSource::APacketSource(
const sp<ASessionDescription> &sessionDesc, size_t index)
: mInitCheck(NO_INIT),
@@ -351,6 +507,36 @@
if (sampleRate != 16000 || numChannels != 1) {
mInitCheck = ERROR_UNSUPPORTED;
}
+ } else if (!strncmp(desc.c_str(), "MP4V-ES/", 8)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+
+ int32_t width, height;
+ if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
+ width = -1;
+ height = -1;
+ }
+
+ int32_t encWidth, encHeight;
+ sp<ABuffer> codecSpecificData =
+ MakeMPEG4VideoCodecSpecificData(
+ params.c_str(), &encWidth, &encHeight);
+
+ if (codecSpecificData != NULL) {
+ mFormat->setData(
+ kKeyESDS, 0,
+ codecSpecificData->data(), codecSpecificData->size());
+
+ if (width < 0) {
+ width = encWidth;
+ height = encHeight;
+ }
+ } else if (width < 0) {
+ mInitCheck = ERROR_UNSUPPORTED;
+ return;
+ }
+
+ mFormat->setInt32(kKeyWidth, width);
+ mFormat->setInt32(kKeyHeight, height);
} else {
mInitCheck = ERROR_UNSUPPORTED;
}
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index 469af3e..6816c45 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -321,6 +321,8 @@
buffer->setRange(0, nbytes);
+ // LOG(INFO) << "received " << buffer->size() << " bytes.";
+
status_t err;
if (receiveRTP) {
err = parseRTP(s, buffer);
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
index 225f6e8..775c4ee 100644
--- a/media/libstagefright/rtsp/ARTPSource.cpp
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -20,6 +20,7 @@
#include "AAVCAssembler.h"
#include "AH263Assembler.h"
#include "AMPEG4AudioAssembler.h"
+#include "AMPEG4ElementaryAssembler.h"
#include "ASessionDescription.h"
#include <media/stagefright/foundation/ABuffer.h>
@@ -63,6 +64,9 @@
mAssembler = new AAMRAssembler(notify, false /* isWide */, params);
} else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) {
mAssembler = new AAMRAssembler(notify, true /* isWide */, params);
+ } else if (!strncmp(desc.c_str(), "MP4V-ES/", 8)) {
+ mAssembler = new AMPEG4ElementaryAssembler(notify);
+ mIssueFIRRequests = true;
} else {
TRESPASS();
}
diff --git a/media/libstagefright/rtsp/ARTPWriter.cpp b/media/libstagefright/rtsp/ARTPWriter.cpp
index d6dd597..d4eed7c 100644
--- a/media/libstagefright/rtsp/ARTPWriter.cpp
+++ b/media/libstagefright/rtsp/ARTPWriter.cpp
@@ -134,10 +134,10 @@
return OK;
}
-void ARTPWriter::stop() {
+status_t ARTPWriter::stop() {
Mutex::Autolock autoLock(mLock);
if (!(mFlags & kFlagStarted)) {
- return;
+ return OK;
}
(new AMessage(kWhatStop, mReflector->id()))->post();
@@ -145,9 +145,11 @@
while (mFlags & kFlagStarted) {
mCondition.wait(mLock);
}
+ return OK;
}
-void ARTPWriter::pause() {
+status_t ARTPWriter::pause() {
+ return OK;
}
static void StripStartcode(MediaBuffer *buffer) {
diff --git a/media/libstagefright/rtsp/ARTPWriter.h b/media/libstagefright/rtsp/ARTPWriter.h
index b1b8b45..fdc8d23 100644
--- a/media/libstagefright/rtsp/ARTPWriter.h
+++ b/media/libstagefright/rtsp/ARTPWriter.h
@@ -40,8 +40,8 @@
virtual status_t addSource(const sp<MediaSource> &source);
virtual bool reachedEOS();
virtual status_t start(MetaData *params);
- virtual void stop();
- virtual void pause();
+ virtual status_t stop();
+ virtual status_t pause();
virtual void onMessageReceived(const sp<AMessage> &msg);
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index 7f3659f..ed16059 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -7,6 +7,7 @@
AAVCAssembler.cpp \
AH263Assembler.cpp \
AMPEG4AudioAssembler.cpp \
+ AMPEG4ElementaryAssembler.cpp \
APacketSource.cpp \
ARTPAssembler.cpp \
ARTPConnection.cpp \
diff --git a/opengl/libagl/array.cpp b/opengl/libagl/array.cpp
index 4997dc8..7fbe9b5 100644
--- a/opengl/libagl/array.cpp
+++ b/opengl/libagl/array.cpp
@@ -685,8 +685,8 @@
} while (num);
}
if (count) {
- v0 = c->vc.vBuffer + 2 + num - 2;
- v1 = c->vc.vBuffer + 2 + num - 1;
+ v0 = c->vc.vBuffer + 2 + vcs - 2;
+ v1 = c->vc.vBuffer + 2 + vcs - 1;
if ((winding&2) == 0) {
// for strips copy back the two last compiled vertices
c->vc.vBuffer[0] = *v0;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6a5290e..2e95932 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -222,16 +222,11 @@
final String value = c.moveToNext() ? c.getString(0) : null;
if (value == null) {
final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
- String serial = SystemProperties.get("ro.serialno");
- if (serial != null) {
- try {
- random.setSeed(serial.getBytes("UTF-8"));
- } catch (UnsupportedEncodingException ignore) {
- // stick with default seed
- }
- }
+ String serial = SystemProperties.get("ro.serialno", "");
+ random.setSeed(
+ (serial + System.nanoTime() + new SecureRandom().nextLong()).getBytes());
final String newAndroidIdValue = Long.toHexString(random.nextLong());
- Log.d(TAG, "Generated and saved new ANDROID_ID");
+ Log.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue + "]");
final ContentValues values = new ContentValues();
values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
index 07bcce7..d2346e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
@@ -817,7 +817,8 @@
void performCollapse() {
if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded
- + " mExpandedVisible=" + mExpandedVisible);
+ + " mExpandedVisible=" + mExpandedVisible
+ + " mTicking=" + mTicking);
if (!mExpandedVisible) {
return;
@@ -832,7 +833,9 @@
if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
}
- setDateViewVisibility(false, com.android.internal.R.anim.fade_out);
+ if (mDateView.getVisibility() == View.VISIBLE) {
+ setDateViewVisibility(false, com.android.internal.R.anim.fade_out);
+ }
if (!mExpanded) {
return;
@@ -1147,6 +1150,7 @@
@Override
void tickerStarting() {
+ if (SPEW) Slog.d(TAG, "tickerStarting");
mTicking = true;
mIcons.setVisibility(View.GONE);
mTickerView.setVisibility(View.VISIBLE);
@@ -1159,38 +1163,30 @@
@Override
void tickerDone() {
+ if (SPEW) Slog.d(TAG, "tickerDone");
+ mTicking = false;
mIcons.setVisibility(View.VISIBLE);
mTickerView.setVisibility(View.GONE);
mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
- mTickingDoneListener));
+ mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, null));
if (mExpandedVisible) {
setDateViewVisibility(true, com.android.internal.R.anim.push_down_in);
}
}
void tickerHalting() {
+ if (SPEW) Slog.d(TAG, "tickerHalting");
+ mTicking = false;
mIcons.setVisibility(View.VISIBLE);
mTickerView.setVisibility(View.GONE);
mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
- mTickingDoneListener));
+ mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out, null));
if (mExpandedVisible) {
setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
}
}
}
- Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
- public void onAnimationEnd(Animation animation) {
- mTicking = false;
- }
- public void onAnimationRepeat(Animation animation) {
- }
- public void onAnimationStart(Animation animation) {
- }
- };
-
private Animation loadAnim(int id, Animation.AnimationListener listener) {
Animation anim = AnimationUtils.loadAnimation(StatusBarService.this, id);
if (listener != null) {
@@ -1373,7 +1369,8 @@
void updateExpandedViewPos(int expandedPosition) {
if (SPEW) {
Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition
- + " mTrackingParams.y=" + mTrackingParams.y
+ + " mTrackingParams.y="
+ + ((mTrackingParams == null) ? "???" : mTrackingParams.y)
+ " mTrackingPosition=" + mTrackingPosition);
}
@@ -1518,6 +1515,9 @@
}
}
} else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+ Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: "
+ + (((net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0)
+ ? "yes" : "no"));
if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
mTicker.halt();
}
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 3b3904a..ea2c5d4 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -145,7 +145,12 @@
return NULL;
}
- client = new Client(this, cameraClient, cameraId, callingPid);
+ sp<CameraHardwareInterface> hardware = HAL_openCameraHardware(cameraId);
+ if (hardware == NULL) {
+ LOGE("Fail to open camera hardware (id=%d)", cameraId);
+ return NULL;
+ }
+ client = new Client(this, cameraClient, hardware, cameraId, callingPid);
mClient[cameraId] = client;
LOG1("CameraService::connect X");
return client;
@@ -285,16 +290,17 @@
// ----------------------------------------------------------------------------
CameraService::Client::Client(const sp<CameraService>& cameraService,
- const sp<ICameraClient>& cameraClient, int cameraId, int clientPid) {
+ const sp<ICameraClient>& cameraClient,
+ const sp<CameraHardwareInterface>& hardware,
+ int cameraId, int clientPid) {
int callingPid = getCallingPid();
LOG1("Client::Client E (pid %d)", callingPid);
mCameraService = cameraService;
mCameraClient = cameraClient;
+ mHardware = hardware;
mCameraId = cameraId;
mClientPid = clientPid;
-
- mHardware = HAL_openCameraHardware(cameraId);
mUseOverlay = mHardware->useOverlay();
mMsgEnabled = 0;
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 77ccf41..0d69836 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -106,6 +106,7 @@
friend class CameraService;
Client(const sp<CameraService>& cameraService,
const sp<ICameraClient>& cameraClient,
+ const sp<CameraHardwareInterface>& hardware,
int cameraId,
int clientPid);
~Client();
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index db3f536..d33558b 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -381,14 +381,29 @@
: listener);
}
+ // process the command in a new thread
+ private void doCommandAsync(final EventObject command) {
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ processCommand(command);
+ } catch (SipException e) {
+ // TODO: find a better way to do this
+ if ((command instanceof RegisterCommand)
+ || (command == DEREGISTER)) {
+ onRegistrationFailed(e);
+ } else {
+ onError(e);
+ }
+ }
+ }
+ }).start();
+ }
+
public void makeCall(SipProfile peerProfile,
SessionDescription sessionDescription) {
- try {
- processCommand(
- new MakeCallCommand(peerProfile, sessionDescription));
- } catch (SipException e) {
- onError(e);
- }
+ doCommandAsync(
+ new MakeCallCommand(peerProfile, sessionDescription));
}
public void answerCall(SessionDescription sessionDescription) {
@@ -401,36 +416,20 @@
}
public void endCall() {
- try {
- processCommand(END_CALL);
- } catch (SipException e) {
- onError(e);
- }
+ doCommandAsync(END_CALL);
}
public void changeCall(SessionDescription sessionDescription) {
- try {
- processCommand(
- new MakeCallCommand(mPeerProfile, sessionDescription));
- } catch (SipException e) {
- onError(e);
- }
+ doCommandAsync(
+ new MakeCallCommand(mPeerProfile, sessionDescription));
}
public void register(int duration) {
- try {
- processCommand(new RegisterCommand(duration));
- } catch (SipException e) {
- onRegistrationFailed(e);
- }
+ doCommandAsync(new RegisterCommand(duration));
}
public void unregister() {
- try {
- processCommand(DEREGISTER);
- } catch (SipException e) {
- onRegistrationFailed(e);
- }
+ doCommandAsync(DEREGISTER);
}
public boolean isReRegisterRequired() {
diff --git a/services/surfaceflinger/TextureManager.cpp b/services/surfaceflinger/TextureManager.cpp
index 0f448e0..76f6159 100644
--- a/services/surfaceflinger/TextureManager.cpp
+++ b/services/surfaceflinger/TextureManager.cpp
@@ -121,7 +121,6 @@
case HAL_PIXEL_FORMAT_YCbCr_422_SP:
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
case HAL_PIXEL_FORMAT_YCbCr_422_I:
- case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED:
return true;
}
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index ca526a5..606b52d 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -44,10 +44,6 @@
import android.util.Log;
import android.view.WindowManager;
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.SmsMessageBase;
-import com.android.internal.telephony.SmsResponse;
-import com.android.internal.telephony.WapPushOverSms;
import com.android.internal.util.HexDump;
import java.io.ByteArrayOutputStream;
@@ -75,7 +71,7 @@
private static final int DEFAULT_SMS_MAX_COUNT = 100;
/** Default timeout for SMS sent query */
- private static final int DEFAULT_SMS_TIMOUEOUT = 6000;
+ private static final int DEFAULT_SMS_TIMEOUT = 6000;
protected static final String[] RAW_PROJECTION = new String[] {
"pdu",
@@ -83,8 +79,6 @@
"destination_port",
};
- static final int MAIL_SEND_SMS = 1;
-
static final protected int EVENT_NEW_SMS = 1;
static final protected int EVENT_SEND_SMS_COMPLETE = 2;
@@ -143,7 +137,7 @@
private SmsCounter mCounter;
- private ArrayList mSTrackers = new ArrayList(MO_MSG_QUEUE_LIMIT);
+ private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
/** Wake lock to ensure device stays awake while dispatching the SMS intent. */
private PowerManager.WakeLock mWakeLock;
@@ -154,10 +148,6 @@
*/
private final int WAKE_LOCK_TIMEOUT = 5000;
- private static SmsMessage mSmsMessage;
- private static SmsMessageBase mSmsMessageBase;
- private SmsMessageBase.SubmitPduBase mSubmitPduBase;
-
protected boolean mStorageAvailable = true;
protected boolean mReportMemoryStatusPending = false;
@@ -345,7 +335,7 @@
msg.obj = null;
if (mSTrackers.isEmpty() == false) {
try {
- SmsTracker sTracker = (SmsTracker)mSTrackers.remove(0);
+ SmsTracker sTracker = mSTrackers.remove(0);
sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
} catch (CanceledException ex) {
Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
@@ -358,7 +348,7 @@
case EVENT_SEND_CONFIRMED_SMS:
if (mSTrackers.isEmpty() == false) {
- SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
+ SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
if (isMultipartTracker(sTracker)) {
sendMultipartSms(sTracker);
} else {
@@ -372,7 +362,7 @@
if (mSTrackers.isEmpty() == false) {
// Remove the latest one.
try {
- SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
+ SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
} catch (CanceledException ex) {
Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
@@ -679,7 +669,7 @@
* @param destPort the port to deliver the message to
* @param data the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -706,7 +696,7 @@
* the current default SMSC
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -761,7 +751,7 @@
* defatult SMSC
* @param pdu the raw PDU to send
* @param sentIntent if not NULL this <code>Intent</code> is
- * broadcast when the message is sucessfully sent, or failed.
+ * broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
@@ -837,7 +827,7 @@
mSTrackers.add(tracker);
sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
- DEFAULT_SMS_TIMOUEOUT);
+ DEFAULT_SMS_TIMEOUT);
}
protected String getAppNameByIntent(PendingIntent intent) {
@@ -931,7 +921,7 @@
}
/**
- * Keeps track of an SMS that has been sent to the RIL, until it it has
+ * Keeps track of an SMS that has been sent to the RIL, until it has
* successfully been sent, or we're done trying.
*
*/
@@ -972,27 +962,26 @@
}
};
- private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
- mStorageAvailable = false;
- mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
- } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
- mStorageAvailable = true;
- mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
- } else {
- // Assume the intent is one of the SMS receive intents that
- // was sent as an ordered broadcast. Check result and ACK.
- int rc = getResultCode();
- boolean success = (rc == Activity.RESULT_OK)
- || (rc == Intents.RESULT_SMS_HANDLED);
+ private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
+ mStorageAvailable = false;
+ mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+ } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
+ mStorageAvailable = true;
+ mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+ } else {
+ // Assume the intent is one of the SMS receive intents that
+ // was sent as an ordered broadcast. Check result and ACK.
+ int rc = getResultCode();
+ boolean success = (rc == Activity.RESULT_OK)
+ || (rc == Intents.RESULT_SMS_HANDLED);
- // For a multi-part message, this only ACKs the last part.
- // Previous parts were ACK'd as they were received.
- acknowledgeLastIncomingSms(success, rc, null);
- }
+ // For a multi-part message, this only ACKs the last part.
+ // Previous parts were ACK'd as they were received.
+ acknowledgeLastIncomingSms(success, rc, null);
}
-
- };
+ }
+ };
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index ed93aea..8eaf4a2 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -28,21 +28,20 @@
import android.os.AsyncResult;
import android.os.Message;
import android.os.SystemProperties;
+import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
-import android.preference.PreferenceManager;
-import android.util.Config;
-import android.util.Log;
import android.telephony.SmsManager;
import android.telephony.SmsMessage.MessageClass;
+import android.util.Config;
+import android.util.Log;
-import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
-import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
-import com.android.internal.telephony.cdma.SmsMessage;
+import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.util.HexDump;
@@ -51,20 +50,16 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.lang.Boolean;
final class CdmaSMSDispatcher extends SMSDispatcher {
private static final String TAG = "CDMA";
- private CDMAPhone mCdmaPhone;
-
private byte[] mLastDispatchedSmsFingerprint;
private byte[] mLastAcknowledgedSmsFingerprint;
CdmaSMSDispatcher(CDMAPhone phone) {
super(phone);
- mCdmaPhone = phone;
}
/**
@@ -129,7 +124,7 @@
Log.d(TAG, "Voicemail count=" + voicemailCount);
// Store the voicemail count in preferences.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
- ((CDMAPhone) mPhone).getContext());
+ mPhone.getContext());
SharedPreferences.Editor editor = sp.edit();
editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
editor.commit();
@@ -176,7 +171,7 @@
* TODO(cleanup): Why are we using a getter method for this
* (and for so many other sms fields)? Trivial getters and
* setters like this are direct violations of the style guide.
- * If the purpose is to protect agaist writes (by not
+ * If the purpose is to protect against writes (by not
* providing a setter) then any protection is illusory (and
* hence bad) for cases where the values are not primitives,
* such as this call for the header. Since this is an issue
@@ -440,7 +435,7 @@
protected void sendSms(SmsTracker tracker) {
HashMap map = tracker.mData;
- byte smsc[] = (byte[]) map.get("smsc");
+ // byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA
byte pdu[] = (byte[]) map.get("pdu");
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index d720516..3079a64 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -27,13 +27,12 @@
import android.util.Config;
import android.util.Log;
-import com.android.internal.telephony.IccUtils;
-import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
-import com.android.internal.telephony.gsm.SmsMessage;
import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import java.util.ArrayList;
import java.util.HashMap;
@@ -97,20 +96,20 @@
if (sms.isTypeZero()) {
// As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
// Displayed/Stored/Notified. They should only be acknowledged.
- Log.d(TAG, "Received short message type 0, Dont display or store it. Send Ack");
+ Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
return Intents.RESULT_SMS_HANDLED;
}
// Special case the message waiting indicator messages
if (sms.isMWISetMessage()) {
mGsmPhone.updateMessageWaitingIndicator(true);
- handled |= sms.isMwiDontStore();
+ handled = sms.isMwiDontStore();
if (Config.LOGD) {
Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
}
} else if (sms.isMWIClearMessage()) {
mGsmPhone.updateMessageWaitingIndicator(false);
- handled |= sms.isMwiDontStore();
+ handled = sms.isMwiDontStore();
if (Config.LOGD) {
Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
}
@@ -301,7 +300,7 @@
map.put("smsc", pdus.encodedScAddress);
map.put("pdu", pdus.encodedMessage);
- SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
+ SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
sendSms(tracker);
}
}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 4d071e3..21f3be4 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -637,6 +637,20 @@
}
},
+ new Test("Ticker") {
+ public void run() {
+ Notification not = new Notification(
+ R.drawable.app_gmail,
+ "New mail from joeo@example.com, on the topic of very long ticker texts",
+ System.currentTimeMillis());
+ not.setLatestEventInfo(NotificationTestList.this,
+ "A new message awaits",
+ "The contents are very interesting and important",
+ makeIntent());
+ mNM.notify(1, not);
+ }
+ },
+
new Test("Crash") {
public void run()
{