Merge "Apply values set in "dropDownWidth" attribute of Spinner styles."
diff --git a/api/current.xml b/api/current.xml
index 4ecaf53..b51242b 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -29358,6 +29358,17 @@
  visibility="public"
 >
 </field>
+<field name="COLUMN_MEDIAPROVIDER_URI"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;mediaprovider_uri&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="COLUMN_MEDIA_TYPE"
  type="java.lang.String"
  transient="false"
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index b9a541d..fd96cf0 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -28,7 +28,6 @@
 import android.os.ParcelFileDescriptor;
 import android.provider.BaseColumns;
 import android.provider.Downloads;
-import android.util.Log;
 import android.util.Pair;
 
 import java.io.File;
@@ -141,6 +140,12 @@
      */
     public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
 
+    /**
+     * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
+     * used to delete the entries from MediaProvider database when it is deleted from the
+     * downloaded list.
+     */
+    public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
 
     /**
      * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
@@ -271,6 +276,7 @@
     // this array must contain all public columns
     private static final String[] COLUMNS = new String[] {
         COLUMN_ID,
+        COLUMN_MEDIAPROVIDER_URI,
         COLUMN_TITLE,
         COLUMN_DESCRIPTION,
         COLUMN_URI,
@@ -287,6 +293,7 @@
     // columns to request from DownloadProvider
     private static final String[] UNDERLYING_COLUMNS = new String[] {
         Downloads.Impl._ID,
+        Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
         Downloads.COLUMN_TITLE,
         Downloads.COLUMN_DESCRIPTION,
         Downloads.COLUMN_URI,
@@ -683,6 +690,9 @@
                 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
             }
 
+            // only return rows which are not marked 'deleted = 1'
+            selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
+
             String selection = joinStrings(" AND ", selectionParts);
             String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
             String orderBy = mOrderByColumn + " " + orderDirection;
@@ -749,6 +759,26 @@
     }
 
     /**
+     * Marks the specified download as 'to be deleted'. This is done when a completed download
+     * is to be removed but the row was stored without enough info to delete the corresponding
+     * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
+     *
+     * @param ids the IDs of the downloads to be marked 'deleted'
+     * @return the number of downloads actually updated
+     * @hide
+     */
+    public int markRowDeleted(long... ids) {
+        if (ids == null || ids.length == 0) {
+            // called with nothing to remove!
+            throw new IllegalArgumentException("input param 'ids' can't be null");
+        }
+        ContentValues values = new ContentValues();
+        values.put(Downloads.Impl.COLUMN_DELETED, 1);
+        return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
+                getWhereArgsForIds(ids));
+    }
+
+    /**
      * 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 to external storage, it will not be deleted.
@@ -959,6 +989,9 @@
             if (column.equals(COLUMN_LOCAL_FILENAME)) {
                 return getUnderlyingString(Downloads.Impl._DATA);
             }
+            if (column.equals(COLUMN_MEDIAPROVIDER_URI)) {
+                return getUnderlyingString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
+            }
 
             assert column.equals(COLUMN_LOCAL_URI);
             return getLocalUri();
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index ce511bd..41ec3cc 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -156,6 +156,10 @@
                 Message msg = new Message();
                 msg.what = AUTO_CONNECT_PROFILES;
                 sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
+            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+                // This is technically not needed, but we can get stuck sometimes.
+                // For example, if incoming A2DP fails, we are not informed by Bluez
+                sendMessage(TRANSITION_TO_STABLE);
             }
       }
     };
@@ -198,6 +202,7 @@
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
+        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
 
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
@@ -348,7 +353,11 @@
                 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
             }
             mStatus = processCommand(mCommand);
-            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+            if (!mStatus) {
+                sendMessage(TRANSITION_TO_STABLE);
+                mService.sendProfileStateMessage(BluetoothProfileState.HFP,
+                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
+            }
         }
 
         @Override
@@ -456,7 +465,11 @@
                 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
             }
             mStatus = processCommand(mCommand);
-            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+            if (!mStatus) {
+                sendMessage(TRANSITION_TO_STABLE);
+                mService.sendProfileStateMessage(BluetoothProfileState.HFP,
+                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
+            }
         }
 
         @Override
@@ -531,7 +544,11 @@
                 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
             }
             mStatus = processCommand(mCommand);
-            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+            if (!mStatus) {
+                sendMessage(TRANSITION_TO_STABLE);
+                mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
+                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
+            }
         }
 
         @Override
@@ -636,7 +653,11 @@
                 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
             }
             mStatus = processCommand(mCommand);
-            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+            if (!mStatus) {
+                sendMessage(TRANSITION_TO_STABLE);
+                mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
+                                                 BluetoothProfileState.TRANSITION_TO_STABLE);
+            }
         }
 
         @Override
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
index 7f42baf..584008b 100644
--- a/core/java/android/bluetooth/BluetoothProfileState.java
+++ b/core/java/android/bluetooth/BluetoothProfileState.java
@@ -47,7 +47,7 @@
     public static final int A2DP = 1;
     public static final int HID = 2;
 
-    private static int TRANSITION_TO_STABLE = 100;
+    static final int TRANSITION_TO_STABLE = 100;
 
     private int mProfile;
     private BluetoothDevice mPendingDevice;
@@ -58,6 +58,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
             if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
@@ -77,6 +78,10 @@
                     newState == BluetoothInputDevice.STATE_DISCONNECTED)) {
                     sendMessage(TRANSITION_TO_STABLE);
                 }
+            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+                if (device.equals(mPendingDevice)) {
+                    sendMessage(TRANSITION_TO_STABLE);
+                }
             }
         }
     };
@@ -92,6 +97,7 @@
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
+        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         context.registerReceiver(mBroadcastReceiver, filter);
     }
 
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 8fd0e0a..72bf6b0 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -300,6 +300,15 @@
      */
     public static final String COLUMN_DESCRIPTION = "description";
 
