Merge "Free created FileMap when uncompressing files" 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/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7901b155..244d126 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -389,15 +389,17 @@
if (permission != null) {
pw.println(prefix + "permission=" + permission);
}
- pw.println(prefix + "uid=" + uid + " taskAffinity=" + taskAffinity);
- if (theme != 0) {
- pw.println(prefix + "theme=0x" + Integer.toHexString(theme));
- }
- pw.println(prefix + "flags=0x" + Integer.toHexString(flags)
- + " processName=" + processName);
+ pw.println(prefix + "processName=" + processName);
+ pw.println(prefix + "taskAffinity=" + taskAffinity);
+ pw.println(prefix + "uid=" + uid + " flags=0x" + Integer.toHexString(flags)
+ + " theme=0x" + Integer.toHexString(theme));
pw.println(prefix + "sourceDir=" + sourceDir);
- pw.println(prefix + "publicSourceDir=" + publicSourceDir);
- pw.println(prefix + "resourceDirs=" + resourceDirs);
+ if (!sourceDir.equals(publicSourceDir)) {
+ pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+ }
+ if (resourceDirs != null) {
+ pw.println(prefix + "resourceDirs=" + resourceDirs);
+ }
pw.println(prefix + "dataDir=" + dataDir);
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
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/hardware/Camera.java b/core/java/android/hardware/Camera.java
index a0abc6c..e432a47 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -960,6 +960,7 @@
private static final String KEY_PREVIEW_SIZE = "preview-size";
private static final String KEY_PREVIEW_FORMAT = "preview-format";
private static final String KEY_PREVIEW_FRAME_RATE = "preview-frame-rate";
+ private static final String KEY_PREVIEW_FPS_RANGE = "preview-fps-range";
private static final String KEY_PICTURE_SIZE = "picture-size";
private static final String KEY_PICTURE_FORMAT = "picture-format";
private static final String KEY_JPEG_THUMBNAIL_SIZE = "jpeg-thumbnail-size";
@@ -1219,6 +1220,22 @@
*/
public static final String METERING_MODE_SPOT = "spot";
+ /**
+ * The array index of minimum preview fps for use with {@link
+ * #getPreviewFpsRange(int[])} or {@link
+ * #getSupportedPreviewFpsRange()}.
+ * @hide
+ */
+ public static final int PREVIEW_FPS_MIN_INDEX = 0;
+
+ /**
+ * The array index of maximum preview fps for use with {@link
+ * #getPreviewFpsRange(int[])} or {@link
+ * #getSupportedPreviewFpsRange()}.
+ * @hide
+ */
+ public static final int PREVIEW_FPS_MAX_INDEX = 1;
+
// Formats for setPreviewFormat and setPictureFormat.
private static final String PIXEL_FORMAT_YUV422SP = "yuv422sp";
private static final String PIXEL_FORMAT_YUV420SP = "yuv420sp";
@@ -1482,6 +1499,64 @@
}
/**
+ * Sets the maximum and maximum preview fps. This controls the rate of
+ * preview frames received in {@link #PreviewCallback}. The minimum and
+ * maximum preview fps must be one of the elements from {@link
+ * #getSupportedPreviewFpsRange}.
+ *
+ * @param min the minimum preview fps (scaled by 1000).
+ * @param max the maximum preview fps (scaled by 1000).
+ * @throws RuntimeException if fps range is invalid.
+ * @see #setPreviewCallbackWithBuffer(Camera.PreviewCallback)
+ * @see #getSupportedPreviewFpsRange()
+ * @hide
+ */
+ public void setPreviewFpsRange(int min, int max) {
+ set(KEY_PREVIEW_FPS_RANGE, "" + min + "," + max);
+ }
+
+ /**
+ * Returns the current minimum and maximum preview fps. The values are
+ * one of the elements returned by {@link #getSupportedPreviewFpsRange}.
+ *
+ * @return range the minimum and maximum preview fps (scaled by 1000).
+ * @see #PREVIEW_FPS_MIN_INDEX
+ * @see #PREVIEW_FPS_MAX_INDEX
+ * @see #getSupportedPreviewFpsRange()
+ * @hide
+ */
+ public void getPreviewFpsRange(int[] range) {
+ if (range == null || range.length != 2) {
+ throw new IllegalArgumentException(
+ "range must be an float array with two elements.");
+ }
+ splitInt(get(KEY_PREVIEW_FPS_RANGE), range);
+ }
+
+ /**
+ * Gets the supported preview fps (frame-per-second) ranges. Each range
+ * contains a minimum fps and maximum fps. If minimum fps equals to
+ * maximum fps, the camera outputs frames in fixed frame rate. If not,
+ * the camera outputs frames in auto frame rate. The actual frame rate
+ * fluctuates between the minimum and the maximum. The values are
+ * multiplied by 1000 and represented in integers. For example, if frame
+ * rate is 26.623 frames per second, the value is 26623.
+ *
+ * @return a list of supported preview fps ranges. This method returns a
+ * list with at least one element. Every element is an int array
+ * of two values - minimum fps and maximum fps. The list is
+ * sorted from small to large (first by maximum fps and then
+ * minimum fps).
+ * @see #PREVIEW_FPS_MIN_INDEX
+ * @see #PREVIEW_FPS_MAX_INDEX
+ * @hide
+ */
+ public List<int[]> getSupportedPreviewFpsRange() {
+ String str = get(KEY_PREVIEW_FPS_RANGE + SUPPORTED_VALUES_SUFFIX);
+ return splitRange(str);
+ }
+
+ /**
* Sets the image format for preview pictures.
* <p>If this is never called, the default format will be
* {@link android.graphics.ImageFormat#NV21}, which
@@ -2184,10 +2259,7 @@
throw new IllegalArgumentException(
"output must be an float array with three elements.");
}
- List<Float> distances = splitFloat(get(KEY_FOCUS_DISTANCES));
- output[0] = distances.get(0);
- output[1] = distances.get(1);
- output[2] = distances.get(2);
+ splitFloat(get(KEY_FOCUS_DISTANCES), output);
}
/**
@@ -2255,19 +2327,27 @@
return substrings;
}
- // Splits a comma delimited string to an ArrayList of Float.
- // Return null if the passing string is null or the size is 0.
- private ArrayList<Float> splitFloat(String str) {
- if (str == null) return null;
+ private void splitInt(String str, int[] output) {
+ if (str == null) return;
StringTokenizer tokenizer = new StringTokenizer(str, ",");
- ArrayList<Float> substrings = new ArrayList<Float>();
+ int index = 0;
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken();
- substrings.add(Float.parseFloat(token));
+ output[index++] = Integer.parseInt(token);
}
- if (substrings.size() == 0) return null;
- return substrings;
+ }
+
+ // Splits a comma delimited string to an ArrayList of Float.
+ private void splitFloat(String str, float[] output) {
+ if (str == null) return;
+
+ StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ int index = 0;
+ while (tokenizer.hasMoreElements()) {
+ String token = tokenizer.nextToken();
+ output[index++] = Float.parseFloat(token);
+ }
}
// Returns the value of a float parameter.
@@ -2318,5 +2398,30 @@
Log.e(TAG, "Invalid size parameter string=" + str);
return null;
}
+
+ // Splits a comma delimited string to an ArrayList of int array.
+ // Example string: "(10000,26623),(10000,30000)". Return null if the
+ // passing string is null or the size is 0.
+ private ArrayList<int[]> splitRange(String str) {
+ if (str == null || str.charAt(0) != '('
+ || str.charAt(str.length() - 1) != ')') {
+ Log.e(TAG, "Invalid range list string=" + str);
+ return null;
+ }
+
+ ArrayList<int[]> rangeList = new ArrayList<int[]>();
+ int endIndex, fromIndex = 1;
+ do {
+ int[] range = new int[2];
+ endIndex = str.indexOf("),(", fromIndex);
+ if (endIndex == -1) endIndex = str.length() - 1;
+ splitInt(str.substring(fromIndex, endIndex), range);
+ rangeList.add(range);
+ fromIndex = endIndex + 3;
+ } while (endIndex != str.length() - 1);
+
+ if (rangeList.size() == 0) return null;
+ return rangeList;
+ }
};
}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index c34cb2f..eb7117b 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -240,6 +240,7 @@
if (mInterfaceName == null) {
Log.d(TAG, "CONNECTED event did not supply interface name.");
}
+ mDefaultGatewayAddr = intent.getIntExtra(Phone.DATA_GATEWAY_KEY, 0);
setDetailedState(DetailedState.CONNECTED, reason, apnName);
break;
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index a3ae01b..e4f3d5c 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -128,4 +128,19 @@
| (addrBytes[0] & 0xff);
return addr;
}
+
+ public static int v4StringToInt(String str) {
+ int result = 0;
+ String[] array = str.split("\\.");
+ if (array.length != 4) return 0;
+ try {
+ result = Integer.parseInt(array[3]);
+ result = (result << 8) + Integer.parseInt(array[2]);
+ result = (result << 8) + Integer.parseInt(array[1]);
+ result = (result << 8) + Integer.parseInt(array[0]);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ return result;
+ }
}
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/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c22f991..fc61700 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -622,6 +622,7 @@
mPackageName = null;
mContentDescription = null;
mBeforeText = null;
+ mParcelableData = null;
mText.clear();
}
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..8aed454a4 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/camera/CameraParameters.h b/include/camera/CameraParameters.h
index 99a3115..a5c7874 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -22,6 +22,21 @@
namespace android {
+struct Size {
+ int width;
+ int height;
+
+ Size() {
+ width = 0;
+ height = 0;
+ }
+
+ Size(int w, int h) {
+ width = w;
+ height = h;
+ }
+};
+
class CameraParameters
{
public:
@@ -43,12 +58,15 @@
void setPreviewSize(int width, int height);
void getPreviewSize(int *width, int *height) const;
+ void getSupportedPreviewSizes(Vector<Size> &sizes) const;
void setPreviewFrameRate(int fps);
int getPreviewFrameRate() const;
+ void getPreviewFpsRange(int *min_fps, int *max_fps) const;
void setPreviewFormat(const char *format);
const char *getPreviewFormat() const;
void setPictureSize(int width, int height);
void getPictureSize(int *width, int *height) const;
+ void getSupportedPictureSizes(Vector<Size> &sizes) const;
void setPictureFormat(const char *format);
const char *getPictureFormat() const;
@@ -65,6 +83,20 @@
// Supported preview frame sizes in pixels.
// Example value: "800x600,480x320". Read only.
static const char KEY_SUPPORTED_PREVIEW_SIZES[];
+ // The current minimum and maximum preview fps. This controls the rate of
+ // preview frames received (CAMERA_MSG_PREVIEW_FRAME). The minimum and
+ // maximum fps must be one of the elements from
+ // KEY_SUPPORTED_PREVIEW_FPS_RANGE parameter.
+ // Example value: "10500,26623"
+ static const char KEY_PREVIEW_FPS_RANGE[];
+ // The supported preview fps (frame-per-second) ranges. Each range contains
+ // a minimum fps and maximum fps. If minimum fps equals to maximum fps, the
+ // camera outputs frames in fixed frame rate. If not, the camera outputs
+ // frames in auto frame rate. The actual frame rate fluctuates between the
+ // minimum and the maximum. The list has at least one element. The list is
+ // sorted from small to large (first by maximum fps and then minimum fps).
+ // Example value: "(10500,26623),(15000,26623),(30000,30000)"
+ static const char KEY_SUPPORTED_PREVIEW_FPS_RANGE[];
// The image format for preview frames. See CAMERA_MSG_PREVIEW_FRAME in
// frameworks/base/include/camera/Camera.h.
// Example value: "yuv420sp" or PIXEL_FORMAT_XXX constants. Read/write.
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/media/libstagefright/mpeg2ts/ABitReader.h b/include/media/stagefright/foundation/ABitReader.h
similarity index 100%
rename from media/libstagefright/mpeg2ts/ABitReader.h
rename to include/media/stagefright/foundation/ABitReader.h
diff --git a/libs/camera/CameraParameters.cpp b/libs/camera/CameraParameters.cpp
index 1415493..d0ed7df 100644
--- a/libs/camera/CameraParameters.cpp
+++ b/libs/camera/CameraParameters.cpp
@@ -30,6 +30,8 @@
const char CameraParameters::KEY_SUPPORTED_PREVIEW_FORMATS[] = "preview-format-values";
const char CameraParameters::KEY_PREVIEW_FRAME_RATE[] = "preview-frame-rate";
const char CameraParameters::KEY_SUPPORTED_PREVIEW_FRAME_RATES[] = "preview-frame-rate-values";
+const char CameraParameters::KEY_PREVIEW_FPS_RANGE[] = "preview-fps-range";
+const char CameraParameters::KEY_SUPPORTED_PREVIEW_FPS_RANGE[] = "preview-fps-range-values";
const char CameraParameters::KEY_PICTURE_SIZE[] = "picture-size";
const char CameraParameters::KEY_SUPPORTED_PICTURE_SIZES[] = "picture-size-values";
const char CameraParameters::KEY_PICTURE_FORMAT[] = "picture-format";
@@ -269,24 +271,57 @@
mMap.removeItem(String8(key));
}
-static int parse_size(const char *str, int &width, int &height)
+// Parse string like "640x480" or "10000,20000"
+static int parse_pair(const char *str, int *first, int *second, char delim,
+ char **endptr = NULL)
{
- // Find the width.
+ // Find the first integer.
char *end;
int w = (int)strtol(str, &end, 10);
- // If an 'x' does not immediately follow, give up.
- if (*end != 'x')
+ // If a delimeter does not immediately follow, give up.
+ if (*end != delim) {
+ LOGE("Cannot find delimeter (%c) in str=%s", delim, str);
return -1;
+ }
- // Find the height, immediately after the 'x'.
- int h = (int)strtol(end+1, 0, 10);
+ // Find the second integer, immediately after the delimeter.
+ int h = (int)strtol(end+1, &end, 10);
- width = w;
- height = h;
+ *first = w;
+ *second = h;
+
+ if (endptr) {
+ *endptr = end;
+ }
return 0;
}
+static void parseSizesList(const char *sizesStr, Vector<Size> &sizes)
+{
+ if (sizesStr == 0) {
+ return;
+ }
+
+ char *sizeStartPtr = (char *)sizesStr;
+
+ while (true) {
+ int width, height;
+ int success = parse_pair(sizeStartPtr, &width, &height, 'x',
+ &sizeStartPtr);
+ if (success == -1 || (*sizeStartPtr != ',' && *sizeStartPtr != '\0')) {
+ LOGE("Picture sizes string \"%s\" contains invalid character.", sizesStr);
+ return;
+ }
+ sizes.push(Size(width, height));
+
+ if (*sizeStartPtr == '\0') {
+ return;
+ }
+ sizeStartPtr++;
+ }
+}
+
void CameraParameters::setPreviewSize(int width, int height)
{
char str[32];
@@ -296,19 +331,17 @@
void CameraParameters::getPreviewSize(int *width, int *height) const
{
- *width = -1;
- *height = -1;
-
+ *width = *height = -1;
// Get the current string, if it doesn't exist, leave the -1x-1
const char *p = get(KEY_PREVIEW_SIZE);
- if (p == 0)
- return;
+ if (p == 0) return;
+ parse_pair(p, width, height, 'x');
+}
- int w, h;
- if (parse_size(p, w, h) == 0) {
- *width = w;
- *height = h;
- }
+void CameraParameters::getSupportedPreviewSizes(Vector<Size> &sizes) const
+{
+ const char *previewSizesStr = get(KEY_SUPPORTED_PREVIEW_SIZES);
+ parseSizesList(previewSizesStr, sizes);
}
void CameraParameters::setPreviewFrameRate(int fps)
@@ -321,6 +354,14 @@
return getInt(KEY_PREVIEW_FRAME_RATE);
}
+void CameraParameters::getPreviewFpsRange(int *min_fps, int *max_fps) const
+{
+ *min_fps = *max_fps = -1;
+ const char *p = get(KEY_PREVIEW_FPS_RANGE);
+ if (p == 0) return;
+ parse_pair(p, min_fps, max_fps, ',');
+}
+
void CameraParameters::setPreviewFormat(const char *format)
{
set(KEY_PREVIEW_FORMAT, format);
@@ -340,19 +381,17 @@
void CameraParameters::getPictureSize(int *width, int *height) const
{
- *width = -1;
- *height = -1;
-
+ *width = *height = -1;
// Get the current string, if it doesn't exist, leave the -1x-1
const char *p = get(KEY_PICTURE_SIZE);
- if (p == 0)
- return;
+ if (p == 0) return;
+ parse_pair(p, width, height, 'x');
+}
- int w, h;
- if (parse_size(p, w, h) == 0) {
- *width = w;
- *height = h;
- }
+void CameraParameters::getSupportedPictureSizes(Vector<Size> &sizes) const
+{
+ const char *pictureSizesStr = get(KEY_SUPPORTED_PICTURE_SIZES);
+ parseSizesList(pictureSizesStr, sizes);
}
void CameraParameters::setPictureFormat(const char *format)
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/java/android/media/BassBoost.java b/media/java/android/media/BassBoost.java
index 73c1751..476b056 100644
--- a/media/java/android/media/BassBoost.java
+++ b/media/java/android/media/BassBoost.java
@@ -99,7 +99,7 @@
UnsupportedOperationException, RuntimeException {
super(EFFECT_TYPE_BASS_BOOST, EFFECT_TYPE_NULL, priority, audioSession);
- short[] value = new short[1];
+ int[] value = new int[1];
checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value));
mStrengthSupported = (value[0] != 0);
}
diff --git a/media/java/android/media/Virtualizer.java b/media/java/android/media/Virtualizer.java
index d03c2a8..b08f36e 100644
--- a/media/java/android/media/Virtualizer.java
+++ b/media/java/android/media/Virtualizer.java
@@ -100,7 +100,7 @@
UnsupportedOperationException, RuntimeException {
super(EFFECT_TYPE_VIRTUALIZER, EFFECT_TYPE_NULL, priority, audioSession);
- short[] value = new short[1];
+ int[] value = new int[1];
checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value));
mStrengthSupported = (value[0] != 0);
}
diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index a70bdff..bcd646a 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -1626,9 +1626,15 @@
switch (param){
case BASSBOOST_PARAM_STRENGTH_SUPPORTED:
+ if (*pValueSize != sizeof(uint32_t)){
+ LOGV("\tLVM_ERROR : BassBoost_getParameter() invalid pValueSize %d", *pValueSize);
+ return -EINVAL;
+ }
+ *pValueSize = sizeof(uint32_t);
+ break;
case BASSBOOST_PARAM_STRENGTH:
if (*pValueSize != sizeof(int16_t)){
- LOGV("\tLVM_ERROR : BassBoost_getParameter() invalid pValueSize2 %d", *pValueSize);
+ LOGV("\tLVM_ERROR : BassBoost_getParameter() invalid pValueSize %d", *pValueSize);
return -EINVAL;
}
*pValueSize = sizeof(int16_t);
@@ -1736,9 +1742,16 @@
switch (param){
case VIRTUALIZER_PARAM_STRENGTH_SUPPORTED:
+ if (*pValueSize != sizeof(uint32_t)){
+ LOGV("\tLVM_ERROR : Virtualizer_getParameter() invalid pValueSize %d",*pValueSize);
+ return -EINVAL;
+ }
+ *pValueSize = sizeof(uint32_t);
+ break;
+
case VIRTUALIZER_PARAM_STRENGTH:
if (*pValueSize != sizeof(int16_t)){
- LOGV("\tLVM_ERROR : Virtualizer_getParameter() invalid pValueSize2 %d",*pValueSize);
+ LOGV("\tLVM_ERROR : Virtualizer_getParameter() invalid pValueSize %d",*pValueSize);
return -EINVAL;
}
*pValueSize = sizeof(int16_t);
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/Android.mk b/media/libstagefright/Android.mk
index b8b2f3f..86fa668 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -39,6 +39,7 @@
TimedEventQueue.cpp \
Utils.cpp \
WAVExtractor.cpp \
+ avc_utils.cpp \
string.cpp
LOCAL_C_INCLUDES:= \
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/avc_utils.cpp b/media/libstagefright/avc_utils.cpp
new file mode 100644
index 0000000..511ae12
--- /dev/null
+++ b/media/libstagefright/avc_utils.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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 "include/avc_utils.h"
+
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+static unsigned parseUE(ABitReader *br) {
+ unsigned numZeroes = 0;
+ while (br->getBits(1) == 0) {
+ ++numZeroes;
+ }
+
+ unsigned x = br->getBits(numZeroes);
+
+ return x + (1u << numZeroes) - 1;
+}
+
+// Determine video dimensions from the sequence parameterset.
+void FindAVCDimensions(
+ const sp<ABuffer> &seqParamSet, int32_t *width, int32_t *height) {
+ ABitReader br(seqParamSet->data() + 1, seqParamSet->size() - 1);
+
+ unsigned profile_idc = br.getBits(8);
+ br.skipBits(16);
+ parseUE(&br); // seq_parameter_set_id
+
+ if (profile_idc == 100 || profile_idc == 110
+ || profile_idc == 122 || profile_idc == 244
+ || profile_idc == 44 || profile_idc == 83 || profile_idc == 86) {
+ unsigned chroma_format_idc = parseUE(&br);
+ if (chroma_format_idc == 3) {
+ br.skipBits(1); // residual_colour_transform_flag
+ }
+ parseUE(&br); // bit_depth_luma_minus8
+ parseUE(&br); // bit_depth_chroma_minus8
+ br.skipBits(1); // qpprime_y_zero_transform_bypass_flag
+ CHECK_EQ(br.getBits(1), 0u); // seq_scaling_matrix_present_flag
+ }
+
+ parseUE(&br); // log2_max_frame_num_minus4
+ unsigned pic_order_cnt_type = parseUE(&br);
+
+ if (pic_order_cnt_type == 0) {
+ parseUE(&br); // log2_max_pic_order_cnt_lsb_minus4
+ } else if (pic_order_cnt_type == 1) {
+ // offset_for_non_ref_pic, offset_for_top_to_bottom_field and
+ // offset_for_ref_frame are technically se(v), but since we are
+ // just skipping over them the midpoint does not matter.
+
+ br.getBits(1); // delta_pic_order_always_zero_flag
+ parseUE(&br); // offset_for_non_ref_pic
+ parseUE(&br); // offset_for_top_to_bottom_field
+
+ unsigned num_ref_frames_in_pic_order_cnt_cycle = parseUE(&br);
+ for (unsigned i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; ++i) {
+ parseUE(&br); // offset_for_ref_frame
+ }
+ }
+
+ parseUE(&br); // num_ref_frames
+ br.getBits(1); // gaps_in_frame_num_value_allowed_flag
+
+ unsigned pic_width_in_mbs_minus1 = parseUE(&br);
+ unsigned pic_height_in_map_units_minus1 = parseUE(&br);
+ unsigned frame_mbs_only_flag = br.getBits(1);
+
+ *width = pic_width_in_mbs_minus1 * 16 + 16;
+
+ *height = (2 - frame_mbs_only_flag)
+ * (pic_height_in_map_units_minus1 * 16 + 16);
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp
index 389180c..52a391f 100644
--- a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp
+++ b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp
@@ -338,10 +338,15 @@
MediaBuffer *outputBuffer;
CHECK_EQ(OK, mGroup->acquire_buffer(&outputBuffer));
+ uint8_t *outPtr = (uint8_t *) outputBuffer->data();
+ uint32_t dataLength = outputBuffer->size();
- // Add 4 bytes for the start code 0x00000001
- uint8_t *outPtr = (uint8_t *) outputBuffer->data() + 4;
- uint32_t dataLength = outputBuffer->size() - 4;
+ if (!mSpsPpsHeaderReceived && mNumInputFrames < 0) {
+ // 4 bytes are reserved for holding the start code 0x00000001
+ // of the sequence parameter set at the beginning.
+ outPtr += 4;
+ dataLength -= 4;
+ }
int32_t type;
AVCEnc_Status encoderStatus = AVCENC_SUCCESS;
@@ -358,7 +363,7 @@
switch (type) {
case AVC_NALTYPE_SPS:
++mNumInputFrames;
- memcpy(outputBuffer->data(), "\x00\x00\x00\x01", 4);
+ memcpy((uint8_t *)outputBuffer->data(), "\x00\x00\x00\x01", 4);
outputBuffer->set_range(0, dataLength + 4);
outPtr += (dataLength + 4); // 4 bytes for next start code
dataLength = outputBuffer->size() -
diff --git a/media/libstagefright/mpeg2ts/ABitReader.cpp b/media/libstagefright/foundation/ABitReader.cpp
similarity index 100%
rename from media/libstagefright/mpeg2ts/ABitReader.cpp
rename to media/libstagefright/foundation/ABitReader.cpp
diff --git a/media/libstagefright/foundation/Android.mk b/media/libstagefright/foundation/Android.mk
index 35eea7e..f6a8a52 100644
--- a/media/libstagefright/foundation/Android.mk
+++ b/media/libstagefright/foundation/Android.mk
@@ -3,6 +3,7 @@
LOCAL_SRC_FILES:= \
AAtomizer.cpp \
+ ABitReader.cpp \
ABuffer.cpp \
ADebug.cpp \
AHandler.cpp \
diff --git a/media/libstagefright/include/avc_utils.h b/media/libstagefright/include/avc_utils.h
new file mode 100644
index 0000000..cc405b5
--- /dev/null
+++ b/media/libstagefright/include/avc_utils.h
@@ -0,0 +1,30 @@
+/*
+ * 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 AVC_UTILS_H_
+
+#define AVC_UTILS_H_
+
+#include <media/stagefright/foundation/ABuffer.h>
+
+namespace android {
+
+void FindAVCDimensions(
+ const sp<ABuffer> &seqParamSet, int32_t *width, int32_t *height);
+
+} // namespace android
+
+#endif // AVC_UTILS_H_
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index d05975d..26a0fb3 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -16,9 +16,10 @@
#include "ATSParser.h"
-#include "ABitReader.h"
#include "AnotherPacketSource.h"
+#include "include/avc_utils.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>
@@ -473,60 +474,6 @@
}
}
-static unsigned parseUE(ABitReader *br) {
- unsigned numZeroes = 0;
- while (br->getBits(1) == 0) {
- ++numZeroes;
- }
-
- unsigned x = br->getBits(numZeroes);
-
- return x + (1u << numZeroes) - 1;
-}
-
-// Determine video dimensions from the sequence parameterset.
-static void FindDimensions(
- const sp<ABuffer> seqParamSet, int32_t *width, int32_t *height) {
- ABitReader br(seqParamSet->data() + 1, seqParamSet->size() - 1);
-
- unsigned profile_idc = br.getBits(8);
- br.skipBits(16);
- parseUE(&br); // seq_parameter_set_id
-
- if (profile_idc == 100 || profile_idc == 110
- || profile_idc == 122 || profile_idc == 144) {
- TRESPASS();
- }
-
- parseUE(&br); // log2_max_frame_num_minus4
- unsigned pic_order_cnt_type = parseUE(&br);
-
- if (pic_order_cnt_type == 0) {
- parseUE(&br); // log2_max_pic_order_cnt_lsb_minus4
- } else if (pic_order_cnt_type == 1) {
- br.getBits(1); // delta_pic_order_always_zero_flag
- parseUE(&br); // offset_for_non_ref_pic
- parseUE(&br); // offset_for_top_to_bottom_field
-
- unsigned num_ref_frames_in_pic_order_cnt_cycle = parseUE(&br);
- for (unsigned i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; ++i) {
- parseUE(&br); // offset_for_ref_frame
- }
- }
-
- parseUE(&br); // num_ref_frames
- br.getBits(1); // gaps_in_frame_num_value_allowed_flag
-
- unsigned pic_width_in_mbs_minus1 = parseUE(&br);
- unsigned pic_height_in_map_units_minus1 = parseUE(&br);
- unsigned frame_mbs_only_flag = br.getBits(1);
-
- *width = pic_width_in_mbs_minus1 * 16 + 16;
-
- *height = (2 - frame_mbs_only_flag)
- * (pic_height_in_map_units_minus1 * 16 + 16);
-}
-
static sp<ABuffer> MakeAVCCodecSpecificData(
const sp<ABuffer> &buffer, int32_t *width, int32_t *height) {
const uint8_t *data = buffer->data();
@@ -537,7 +484,7 @@
return NULL;
}
- FindDimensions(seqParamSet, width, height);
+ FindAVCDimensions(seqParamSet, width, height);
size_t stopOffset;
sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
diff --git a/media/libstagefright/mpeg2ts/Android.mk b/media/libstagefright/mpeg2ts/Android.mk
index b6772eb..3544b4c 100644
--- a/media/libstagefright/mpeg2ts/Android.mk
+++ b/media/libstagefright/mpeg2ts/Android.mk
@@ -3,7 +3,6 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- ABitReader.cpp \
AnotherPacketSource.cpp \
ATSParser.cpp \
MPEG2TSExtractor.cpp \
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 b2d697b..8c56cb7 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -18,8 +18,11 @@
#include "ASessionDescription.h"
+#include "avc_utils.h"
+
#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>
@@ -96,7 +99,11 @@
return buffer;
}
-static sp<ABuffer> MakeAVCCodecSpecificData(const char *params) {
+static sp<ABuffer> MakeAVCCodecSpecificData(
+ const char *params, int32_t *width, int32_t *height) {
+ *width = 0;
+ *height = 0;
+
AString val;
if (!GetAttribute(params, "profile-level-id", &val)) {
return NULL;
@@ -178,6 +185,11 @@
memcpy(out, nal->data(), nal->size());
out += nal->size();
+
+ if (i == 0) {
+ FindAVCDimensions(nal, width, height);
+ LOG(INFO) << "dimensions " << *width << "x" << *height;
+ }
}
*out++ = numPicParameterSets;
@@ -193,7 +205,7 @@
out += nal->size();
}
- hexdump(csd->data(), csd->size());
+ // hexdump(csd->data(), csd->size());
return csd;
}
@@ -230,7 +242,162 @@
csd->data()[sizeof(kStaticESDS)] = (x >> 8) & 0xff;
csd->data()[sizeof(kStaticESDS) + 1] = x & 0xff;
- hexdump(csd->data(), csd->size());
+ // hexdump(csd->data(), csd->size());
+
+ 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;
}
@@ -260,22 +427,32 @@
int32_t width, height;
if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
- // TODO: extract dimensions from sequence parameter set.
+ width = -1;
+ height = -1;
+ }
+
+ int32_t encWidth, encHeight;
+ sp<ABuffer> codecSpecificData =
+ MakeAVCCodecSpecificData(params.c_str(), &encWidth, &encHeight);
+
+ if (codecSpecificData != NULL) {
+ if (width < 0) {
+ // If no explicit width/height given in the sdp, use the dimensions
+ // extracted from the first sequence parameter set.
+ width = encWidth;
+ height = encHeight;
+ }
+
+ mFormat->setData(
+ kKeyAVCC, 0,
+ codecSpecificData->data(), codecSpecificData->size());
+ } else if (width < 0) {
mInitCheck = ERROR_UNSUPPORTED;
return;
}
mFormat->setInt32(kKeyWidth, width);
mFormat->setInt32(kKeyHeight, height);
-
- sp<ABuffer> codecSpecificData =
- MakeAVCCodecSpecificData(params.c_str());
-
- if (codecSpecificData != NULL) {
- mFormat->setData(
- kKeyAVCC, 0,
- codecSpecificData->data(), codecSpecificData->size());
- }
} else if (!strncmp(desc.c_str(), "H263-2000/", 10)
|| !strncmp(desc.c_str(), "H263-1998/", 10)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
@@ -330,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/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index 664dfa5..7e23422 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -348,7 +349,8 @@
}
public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
- String reason, String apn, String[] apnTypes, String interfaceName, int networkType) {
+ String reason, String apn, String[] apnTypes, String interfaceName, int networkType,
+ String gateway) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
@@ -372,7 +374,7 @@
}
}
broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
- apnTypes, interfaceName);
+ apnTypes, interfaceName, gateway);
}
public void notifyDataConnectionFailed(String reason) {
@@ -535,7 +537,7 @@
private void broadcastDataConnectionStateChanged(int state,
boolean isDataConnectivityPossible,
- String reason, String apn, String[] apnTypes, String interfaceName) {
+ String reason, String apn, String[] apnTypes, String interfaceName, String gateway) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
@@ -558,6 +560,12 @@
}
intent.putExtra(Phone.DATA_APN_TYPES_KEY, types);
intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName);
+ int gatewayAddr = 0;
+ if (gateway != null) {
+ gatewayAddr = NetworkUtils.v4StringToInt(gateway);
+ }
+ intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr);
+
mContext.sendStickyBroadcast(intent);
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 9d31502..b37cd89 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4093,16 +4093,23 @@
}
}
- void grantUriPermissionLocked(int callingUid,
- String targetPkg, Uri uri, int modeFlags, ActivityRecord activity) {
+ /**
+ * Check if the targetPkg can be granted permission to access uri by
+ * the callingUid using the given modeFlags. Throws a security exception
+ * if callingUid is not allowed to do this. Returns the uid of the target
+ * if the URI permission grant should be performed; returns -1 if it is not
+ * needed (for example targetPkg already has permission to access the URI).
+ */
+ int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) {
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (modeFlags == 0) {
- return;
+ return -1;
}
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Requested grant " + targetPkg + " permission to " + uri);
+ "Checking grant " + targetPkg + " permission to " + uri);
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -4110,7 +4117,7 @@
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"Can't grant URI permission for non-content URI: " + uri);
- return;
+ return -1;
}
String name = uri.getAuthority();
@@ -4127,7 +4134,7 @@
}
if (pi == null) {
Slog.w(TAG, "No content provider found for: " + name);
- return;
+ return -1;
}
int targetUid;
@@ -4136,10 +4143,10 @@
if (targetUid < 0) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"Can't grant URI permission no uid for: " + targetPkg);
- return;
+ return -1;
}
} catch (RemoteException ex) {
- return;
+ return -1;
}
// First... does the target actually need this permission?
@@ -4147,7 +4154,7 @@
// No need to grant the target this permission.
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"Target " + targetPkg + " already has full permission to " + uri);
- return;
+ return -1;
}
// Second... is the provider allowing granting of URI permissions?
@@ -4184,12 +4191,23 @@
}
}
- // Okay! So here we are: the caller has the assumed permission
+ return targetUid;
+ }
+
+ void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
+ Uri uri, int modeFlags, UriPermissionOwner owner) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (modeFlags == 0) {
+ return;
+ }
+
+ // So here we are: the caller has the assumed permission
// to the uri, and the target doesn't. Let's now give this to
// the target.
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Granting " + targetPkg + " permission to " + uri);
+ "Granting " + targetPkg + "/" + targetUid + " permission to " + uri);
HashMap<Uri, UriPermission> targetUris
= mGrantedUriPermissions.get(targetUid);
@@ -4205,39 +4223,65 @@
}
perm.modeFlags |= modeFlags;
- if (activity == null) {
+ if (owner == null) {
perm.globalModeFlags |= modeFlags;
} else if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
- perm.readActivities.add(activity);
- if (activity.readUriPermissions == null) {
- activity.readUriPermissions = new HashSet<UriPermission>();
- }
- activity.readUriPermissions.add(perm);
+ perm.readOwners.add(owner);
+ owner.addReadPermission(perm);
} else if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
- perm.writeActivities.add(activity);
- if (activity.writeUriPermissions == null) {
- activity.writeUriPermissions = new HashSet<UriPermission>();
- }
- activity.writeUriPermissions.add(perm);
+ perm.writeOwners.add(owner);
+ owner.addWritePermission(perm);
}
}
- void grantUriPermissionFromIntentLocked(int callingUid,
- String targetPkg, Intent intent, ActivityRecord activity) {
+ void grantUriPermissionLocked(int callingUid,
+ String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+ int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+ if (targetUid < 0) {
+ return;
+ }
+
+ grantUriPermissionUncheckedLocked(targetUid, targetPkg, uri, modeFlags, owner);
+ }
+
+ /**
+ * Like checkGrantUriPermissionLocked, but takes an Intent.
+ */
+ int checkGrantUriPermissionFromIntentLocked(int callingUid,
+ String targetPkg, Intent intent) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Grant URI perm to " + (intent != null ? intent.getData() : null)
+ "Checking URI perm to " + (intent != null ? intent.getData() : null)
+ " from " + intent + "; flags=0x"
+ Integer.toHexString(intent != null ? intent.getFlags() : 0));
if (intent == null) {
- return;
+ return -1;
}
Uri data = intent.getData();
if (data == null) {
+ return -1;
+ }
+ return checkGrantUriPermissionLocked(callingUid, targetPkg, data,
+ intent.getFlags());
+ }
+
+ /**
+ * Like grantUriPermissionUncheckedLocked, but takes an Intent.
+ */
+ void grantUriPermissionUncheckedFromIntentLocked(int targetUid,
+ String targetPkg, Intent intent, UriPermissionOwner owner) {
+ grantUriPermissionUncheckedLocked(targetUid, targetPkg, intent.getData(),
+ intent.getFlags(), owner);
+ }
+
+ void grantUriPermissionFromIntentLocked(int callingUid,
+ String targetPkg, Intent intent, UriPermissionOwner owner) {
+ int targetUid = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg, intent);
+ if (targetUid < 0) {
return;
}
- grantUriPermissionLocked(callingUid, targetPkg, data,
- intent.getFlags(), activity);
+
+ grantUriPermissionUncheckedFromIntentLocked(targetUid, targetPkg, intent, owner);
}
public void grantUriPermission(IApplicationThread caller, String targetPkg,
@@ -8187,18 +8231,23 @@
return;
}
- int i = 0;
- while (i < N) {
+ while (r.pendingStarts.size() > 0) {
try {
- ServiceRecord.StartItem si = r.pendingStarts.get(i);
+ ServiceRecord.StartItem si = r.pendingStarts.remove(0);
if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to service: "
+ r.name + " " + r.intent + " args=" + si.intent);
- if (si.intent == null && N > 1) {
+ if (si.intent == null) {
// If somehow we got a dummy start at the front, then
// just drop it here.
- i++;
continue;
}
+ si.deliveredTime = SystemClock.uptimeMillis();
+ r.deliveredStarts.add(si);
+ si.deliveryCount++;
+ if (si.targetPermissionUid >= 0) {
+ grantUriPermissionUncheckedFromIntentLocked(si.targetPermissionUid,
+ r.packageName, si.intent, si);
+ }
bumpServiceExecutingLocked(r);
if (!oomAdjusted) {
oomAdjusted = true;
@@ -8212,10 +8261,6 @@
flags |= Service.START_FLAG_REDELIVERY;
}
r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent);
- si.deliveredTime = SystemClock.uptimeMillis();
- r.deliveredStarts.add(si);
- si.deliveryCount++;
- i++;
} catch (RemoteException e) {
// Remote process gone... we'll let the normal cleanup take
// care of this.
@@ -8225,14 +8270,6 @@
break;
}
}
- if (i == N) {
- r.pendingStarts.clear();
- } else {
- while (i > 0) {
- i--;
- r.pendingStarts.remove(i);
- }
- }
}
private final boolean requestServiceBindingLocked(ServiceRecord r,
@@ -8315,7 +8352,7 @@
if (r.lastStartId < 1) {
r.lastStartId = 1;
}
- r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, null));
+ r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, null, -1));
}
sendServiceArgsLocked(r, true);
@@ -8335,6 +8372,7 @@
if (N > 0) {
for (int i=N-1; i>=0; i--) {
ServiceRecord.StartItem si = r.deliveredStarts.get(i);
+ si.removeUriPermissionsLocked();
if (si.intent == null) {
// We'll generate this again if needed.
} else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT
@@ -8574,7 +8612,7 @@
r.foregroundNoti = null;
// Clear start entries.
- r.deliveredStarts.clear();
+ r.clearDeliveredStartsLocked();
r.pendingStarts.clear();
if (r.app != null) {
@@ -8634,6 +8672,8 @@
? res.permission : "private to package");
}
ServiceRecord r = res.record;
+ int targetPermissionUid = checkGrantUriPermissionFromIntentLocked(
+ callingUid, r.packageName, service);
if (unscheduleServiceRestartLocked(r)) {
if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: "
+ r.shortName);
@@ -8644,7 +8684,8 @@
if (r.lastStartId < 1) {
r.lastStartId = 1;
}
- r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, service));
+ r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId,
+ service, targetPermissionUid));
r.lastActivity = SystemClock.uptimeMillis();
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked();
@@ -8769,7 +8810,9 @@
ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
if (si != null) {
while (r.deliveredStarts.size() > 0) {
- if (r.deliveredStarts.remove(0) == si) {
+ ServiceRecord.StartItem cur = r.deliveredStarts.remove(0);
+ cur.removeUriPermissionsLocked();
+ if (cur == si) {
break;
}
}
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 79756a7..80a41b7 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -29,6 +29,7 @@
import android.os.Bundle;
import android.os.Message;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.EventLog;
import android.util.Log;
@@ -43,7 +44,7 @@
/**
* An entry in the history stack, representing an activity.
*/
-class ActivityRecord extends IApplicationToken.Stub {
+class ActivityRecord extends IApplicationToken.Stub implements UriPermissionOwner {
final ActivityManagerService service; // owner
final ActivityStack stack; // owner
final ActivityInfo info; // all about me
@@ -340,16 +341,22 @@
* Deliver a new Intent to an existing activity, so that its onNewIntent()
* method will be called at the proper time.
*/
- final void deliverNewIntentLocked(Intent intent) {
+ final void deliverNewIntentLocked(int callingUid, Intent intent) {
boolean sent = false;
if (state == ActivityState.RESUMED
&& app != null && app.thread != null) {
try {
ArrayList<Intent> ar = new ArrayList<Intent>();
- ar.add(new Intent(intent));
+ intent = new Intent(intent);
+ ar.add(intent);
+ service.grantUriPermissionFromIntentLocked(callingUid, packageName,
+ intent, this);
app.thread.scheduleNewIntent(ar, this);
sent = true;
- } catch (Exception e) {
+ } catch (RemoteException e) {
+ Slog.w(ActivityManagerService.TAG,
+ "Exception thrown sending new intent to " + this, e);
+ } catch (NullPointerException e) {
Slog.w(ActivityManagerService.TAG,
"Exception thrown sending new intent to " + this, e);
}
@@ -362,23 +369,25 @@
void removeUriPermissionsLocked() {
if (readUriPermissions != null) {
for (UriPermission perm : readUriPermissions) {
- perm.readActivities.remove(this);
- if (perm.readActivities.size() == 0 && (perm.globalModeFlags
+ perm.readOwners.remove(this);
+ if (perm.readOwners.size() == 0 && (perm.globalModeFlags
&Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) {
perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
- service.removeUriPermissionIfNeededLocked(perm);
+ service.removeUriPermissionIfNeededLocked(perm);
}
}
+ readUriPermissions = null;
}
if (writeUriPermissions != null) {
for (UriPermission perm : writeUriPermissions) {
- perm.writeActivities.remove(this);
- if (perm.writeActivities.size() == 0 && (perm.globalModeFlags
+ perm.writeOwners.remove(this);
+ if (perm.writeOwners.size() == 0 && (perm.globalModeFlags
&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) {
perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
service.removeUriPermissionIfNeededLocked(perm);
}
}
+ writeUriPermissions = null;
}
}
@@ -569,6 +578,37 @@
state == ActivityState.RESUMED;
}
+ @Override
+ public void addReadPermission(UriPermission perm) {
+ if (readUriPermissions == null) {
+ readUriPermissions = new HashSet<UriPermission>();
+ }
+ readUriPermissions.add(perm);
+ }
+
+ @Override
+ public void addWritePermission(UriPermission perm) {
+ if (writeUriPermissions == null) {
+ writeUriPermissions = new HashSet<UriPermission>();
+ }
+ writeUriPermissions.add(perm);
+ }
+
+ @Override
+ public void removeReadPermission(UriPermission perm) {
+ readUriPermissions.remove(perm);
+ if (readUriPermissions.size() == 0) {
+ readUriPermissions = null;
+ }
+ }
+
+ @Override
+ public void removeWritePermission(UriPermission perm) {
+ writeUriPermissions.remove(perm);
+ if (writeUriPermissions.size() == 0) {
+ writeUriPermissions = null;
+ }
+ }
public String toString() {
if (stringName != null) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index de7b15c..a5f7e96 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -2033,16 +2033,6 @@
}
}
- if (grantedUriPermissions != null && callingUid > 0) {
- for (int i=0; i<grantedUriPermissions.length; i++) {
- mService.grantUriPermissionLocked(callingUid, r.packageName,
- grantedUriPermissions[i], grantedMode, r);
- }
- }
-
- mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
- intent, r);
-
if (sourceRecord == null) {
// This activity is not being started from another... in this
// case we -always- start a new task.
@@ -2150,7 +2140,7 @@
top.task.setIntent(r.intent, r.info);
}
logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
- top.deliverNewIntentLocked(r.intent);
+ top.deliverNewIntentLocked(callingUid, r.intent);
} else {
// A special case: we need to
// start the activity because it is not currently
@@ -2175,7 +2165,7 @@
if (taskTop.frontOfTask) {
taskTop.task.setIntent(r.intent, r.info);
}
- taskTop.deliverNewIntentLocked(r.intent);
+ taskTop.deliverNewIntentLocked(callingUid, r.intent);
} else if (!r.intent.filterEquals(taskTop.task.intent)) {
// In this case we are launching the root activity
// of the task, but with a different intent. We
@@ -2243,7 +2233,7 @@
// is the case, so this is it!
return START_RETURN_INTENT_TO_CALLER;
}
- top.deliverNewIntentLocked(r.intent);
+ top.deliverNewIntentLocked(callingUid, r.intent);
return START_DELIVERED_TO_TOP;
}
}
@@ -2288,7 +2278,7 @@
sourceRecord.task.taskId, r, launchFlags, true);
if (top != null) {
logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
- top.deliverNewIntentLocked(r.intent);
+ top.deliverNewIntentLocked(callingUid, r.intent);
// For paranoia, make sure we have correctly
// resumed the top activity.
if (doResume) {
@@ -2305,7 +2295,7 @@
if (where >= 0) {
ActivityRecord top = moveActivityToFrontLocked(where);
logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
- top.deliverNewIntentLocked(r.intent);
+ top.deliverNewIntentLocked(callingUid, r.intent);
if (doResume) {
resumeTopActivityLocked(null);
}
@@ -2333,6 +2323,17 @@
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ " in new guessed " + r.task);
}
+
+ if (grantedUriPermissions != null && callingUid > 0) {
+ for (int i=0; i<grantedUriPermissions.length; i++) {
+ mService.grantUriPermissionLocked(callingUid, r.packageName,
+ grantedUriPermissions[i], grantedMode, r);
+ }
+ }
+
+ mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
+ intent, r);
+
if (newTask) {
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId);
}
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index c3f0b3e..bac21b1 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -26,6 +26,7 @@
import android.os.IBinder;
import android.os.SystemClock;
import android.util.PrintWriterPrinter;
+import android.util.TimeUtils;
import java.io.PrintWriter;
import java.util.List;
@@ -73,61 +74,65 @@
ActivityInfo curReceiver; // info about the receiver that is currently running.
void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + this);
- pw.println(prefix + intent);
+ final long now = SystemClock.uptimeMillis();
+
+ pw.print(prefix); pw.println(this);
+ pw.print(prefix); pw.println(intent);
if (sticky) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
- pw.println(prefix + "extras: " + bundle.toString());
+ pw.print(prefix); pw.print("extras: "); pw.println(bundle.toString());
}
}
- pw.println(prefix + "proc=" + callerApp);
- pw.println(prefix + "caller=" + callerPackage
- + " callingPid=" + callingPid
- + " callingUid=" + callingUid);
+ pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.println(" ");
+ pw.println(callerApp != null ? callerApp.toShortString() : "null");
+ pw.print(" pid="); pw.print(callingPid);
+ pw.print(" uid="); pw.println(callingUid);
if (requiredPermission != null) {
- pw.println(prefix + "requiredPermission=" + requiredPermission);
+ pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
}
- pw.println(prefix + "dispatchTime=" + dispatchTime + " ("
- + (SystemClock.uptimeMillis()-dispatchTime) + "ms since now)");
+ pw.print(prefix); pw.print("dispatchTime=");
+ TimeUtils.formatDuration(dispatchTime, now, pw);
if (finishTime != 0) {
- pw.println(prefix + "finishTime=" + finishTime + " ("
- + (SystemClock.uptimeMillis()-finishTime) + "ms since now)");
+ pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw);
} else {
- pw.println(prefix + "receiverTime=" + receiverTime + " ("
- + (SystemClock.uptimeMillis()-receiverTime) + "ms since now)");
+ pw.print(" receiverTime="); TimeUtils.formatDuration(receiverTime, now, pw);
}
+ pw.println("");
if (anrCount != 0) {
- pw.println(prefix + "anrCount=" + anrCount);
+ pw.print(prefix); pw.print("anrCount="); pw.println(anrCount);
}
if (resultTo != null || resultCode != -1 || resultData != null) {
- pw.println(prefix + "resultTo=" + resultTo
- + " resultCode=" + resultCode + " resultData=" + resultData);
+ pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
+ pw.print(" resultCode="); pw.print(resultCode);
+ pw.print(" resultData="); pw.println(resultData);
}
if (resultExtras != null) {
- pw.println(prefix + "resultExtras=" + resultExtras);
+ pw.print(prefix); pw.print("resultExtras="); pw.println(resultExtras);
}
if (resultAbort || ordered || sticky || initialSticky) {
- pw.println(prefix + "resultAbort=" + resultAbort
- + " ordered=" + ordered + " sticky=" + sticky
- + " initialSticky=" + initialSticky);
+ pw.print(prefix); pw.print("resultAbort="); pw.print(resultAbort);
+ pw.print(" ordered="); pw.print(ordered);
+ pw.print(" sticky="); pw.print(sticky);
+ pw.print(" initialSticky="); pw.println(initialSticky);
}
if (nextReceiver != 0 || receiver != null) {
- pw.println(prefix + "nextReceiver=" + nextReceiver
- + " receiver=" + receiver);
+ pw.print(prefix); pw.print("nextReceiver="); pw.print(nextReceiver);
+ pw.print(" receiver="); pw.println(receiver);
}
if (curFilter != null) {
- pw.println(prefix + "curFilter=" + curFilter);
+ pw.print(prefix); pw.print("curFilter="); pw.println(curFilter);
}
if (curReceiver != null) {
- pw.println(prefix + "curReceiver=" + curReceiver);
+ pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver);
}
if (curApp != null) {
- pw.println(prefix + "curApp=" + curApp);
- pw.println(prefix + "curComponent="
- + (curComponent != null ? curComponent.toShortString() : "--"));
+ pw.print(prefix); pw.print("curApp="); pw.println(curApp);
+ pw.print(prefix); pw.print("curComponent=");
+ pw.println((curComponent != null ? curComponent.toShortString() : "--"));
if (curReceiver != null && curReceiver.applicationInfo != null) {
- pw.println(prefix + "curSourceDir=" + curReceiver.applicationInfo.sourceDir);
+ pw.print(prefix); pw.print("curSourceDir=");
+ pw.println(curReceiver.applicationInfo.sourceDir);
}
}
String stateStr = " (?)";
@@ -137,13 +142,14 @@
case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break;
case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break;
}
- pw.println(prefix + "state=" + state + stateStr);
+ pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr);
final int N = receivers != null ? receivers.size() : 0;
String p2 = prefix + " ";
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
for (int i=0; i<N; i++) {
Object o = receivers.get(i);
- pw.println(prefix + "Receiver #" + i + ": " + o);
+ pw.print(prefix); pw.print("Receiver #"); pw.print(i);
+ pw.print(": "); pw.println(o);
if (o instanceof BroadcastFilter)
((BroadcastFilter)o).dumpBrief(pw, p2);
else if (o instanceof ResolveInfo)
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index ab5a78d..255fbe3 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -36,6 +36,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -43,6 +44,12 @@
* A running application service.
*/
class ServiceRecord extends Binder {
+ // Maximum number of delivery attempts before giving up.
+ static final int MAX_DELIVERY_COUNT = 3;
+
+ // Maximum number of times it can fail during execution before giving up.
+ static final int MAX_DONE_EXECUTING_COUNT = 6;
+
final ActivityManagerService ams;
final BatteryStatsImpl.Uid.Pkg.Serv stats;
final ComponentName name; // service component.
@@ -68,29 +75,6 @@
final HashMap<IBinder, ConnectionRecord> connections
= new HashMap<IBinder, ConnectionRecord>();
// IBinder -> ConnectionRecord of all bound clients
-
- // Maximum number of delivery attempts before giving up.
- static final int MAX_DELIVERY_COUNT = 3;
-
- // Maximum number of times it can fail during execution before giving up.
- static final int MAX_DONE_EXECUTING_COUNT = 6;
-
- static class StartItem {
- final int id;
- final Intent intent;
- long deliveredTime;
- int deliveryCount;
- int doneExecutingCount;
-
- StartItem(int _id, Intent _intent) {
- id = _id;
- intent = _intent;
- }
- }
- final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>();
- // start() arguments which been delivered.
- final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
- // start() arguments that haven't yet been delivered.
ProcessRecord app; // where this service is running or null.
boolean isForeground; // is service currently in foreground mode?
@@ -112,6 +96,104 @@
String stringName; // caching of toString
+ static class StartItem implements UriPermissionOwner {
+ final ServiceRecord sr;
+ final int id;
+ final Intent intent;
+ final int targetPermissionUid;
+ long deliveredTime;
+ int deliveryCount;
+ int doneExecutingCount;
+
+ String stringName; // caching of toString
+
+ HashSet<UriPermission> readUriPermissions; // special access to reading uris.
+ HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+
+ StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) {
+ sr = _sr;
+ id = _id;
+ intent = _intent;
+ targetPermissionUid = _targetPermissionUid;
+ }
+
+ void removeUriPermissionsLocked() {
+ if (readUriPermissions != null) {
+ for (UriPermission perm : readUriPermissions) {
+ perm.readOwners.remove(this);
+ if (perm.readOwners.size() == 0 && (perm.globalModeFlags
+ &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) {
+ perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ sr.ams.removeUriPermissionIfNeededLocked(perm);
+ }
+ }
+ readUriPermissions = null;
+ }
+ if (writeUriPermissions != null) {
+ for (UriPermission perm : writeUriPermissions) {
+ perm.writeOwners.remove(this);
+ if (perm.writeOwners.size() == 0 && (perm.globalModeFlags
+ &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) {
+ perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+ sr.ams.removeUriPermissionIfNeededLocked(perm);
+ }
+ }
+ writeUriPermissions = null;
+ }
+ }
+
+ @Override
+ public void addReadPermission(UriPermission perm) {
+ if (readUriPermissions == null) {
+ readUriPermissions = new HashSet<UriPermission>();
+ }
+ readUriPermissions.add(perm);
+ }
+
+ @Override
+ public void addWritePermission(UriPermission perm) {
+ if (writeUriPermissions == null) {
+ writeUriPermissions = new HashSet<UriPermission>();
+ }
+ writeUriPermissions.add(perm);
+ }
+
+ @Override
+ public void removeReadPermission(UriPermission perm) {
+ readUriPermissions.remove(perm);
+ if (readUriPermissions.size() == 0) {
+ readUriPermissions = null;
+ }
+ }
+
+ @Override
+ public void removeWritePermission(UriPermission perm) {
+ writeUriPermissions.remove(perm);
+ if (writeUriPermissions.size() == 0) {
+ writeUriPermissions = null;
+ }
+ }
+
+ public String toString() {
+ if (stringName != null) {
+ return stringName;
+ }
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ServiceRecord{")
+ .append(Integer.toHexString(System.identityHashCode(sr)))
+ .append(' ').append(sr.shortName)
+ .append(" StartItem ")
+ .append(Integer.toHexString(System.identityHashCode(this)))
+ .append(" id=").append(id).append('}');
+ return stringName = sb.toString();
+ }
+ }
+
+ final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>();
+ // start() arguments which been delivered.
+ final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
+ // start() arguments that haven't yet been delivered.
+
void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
final int N = list.size();
for (int i=0; i<N; i++) {
@@ -128,9 +210,22 @@
if (si.doneExecutingCount != 0) {
pw.print(" dxc="); pw.print(si.doneExecutingCount);
}
- pw.print(" ");
+ pw.println("");
+ pw.print(prefix); pw.print(" intent=");
if (si.intent != null) pw.println(si.intent.toString());
else pw.println("null");
+ if (si.targetPermissionUid >= 0) {
+ pw.print(prefix); pw.print(" targetPermissionUid=");
+ pw.println(si.targetPermissionUid);
+ }
+ if (si.readUriPermissions != null) {
+ pw.print(prefix); pw.print(" readUriPermissions=");
+ pw.println(si.readUriPermissions);
+ }
+ if (si.writeUriPermissions != null) {
+ pw.print(prefix); pw.print(" writeUriPermissions=");
+ pw.println(si.writeUriPermissions);
+ }
}
}
@@ -324,6 +419,13 @@
}
}
+ public void clearDeliveredStartsLocked() {
+ for (int i=deliveredStarts.size()-1; i>=0; i--) {
+ deliveredStarts.get(i).removeUriPermissionsLocked();
+ }
+ deliveredStarts.clear();
+ }
+
public String toString() {
if (stringName != null) {
return stringName;
diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java
index 81450c5..93c59cc 100644
--- a/services/java/com/android/server/am/UriPermission.java
+++ b/services/java/com/android/server/am/UriPermission.java
@@ -22,13 +22,20 @@
import java.io.PrintWriter;
import java.util.HashSet;
+interface UriPermissionOwner {
+ void addReadPermission(UriPermission perm);
+ void addWritePermission(UriPermission perm);
+ void removeReadPermission(UriPermission perm);
+ void removeWritePermission(UriPermission perm);
+}
+
class UriPermission {
final int uid;
final Uri uri;
int modeFlags = 0;
int globalModeFlags = 0;
- final HashSet<ActivityRecord> readActivities = new HashSet<ActivityRecord>();
- final HashSet<ActivityRecord> writeActivities = new HashSet<ActivityRecord>();
+ final HashSet<UriPermissionOwner> readOwners = new HashSet<UriPermissionOwner>();
+ final HashSet<UriPermissionOwner> writeOwners = new HashSet<UriPermissionOwner>();
String stringName;
@@ -41,27 +48,21 @@
if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
- if (readActivities.size() > 0) {
- for (ActivityRecord r : readActivities) {
- r.readUriPermissions.remove(this);
- if (r.readUriPermissions.size() == 0) {
- r.readUriPermissions = null;
- }
+ if (readOwners.size() > 0) {
+ for (UriPermissionOwner r : readOwners) {
+ r.removeReadPermission(this);
}
- readActivities.clear();
+ readOwners.clear();
}
}
if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
- if (readActivities.size() > 0) {
- for (ActivityRecord r : readActivities) {
- r.writeUriPermissions.remove(this);
- if (r.writeUriPermissions.size() == 0) {
- r.writeUriPermissions = null;
- }
+ if (readOwners.size() > 0) {
+ for (UriPermissionOwner r : writeOwners) {
+ r.removeWritePermission(this);
}
- readActivities.clear();
+ readOwners.clear();
}
}
}
@@ -85,11 +86,17 @@
pw.print(" uid="); pw.print(uid);
pw.print(" globalModeFlags=0x");
pw.println(Integer.toHexString(globalModeFlags));
- if (readActivities.size() != 0) {
- pw.print(prefix); pw.print("readActivities="); pw.println(readActivities);
+ if (readOwners.size() != 0) {
+ pw.print(prefix); pw.println("readOwners:");
+ for (UriPermissionOwner owner : readOwners) {
+ pw.print(prefix); pw.print(" * "); pw.println(owner);
+ }
}
- if (writeActivities.size() != 0) {
- pw.print(prefix); pw.print("writeActivities="); pw.println(writeActivities);
+ if (writeOwners.size() != 0) {
+ pw.print(prefix); pw.println("writeOwners:");
+ for (UriPermissionOwner owner : writeOwners) {
+ pw.print(prefix); pw.print(" * "); pw.println(owner);
+ }
}
}
}
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/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 4da4b6a..057ba0a 100644
--- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -102,7 +102,8 @@
sender.getActiveApnTypes(),
sender.getInterfaceName(null),
((telephony!=null) ? telephony.getNetworkType() :
- TelephonyManager.NETWORK_TYPE_UNKNOWN));
+ TelephonyManager.NETWORK_TYPE_UNKNOWN),
+ sender.getGateway(null));
} catch (RemoteException ex) {
// system process is dead
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 5bf8e58..87e4b52 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,7 +32,8 @@
void notifyCallForwardingChanged(boolean cfi);
void notifyDataActivity(int state);
void notifyDataConnection(int state, boolean isDataConnectivityPossible,
- String reason, String apn, in String[] apnTypes, String interfaceName, int networkType);
+ String reason, String apn, in String[] apnTypes, String interfaceName, int networkType,
+ String gateway);
void notifyDataConnectionFailed(String reason);
void notifyCellLocation(in Bundle cellLocation);
}
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 33f7c9d..9afade3 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -103,6 +103,7 @@
static final String DATA_APN_KEY = "apn";
static final String DATA_IFACE_NAME_KEY = "iface";
+ static final String DATA_GATEWAY_KEY = "gateway";
static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
static final String PHONE_IN_ECM_STATE = "phoneinECMState";
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/SipPhoneNotifier.java b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
index 81e151e..4092c69 100644
--- a/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
@@ -104,7 +104,8 @@
sender.getActiveApnTypes(),
sender.getInterfaceName(null),
((telephony!=null) ? telephony.getNetworkType() :
- TelephonyManager.NETWORK_TYPE_UNKNOWN));
+ TelephonyManager.NETWORK_TYPE_UNKNOWN),
+ sender.getGateway(null));
} catch (RemoteException ex) {
// system process is dead
}
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()
{