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();