+    /**
+     * Set to true if this download is deleted. It is completely removed from the database
+     * when MediaProvider database also deletes the metadata asociated with this downloaded file.
+     * <P>Type: BOOLEAN</P>
+     * <P>Owner can Read</P>
+     * @hide
+     */
+    public static final String COLUMN_DELETED = "deleted";
+
     /*
      * Lists the destinations that an application can specify for a download.
      */
@@ -881,6 +890,23 @@
         public static final String COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT =
             "bypass_recommended_size_limit";
 
+        /**
+         * Set to true if this download is deleted. It is completely removed from the database
+         * when MediaProvider database also deletes the metadata asociated with this downloaded file.
+         * <P>Type: BOOLEAN</P>
+         * <P>Owner can Read</P>
+         */
+        public static final String COLUMN_DELETED = "deleted";
+
+        /**
+         * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
+         * used to delete the entries from MediaProvider database when it is deleted from the
+         * downloaded list.
+         * <P>Type: TEXT</P>
+         * <P>Owner can Read</P>
+         */
+        public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
+
         /*
          * Lists the destinations that an application can specify for a download.
          */
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 4edc01f..cfbc2bd 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -596,7 +596,14 @@
             authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
             if (authorized) {
                 Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
-                mBluetoothService.notifyIncomingA2dpConnection(address);
+                // Some headsets try to connect AVCTP before AVDTP - against the recommendation
+                // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state
+                // machine.  We don't handle AVCTP signals currently. We only send
+                // intents for AVDTP state changes. We need to handle both of them in
+                // some cases. For now, just don't move to incoming state in this case.
+                if (!BluetoothUuid.isAvrcpTarget(uuid)) {
+                    mBluetoothService.notifyIncomingA2dpConnection(address);
+                }
             } else {
                 Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
             }
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index f8caa2c..f8a39f7 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -2717,6 +2717,16 @@
         mA2dpService = a2dpService;
     }
 
+    public void sendProfileStateMessage(int profile, int cmd) {
+        Message msg = new Message();
+        msg.what = cmd;
+        if (profile == BluetoothProfileState.HFP) {
+            mHfpProfileState.sendMessage(msg);
+        } else if (profile == BluetoothProfileState.A2DP) {
+            mA2dpProfileState.sendMessage(msg);
+        }
+    }
+
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
diff --git a/libs/rs/rsObjectBase.cpp b/libs/rs/rsObjectBase.cpp
index e4b07c4..46b1750 100644
--- a/libs/rs/rsObjectBase.cpp
+++ b/libs/rs/rsObjectBase.cpp
@@ -93,9 +93,8 @@
         if (mRSC && mRSC->props.mLogObjects) {
             dumpLOGV("checkDelete");
         }
-        delete this;
-
         unlockUserRef();
+        delete this;
         return true;
     }
     return false;
@@ -170,9 +169,7 @@
 
 void ObjectBase::remove() const
 {
-    // Should be within gObjectInitMutex lock
-    // lock will be from checkDelete a few levels up in the stack.
-
+    lockUserRef();
     //LOGV("calling remove  rsc %p", mRSC);
     if (!mRSC) {
         rsAssert(!mPrev);
@@ -191,12 +188,11 @@
     }
     mPrev = NULL;
     mNext = NULL;
+    unlockUserRef();
 }
 
 void ObjectBase::zeroAllUserRef(Context *rsc)
 {
-    lockUserRef();
-
     if (rsc->props.mLogObjects) {
         LOGV("Forcing release of all outstanding user refs.");
     }
@@ -219,8 +215,6 @@
         LOGV("Objects remaining.");
         dumpAll(rsc);
     }
-
-    unlockUserRef();
 }
 
 void ObjectBase::dumpAll(Context *rsc)