Merge "Maybe fix #3076572: phone process crashes in SipService, trying to get wifi lock" into gingerbread
diff --git a/api/current.xml b/api/current.xml
index a2bce1d..2a2200d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -24549,7 +24549,7 @@
</parameter>
</method>
<method name="remove"
- return="void"
+ return="int"
abstract="false"
native="false"
synchronized="false"
@@ -24558,7 +24558,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="id" type="long">
+<parameter name="ids" type="long...">
</parameter>
</method>
<field name="ACTION_DOWNLOAD_COMPLETE"
@@ -24951,7 +24951,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="id" type="long">
+<parameter name="ids" type="long...">
</parameter>
</method>
<method name="setFilterByStatus"
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index c476b8f..6256303 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -567,18 +567,18 @@
*/
public static final int ORDER_DESCENDING = 2;
- private Long mId = null;
+ private long[] mIds = null;
private Integer mStatusFlags = null;
private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
private int mOrderDirection = ORDER_DESCENDING;
private boolean mOnlyIncludeVisibleInDownloadsUi = false;
/**
- * Include only the download with the given ID.
+ * Include only the downloads with the given IDs.
* @return this object
*/
- public Query setFilterById(long id) {
- mId = id;
+ public Query setFilterById(long... ids) {
+ mIds = ids;
return this;
}
@@ -639,9 +639,11 @@
Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
Uri uri = baseUri;
List<String> selectionParts = new ArrayList<String>();
+ String[] selectionArgs = null;
- if (mId != null) {
- uri = ContentUris.withAppendedId(uri, mId);
+ if (mIds != null) {
+ selectionParts.add(getWhereClauseForIds(mIds));
+ selectionArgs = getWhereArgsForIds(mIds);
}
if (mStatusFlags != null) {
@@ -676,7 +678,7 @@
String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
String orderBy = mOrderByColumn + " " + orderDirection;
- return resolver.query(uri, projection, selection, null, orderBy);
+ return resolver.query(uri, projection, selection, selectionArgs, orderBy);
}
private String joinStrings(String joiner, Iterable<String> parts) {
@@ -738,17 +740,28 @@
}
/**
- * Cancel a download and remove it from the download manager. The download will be stopped if
+ * Cancel downloads and remove them from the download manager. Each download will be stopped if
* it was running, and it will no longer be accessible through the download manager. If a file
- * was already downloaded, it will not be deleted.
+ * was already downloaded to external storage, it will not be deleted.
*
- * @param id the ID of the download
+ * @param ids the IDs of the downloads to remove
+ * @return the number of downloads actually removed
*/
- public void remove(long id) {
- int numDeleted = mResolver.delete(getDownloadUri(id), null, null);
- if (numDeleted == 0) {
- throw new IllegalArgumentException("Download " + id + " does not exist");
+ public int remove(long... ids) {
+ StringBuilder whereClause = new StringBuilder();
+ String[] whereArgs = new String[ids.length];
+
+ whereClause.append(Downloads.Impl._ID + " IN (");
+ for (int i = 0; i < ids.length; i++) {
+ if (i > 0) {
+ whereClause.append(",");
+ }
+ whereClause.append("?");
+ whereArgs[i] = Long.toString(ids[i]);
}
+ whereClause.append(")");
+
+ return mResolver.delete(mBaseUri, whereClause.toString(), whereArgs);
}
/**
@@ -776,20 +789,20 @@
}
/**
- * Restart the given download, which must have already completed (successfully or not). This
+ * Restart the given downloads, which must have already completed (successfully or not). This
* method will only work when called from within the download manager's process.
- * @param id the ID of the download
+ * @param ids the IDs of the downloads
* @hide
*/
- public void restartDownload(long id) {
- Cursor cursor = query(new Query().setFilterById(id));
+ public void restartDownload(long... ids) {
+ Cursor cursor = query(new Query().setFilterById(ids));
try {
- if (!cursor.moveToFirst()) {
- throw new IllegalArgumentException("No download with id " + id);
- }
- int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
- if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
- throw new IllegalArgumentException("Cannot restart incomplete download: " + id);
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
+ if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
+ throw new IllegalArgumentException("Cannot restart incomplete download: "
+ + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
+ }
}
} finally {
cursor.close();
@@ -800,7 +813,7 @@
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
values.putNull(Downloads.Impl._DATA);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
- mResolver.update(getDownloadUri(id), values, null, null);
+ mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
}
/**
@@ -811,6 +824,33 @@
}
/**
+ * Get a parameterized SQL WHERE clause to select a bunch of IDs.
+ */
+ static String getWhereClauseForIds(long[] ids) {
+ StringBuilder whereClause = new StringBuilder();
+ whereClause.append(Downloads.Impl._ID + " IN (");
+ for (int i = 0; i < ids.length; i++) {
+ if (i > 0) {
+ whereClause.append(",");
+ }
+ whereClause.append("?");
+ }
+ whereClause.append(")");
+ return whereClause.toString();
+ }
+
+ /**
+ * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
+ */
+ static String[] getWhereArgsForIds(long[] ids) {
+ String[] whereArgs = new String[ids.length];
+ for (int i = 0; i < ids.length; i++) {
+ whereArgs[i] = Long.toString(ids[i]);
+ }
+ return whereArgs;
+ }
+
+ /**
* This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
* presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
* Some columns correspond directly to underlying values while others are computed from
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d1974dc..44b14e7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6549,12 +6549,9 @@
int selEnd = getSelectionEnd();
if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
+ // If a tap was used to give focus to that view, move cursor at tap position.
// Has to be done before onTakeFocus, which can be overloaded.
- if (mLastTouchOffset >= 0) {
- // Can happen when a TextView is displayed after its content has been deleted.
- mLastTouchOffset = Math.min(mLastTouchOffset, mText.length());
- Selection.setSelection((Spannable) mText, mLastTouchOffset);
- }
+ moveCursorToLastTapPosition();
if (mMovement != null) {
mMovement.onTakeFocus(this, (Spannable) mText, direction);
@@ -6613,8 +6610,6 @@
} else {
terminateTextSelectionMode();
}
-
- mLastTouchOffset = -1;
}
startStopMarquee(focused);
@@ -6626,6 +6621,22 @@
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
+ private void moveCursorToLastTapPosition() {
+ if (mSelectionModifierCursorController != null) {
+ int mTapToFocusPosition = ((SelectionModifierCursorController)
+ mSelectionModifierCursorController).getMinTouchOffset();
+ if (mTapToFocusPosition >= 0) {
+ // Safety check, should not be possible.
+ if (mTapToFocusPosition > mText.length()) {
+ Log.e(LOG_TAG, "Invalid tap focus position (" + mTapToFocusPosition + " vs "
+ + mText.length() + ")");
+ mTapToFocusPosition = mText.length();
+ }
+ Selection.setSelection((Spannable) mText, mTapToFocusPosition);
+ }
+ }
+ }
+
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
@@ -6717,7 +6728,7 @@
// Tapping outside stops selection mode, if any
stopTextSelectionMode();
- if (mInsertionPointCursorController != null) {
+ if (mInsertionPointCursorController != null && mText.length() > 0) {
mInsertionPointCursorController.show();
}
}
@@ -7255,7 +7266,7 @@
int minOffset, maxOffset;
- if (mDPadCenterIsDown || mEnterKeyIsDown) {
+ if (mContextMenuTriggeredByKey) {
minOffset = getSelectionStart();
maxOffset = getSelectionEnd();
} else {
@@ -7286,6 +7297,9 @@
}
private String getWordForDictionary() {
+ if (!mContextMenuTriggeredByKey) {
+ moveCursorToLastTapPosition();
+ }
long wordLimits = getWordLimitsAt(getSelectionStart());
if (wordLimits >= 0) {
int start = extractRangeStartFromLong(wordLimits);
@@ -7336,6 +7350,14 @@
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
boolean added = false;
+ mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
+ // Problem with context menu on long press: the menu appears while the key in down and when
+ // the key is released, the view does not receive the key_up event. This ensures that the
+ // state is reset whenever the context menu action is displayed.
+ // mContextMenuTriggeredByKey saved that state so that it is available in
+ // onTextContextMenuItem. We cannot simply clear these flags in onTextContextMenuItem since
+ // it may not be called (if the user/ discards the context menu with the back key).
+ mDPadCenterIsDown = mEnterKeyIsDown = false;
if (mIsInTextSelectionMode) {
MenuHandler handler = new MenuHandler();
@@ -7361,21 +7383,6 @@
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()) {
@@ -7529,7 +7536,6 @@
case ID_ADD_TO_DICTIONARY:
String word = getWordForDictionary();
-
if (word != null) {
Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
i.putExtra("word", word);
@@ -8013,6 +8019,7 @@
SelectionModifierCursorController() {
mStartHandle = new HandleView(this, HandleView.LEFT);
mEndHandle = new HandleView(this, HandleView.RIGHT);
+ mMinTouchOffset = mMaxTouchOffset = -1;
}
public void show() {
@@ -8095,14 +8102,16 @@
}
public boolean onTouchEvent(MotionEvent event) {
- if (isFocused() && isTextEditable()) {
+ // This is done even when the View does not have focus, so that long presses can start
+ // selection and tap can move cursor from this tap position.
+ 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 = mLastTouchOffset = getOffset(x, y);
+ mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
break;
@@ -8259,11 +8268,11 @@
private CursorController mInsertionPointCursorController;
private CursorController mSelectionModifierCursorController;
private boolean mIsInTextSelectionMode = false;
- private int mLastTouchOffset = -1;
// These are needed to desambiguate a long click. If the long click comes from ones of these, we
// select from the current cursor position. Otherwise, select from long pressed position.
private boolean mDPadCenterIsDown = false;
private boolean mEnterKeyIsDown = false;
+ private boolean mContextMenuTriggeredByKey = 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();