Merge "Paste as plain text by Ctrl-Shift-V"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index c35f332..c812b6a 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -228,6 +228,11 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/SystemUI_intermediates/)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/Keyguard_intermediates/)
 $(call add-clean-step, rm -f $(OUT_DIR)/target/product/*/system/framework/android.policy.jar)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/inputflinger $(PRODUCT_OUT)/symbols/system/bin/inputflinger)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/system/lib64/libinputflingerhost.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/symbols/system/lib64/libinputflingerhost.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libinputflingerhost.so $(PRODUCT_OUT)/obj_arm/lib/libinputflingerhost.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinputflingerhost_intermediates $(PRODUCT_OUT)/obj_arm/SHARED_LIBRARIES/libinputflingerhost_intermediates)
 
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 62081ee..a501fa7 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -138,6 +138,7 @@
                 "       am task lock <TASK_ID>\n" +
                 "       am task lock stop\n" +
                 "       am task resizeable <TASK_ID> [true|false]\n" +
+                "       am task resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
                 "       am get-config\n" +
                 "\n" +
                 "am start: start an Activity.  Options are:\n" +
@@ -249,11 +250,11 @@
                 "am stack resize: change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>" +
                 ".\n" +
                 "\n" +
-                "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally" +
-                "   starting the new stack with [INTENT] if specified. If [INTENT] isn't" +
-                "   specified and the current stack has more than one task, then the top task" +
-                "   of the current task will be moved to the new stack. Command will also force" +
-                "   all current tasks in both stacks to be resizeable." +
+                "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally\n" +
+                "   starting the new stack with [INTENT] if specified. If [INTENT] isn't\n" +
+                "   specified and the current stack has more than one task, then the top task\n" +
+                "   of the current task will be moved to the new stack. Command will also force\n" +
+                "   all current tasks in both stacks to be resizeable.\n" +
                 "\n" +
                 "am stack list: list all of the activity stacks and their sizes.\n" +
                 "\n" +
@@ -265,6 +266,10 @@
                 "\n" +
                 "am task resizeable: change if <TASK_ID> is resizeable (true) or not (false).\n" +
                 "\n" +
+                "am task resize: makes sure <TASK_ID> is in a stack with the specified bounds.\n" +
+                "   Forces the task to be resizeable and creates a stack if no existing stack\n" +
+                "   has the specified bounds.\n" +
+                "\n" +
                 "am get-config: retrieve the configuration and any recent configurations\n" +
                 "  of the device\n" +
                 "\n" +
@@ -1742,33 +1747,14 @@
     private void runStackResize() throws Exception {
         String stackIdStr = nextArgRequired();
         int stackId = Integer.valueOf(stackIdStr);
-        String leftStr = nextArgRequired();
-        int left = Integer.valueOf(leftStr);
-        String topStr = nextArgRequired();
-        int top = Integer.valueOf(topStr);
-        String rightStr = nextArgRequired();
-        int right = Integer.valueOf(rightStr);
-        String bottomStr = nextArgRequired();
-        int bottom = Integer.valueOf(bottomStr);
-        if (left < 0) {
-            System.err.println("Error: bad left arg: " + leftStr);
-            return;
-        }
-        if (top < 0) {
-            System.err.println("Error: bad top arg: " + topStr);
-            return;
-        }
-        if (right <= 0) {
-            System.err.println("Error: bad right arg: " + rightStr);
-            return;
-        }
-        if (bottom <= 0) {
-            System.err.println("Error: bad bottom arg: " + bottomStr);
+        final Rect bounds = getBounds();
+        if (bounds == null) {
+            System.err.println("Error: invalid input bounds");
             return;
         }
 
         try {
-            mAm.resizeStack(stackId, new Rect(left, top, right, bottom));
+            mAm.resizeStack(stackId, bounds);
         } catch (RemoteException e) {
         }
     }
@@ -1857,6 +1843,8 @@
             runTaskLock();
         } else if (op.equals("resizeable")) {
             runTaskResizeable();
+        } else if (op.equals("resize")) {
+            runTaskResize();
         } else {
             showError("Error: unknown command '" + op + "'");
             return;
@@ -1890,6 +1878,20 @@
         }
     }
 
+    private void runTaskResize() throws Exception {
+        final String taskIdStr = nextArgRequired();
+        final int taskId = Integer.valueOf(taskIdStr);
+        final Rect bounds = getBounds();
+        if (bounds == null) {
+            System.err.println("Error: invalid input bounds");
+            return;
+        }
+        try {
+            mAm.resizeTask(taskId, bounds);
+        } catch (RemoteException e) {
+        }
+    }
+
     private List<Configuration> getRecentConfigurations(int days) {
         IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
                     Context.USAGE_STATS_SERVICE));
@@ -1986,4 +1988,32 @@
         }
         return fd;
     }
+
+    private Rect getBounds() {
+        String leftStr = nextArgRequired();
+        int left = Integer.valueOf(leftStr);
+        String topStr = nextArgRequired();
+        int top = Integer.valueOf(topStr);
+        String rightStr = nextArgRequired();
+        int right = Integer.valueOf(rightStr);
+        String bottomStr = nextArgRequired();
+        int bottom = Integer.valueOf(bottomStr);
+        if (left < 0) {
+            System.err.println("Error: bad left arg: " + leftStr);
+            return null;
+        }
+        if (top < 0) {
+            System.err.println("Error: bad top arg: " + topStr);
+            return null;
+        }
+        if (right <= 0) {
+            System.err.println("Error: bad right arg: " + rightStr);
+            return null;
+        }
+        if (bottom <= 0) {
+            System.err.println("Error: bad bottom arg: " + bottomStr);
+            return null;
+        }
+        return new Rect(left, top, right, bottom);
+    }
 }
diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp
index 81fe5f8..2932130 100644
--- a/cmds/bootanimation/AudioPlayer.cpp
+++ b/cmds/bootanimation/AudioPlayer.cpp
@@ -305,7 +305,7 @@
 exit:
     if (pcm)
         pcm_close(pcm);
-    mCurrentFile->release();
+    delete mCurrentFile;
     mCurrentFile = NULL;
     return false;
 }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 1d4de22..bb25ec6 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -179,7 +179,7 @@
     // FileMap memory is never released until application exit.
     // Release it now as the texture is already loaded and the memory used for
     // the packed resource can be released.
-    frame.map->release();
+    delete frame.map;
 
     // ensure we can call getPixels(). No need to call unlock, since the
     // bitmap will go out of scope when we return from this method.
@@ -446,7 +446,7 @@
     }
 
     outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
-    entryMap->release();
+    delete entryMap;
     return true;
 }
 
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index bbe6eef2..197e36b 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -147,20 +147,20 @@
         char *buf = new char[uncompLen];
         if (NULL == buf) {
             ALOGW("%s: failed to allocate %zd byte\n", __FUNCTION__, uncompLen);
-            dataMap->release();
+            delete dataMap;
             return -1;
         }
         StreamingZipInflater inflater(dataMap, uncompLen);
         if (inflater.read(buf, uncompLen) < 0) {
             ALOGW("%s: failed to inflate %zd byte\n", __FUNCTION__, uncompLen);
             delete[] buf;
-            dataMap->release();
+            delete dataMap;
             return -1;
         }
 
         int priority = parse_manifest(buf, uncompLen, target_package_name);
         delete[] buf;
-        dataMap->release();
+        delete dataMap;
         return priority;
     }
 }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 005b1d9..bb307bb 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2313,6 +2313,15 @@
             return true;
         }
 
+        case RESIZE_TASK_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int taskId = data.readInt();
+            Rect r = Rect.CREATOR.createFromParcel(data);
+            resizeTask(taskId, r);
+            reply.writeNoException();
+            return true;
+        }
+
         case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String filename = data.readString();
@@ -5438,6 +5447,20 @@
     }
 
     @Override
+    public void resizeTask(int taskId, Rect r) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(taskId);
+        r.writeToParcel(data, 0);
+        mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1f5a1a0..a7e9413 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -464,6 +464,7 @@
     public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
             throws RemoteException;
     public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
+    public void resizeTask(int taskId, Rect bounds) throws RemoteException;
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
 
     public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
@@ -808,4 +809,5 @@
     int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282;
     int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283;
     int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284;
+    int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285;
 }
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index cd32dae..1d16516 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -73,9 +73,7 @@
          * {@link #commit} or {@link #apply} are called.
          * 
          * @param key The name of the preference to modify.
-         * @param value The new value for the preference.  Supplying {@code null}
-         *    as the value is equivalent to calling {@link #remove(String)} with
-         *    this key.
+         * @param value The new value for the preference.
          * 
          * @return Returns a reference to the same Editor object, so you can
          * chain put calls together.
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java
index e9ec5a4..e3bc238 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -20,9 +20,9 @@
 import android.os.Parcelable;
 import android.os.ParcelableParcel;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 
 /**
  * Top-level class for managing and interacting with the global undo state for
@@ -54,7 +54,9 @@
  * @hide
  */
 public class UndoManager {
-    private final HashMap<String, UndoOwner> mOwners = new HashMap<String, UndoOwner>();
+    // The common case is a single undo owner (e.g. for a TextView), so default to that capacity.
+    private final ArrayMap<String, UndoOwner> mOwners =
+            new ArrayMap<String, UndoOwner>(1 /* capacity */);
     private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>();
     private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>();
     private int mUpdateCount;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d52dd30..e303f61 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -205,6 +205,20 @@
     public static final int DOZE_WAKE_LOCK = 0x00000040;
 
     /**
+     * Wake lock level: Keep the device awake enough to allow drawing to occur.
+     * <p>
+     * This is used by the window manager to allow applications to draw while the
+     * system is dozing.  It currently has no effect unless the power manager is in
+     * the dozing state.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * {@hide}
+     */
+    public static final int DRAW_WAKE_LOCK = 0x00000080;
+
+    /**
      * Mask for the wake lock level component of a combined wake lock level and flags integer.
      *
      * @hide
@@ -489,6 +503,7 @@
             case FULL_WAKE_LOCK:
             case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
             case DOZE_WAKE_LOCK:
+            case DRAW_WAKE_LOCK:
                 break;
             default:
                 throw new IllegalArgumentException("Must specify a valid wake lock level.");
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index bf51ed1..ac6bbb7 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -16,6 +16,10 @@
 
 package android.security;
 
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.OperationResult;
 import android.security.KeystoreArguments;
 
 /**
@@ -52,4 +56,19 @@
     int reset_uid(int uid);
     int sync_uid(int sourceUid, int targetUid);
     int password_uid(String password, int uid);
+
+    // Keymaster 0.4 methods
+    int addRngEntropy(in byte[] data);
+    int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags,
+        out KeyCharacteristics characteristics);
+    int getKeyCharacteristics(String alias, in byte[] clientId,
+        in byte[] appId, out KeyCharacteristics characteristics);
+    int importKey(String alias, in KeymasterArguments arguments, int format,
+        in byte[] keyData, int uid, int flags, out KeyCharacteristics characteristics);
+    ExportResult exportKey(String alias, int format, in byte[] clientId, in byte[] appId);
+    OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable,
+        in KeymasterArguments params, out KeymasterArguments operationParams);
+    OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input);
+    OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature);
+    int abort(IBinder handle);
 }
diff --git a/core/java/android/security/keymaster/ExportResult.aidl b/core/java/android/security/keymaster/ExportResult.aidl
new file mode 100644
index 0000000..f522355
--- /dev/null
+++ b/core/java/android/security/keymaster/ExportResult.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+/* @hide */
+parcelable ExportResult;
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
new file mode 100644
index 0000000..bb44c03
--- /dev/null
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class for handling parceling the return values from keymaster's export operation.
+ * @hide
+ */
+public class ExportResult implements Parcelable {
+    public final int resultCode;
+    public final byte[] exportData;
+
+    public static final Parcelable.Creator<ExportResult> CREATOR = new
+            Parcelable.Creator<ExportResult>() {
+                public ExportResult createFromParcel(Parcel in) {
+                    return new ExportResult(in);
+                }
+
+                public ExportResult[] newArray(int length) {
+                    return new ExportResult[length];
+                }
+            };
+
+    protected ExportResult(Parcel in) {
+        resultCode = in.readInt();
+        exportData = in.createByteArray();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(resultCode);
+        out.writeByteArray(exportData);
+    }
+};
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.aidl b/core/java/android/security/keymaster/KeyCharacteristics.aidl
new file mode 100644
index 0000000..15014b1
--- /dev/null
+++ b/core/java/android/security/keymaster/KeyCharacteristics.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+/* @hide */
+parcelable KeyCharacteristics;
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java
new file mode 100644
index 0000000..b803a1b
--- /dev/null
+++ b/core/java/android/security/keymaster/KeyCharacteristics.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class KeyCharacteristics implements Parcelable {
+    public KeymasterArguments swEnforced;
+    public KeymasterArguments hwEnforced;
+
+    public static final Parcelable.Creator<KeyCharacteristics> CREATOR = new
+            Parcelable.Creator<KeyCharacteristics>() {
+                public KeyCharacteristics createFromParcel(Parcel in) {
+                    return new KeyCharacteristics(in);
+                }
+
+                public KeyCharacteristics[] newArray(int length) {
+                    return new KeyCharacteristics[length];
+                }
+            };
+
+    public KeyCharacteristics() {}
+
+    protected KeyCharacteristics(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        swEnforced.writeToParcel(out, flags);
+        hwEnforced.writeToParcel(out, flags);
+    }
+
+    public void readFromParcel(Parcel in) {
+        swEnforced = KeymasterArguments.CREATOR.createFromParcel(in);
+        hwEnforced = KeymasterArguments.CREATOR.createFromParcel(in);
+    }
+}
+
diff --git a/core/java/android/security/keymaster/KeymasterArgument.java b/core/java/android/security/keymaster/KeymasterArgument.java
new file mode 100644
index 0000000..9a1c894
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterArgument.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFormatException;
+
+/**
+ * Base class for the Java side of a Keymaster tagged argument.
+ * <p>
+ * Serialization code for this and subclasses must be kept in sync with system/security/keystore
+ * and with hardware/libhardware/include/hardware/keymaster_defs.h
+ * @hide
+ */
+abstract class KeymasterArgument implements Parcelable {
+    public final int tag;
+
+    public static final Parcelable.Creator<KeymasterArgument> CREATOR = new
+            Parcelable.Creator<KeymasterArgument>() {
+                public KeymasterArgument createFromParcel(Parcel in) {
+                    final int pos = in.dataPosition();
+                    final int tag = in.readInt();
+                    switch (KeymasterDefs.getTagType(tag)) {
+                        case KeymasterDefs.KM_ENUM:
+                        case KeymasterDefs.KM_ENUM_REP:
+                        case KeymasterDefs.KM_INT:
+                        case KeymasterDefs.KM_INT_REP:
+                            return new KeymasterIntArgument(tag, in);
+                        case KeymasterDefs.KM_LONG:
+                            return new KeymasterLongArgument(tag, in);
+                        case KeymasterDefs.KM_DATE:
+                            return new KeymasterDateArgument(tag, in);
+                        case KeymasterDefs.KM_BYTES:
+                        case KeymasterDefs.KM_BIGNUM:
+                            return new KeymasterBlobArgument(tag, in);
+                        case KeymasterDefs.KM_BOOL:
+                            return new KeymasterBooleanArgument(tag, in);
+                        default:
+                            throw new ParcelFormatException("Bad tag: " + tag + " at " + pos);
+                    }
+                }
+                public KeymasterArgument[] newArray(int size) {
+                    return new KeymasterArgument[size];
+                }
+            };
+
+    protected KeymasterArgument(int tag) {
+        this.tag = tag;
+    }
+
+    /**
+     * Writes the value of this argument, if any, to the provided parcel.
+     */
+    public abstract void writeValue(Parcel out);
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(tag);
+        writeValue(out);
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterArguments.aidl b/core/java/android/security/keymaster/KeymasterArguments.aidl
new file mode 100644
index 0000000..7aef5a6
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterArguments.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+/* @hide */
+parcelable KeymasterArguments;
diff --git a/core/java/android/security/keymaster/KeymasterArguments.java b/core/java/android/security/keymaster/KeymasterArguments.java
new file mode 100644
index 0000000..b5fd4bd
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterArguments.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Utility class for the java side of user specified Keymaster arguments.
+ * <p>
+ * Serialization code for this and subclasses must be kept in sync with system/security/keystore
+ * @hide
+ */
+public class KeymasterArguments implements Parcelable {
+    List<KeymasterArgument> mArguments;
+
+    public static final Parcelable.Creator<KeymasterArguments> CREATOR = new
+            Parcelable.Creator<KeymasterArguments>() {
+                public KeymasterArguments createFromParcel(Parcel in) {
+                    return new KeymasterArguments(in);
+                }
+                public KeymasterArguments[] newArray(int size) {
+                    return new KeymasterArguments[size];
+                }
+            };
+
+    public KeymasterArguments() {
+        mArguments = new ArrayList<KeymasterArgument>();
+    }
+
+    private KeymasterArguments(Parcel in) {
+        mArguments = in.createTypedArrayList(KeymasterArgument.CREATOR);
+    }
+
+    public void addInt(int tag, int value) {
+        mArguments.add(new KeymasterIntArgument(tag, value));
+    }
+
+    public void addBoolean(int tag) {
+        mArguments.add(new KeymasterBooleanArgument(tag));
+    }
+
+    public void addLong(int tag, long value) {
+        mArguments.add(new KeymasterLongArgument(tag, value));
+    }
+
+    public void addBlob(int tag, byte[] value) {
+        mArguments.add(new KeymasterBlobArgument(tag, value));
+    }
+
+    public void addDate(int tag, Date value) {
+        mArguments.add(new KeymasterDateArgument(tag, value));
+    }
+
+    private KeymasterArgument getArgumentByTag(int tag) {
+        for (KeymasterArgument arg : mArguments) {
+            if (arg.tag == tag) {
+                return arg;
+            }
+        }
+        return null;
+    }
+
+    public boolean containsTag(int tag) {
+        return getArgumentByTag(tag) != null;
+    }
+
+    public int getInt(int tag, int defaultValue) {
+        switch (KeymasterDefs.getTagType(tag)) {
+            case KeymasterDefs.KM_ENUM:
+            case KeymasterDefs.KM_INT:
+                break; // Accepted types
+            case KeymasterDefs.KM_INT_REP:
+            case KeymasterDefs.KM_ENUM_REP:
+                throw new IllegalArgumentException("Repeatable tags must use getInts: " + tag);
+            default:
+                throw new IllegalArgumentException("Tag is not an int type: " + tag);
+        }
+        KeymasterArgument arg = getArgumentByTag(tag);
+        if (arg == null) {
+            return defaultValue;
+        }
+        return ((KeymasterIntArgument) arg).value;
+    }
+
+    public long getLong(int tag, long defaultValue) {
+        if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_LONG) {
+            throw new IllegalArgumentException("Tag is not a long type: " + tag);
+        }
+        KeymasterArgument arg = getArgumentByTag(tag);
+        if (arg == null) {
+            return defaultValue;
+        }
+        return ((KeymasterLongArgument) arg).value;
+    }
+
+    public Date getDate(int tag, Date defaultValue) {
+        if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) {
+            throw new IllegalArgumentException("Tag is not a date type: " + tag);
+        }
+        KeymasterArgument arg = getArgumentByTag(tag);
+        if (arg == null) {
+            return defaultValue;
+        }
+        return ((KeymasterDateArgument) arg).date;
+    }
+
+    public boolean getBoolean(int tag, boolean defaultValue) {
+        if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BOOL) {
+            throw new IllegalArgumentException("Tag is not a boolean type: " + tag);
+        }
+        KeymasterArgument arg = getArgumentByTag(tag);
+        if (arg == null) {
+            return defaultValue;
+        }
+        return true;
+    }
+
+    public byte[] getBlob(int tag, byte[] defaultValue) {
+        switch (KeymasterDefs.getTagType(tag)) {
+            case KeymasterDefs.KM_BYTES:
+            case KeymasterDefs.KM_BIGNUM:
+                break; // Allowed types.
+            default:
+                throw new IllegalArgumentException("Tag is not a blob type: " + tag);
+        }
+        KeymasterArgument arg = getArgumentByTag(tag);
+        if (arg == null) {
+            return defaultValue;
+        }
+        return ((KeymasterBlobArgument) arg).blob;
+    }
+
+    public List<Integer> getInts(int tag) {
+        switch (KeymasterDefs.getTagType(tag)) {
+            case KeymasterDefs.KM_INT_REP:
+            case KeymasterDefs.KM_ENUM_REP:
+                break; // Allowed types.
+            default:
+                throw new IllegalArgumentException("Tag is not a repeating type: " + tag);
+        }
+        List<Integer> values = new ArrayList<Integer>();
+        for (KeymasterArgument arg : mArguments) {
+            if (arg.tag == tag) {
+                values.add(((KeymasterIntArgument) arg).value);
+            }
+        }
+        return values;
+    }
+
+    public int size() {
+        return mArguments.size();
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeTypedList(mArguments);
+    }
+
+    public void readFromParcel(Parcel in) {
+        in.readTypedList(mArguments, KeymasterArgument.CREATOR);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterBlobArgument.java b/core/java/android/security/keymaster/KeymasterBlobArgument.java
new file mode 100644
index 0000000..27f1153
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterBlobArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterBlobArgument extends KeymasterArgument {
+    public final byte[] blob;
+
+    public KeymasterBlobArgument(int tag, byte[] blob) {
+        super(tag);
+        this.blob = blob;
+    }
+
+    public KeymasterBlobArgument(int tag, Parcel in) {
+        super(tag);
+        blob = in.createByteArray();
+    }
+
+    @Override
+    public void writeValue(Parcel out) {
+        out.writeByteArray(blob);
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterBooleanArgument.java b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
new file mode 100644
index 0000000..8e17db4
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterBooleanArgument extends KeymasterArgument {
+
+    // Boolean arguments are always true if they exist and false if they don't.
+    public final boolean value = true;
+
+    public KeymasterBooleanArgument(int tag) {
+        super(tag);
+    }
+
+    public KeymasterBooleanArgument(int tag, Parcel in) {
+        super(tag);
+    }
+
+    @Override
+    public void writeValue(Parcel out) {
+        // Do nothing, value is implicit.
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDateArgument.java b/core/java/android/security/keymaster/KeymasterDateArgument.java
new file mode 100644
index 0000000..e8f4055
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterDateArgument.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+
+/**
+ * @hide
+ */
+class KeymasterDateArgument extends KeymasterArgument {
+    public final Date date;
+
+    public KeymasterDateArgument(int tag, Date date) {
+        super(tag);
+        this.date = date;
+    }
+
+    public KeymasterDateArgument(int tag, Parcel in) {
+        super(tag);
+        date = new Date(in.readLong());
+    }
+
+    @Override
+    public void writeValue(Parcel out) {
+        out.writeLong(date.getTime());
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
new file mode 100644
index 0000000..88cad79
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+/**
+ * Class tracking all the keymaster enum values needed for the binder API to keystore.
+ * This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h
+ * See keymaster_defs.h for detailed descriptions of each constant.
+ * @hide
+ */
+public final class KeymasterDefs {
+
+    private KeymasterDefs() {}
+
+    // Tag types.
+    public static final int KM_INVALID = 0 << 28;
+    public static final int KM_ENUM = 1 << 28;
+    public static final int KM_ENUM_REP = 2 << 28;
+    public static final int KM_INT = 3 << 28;
+    public static final int KM_INT_REP = 4 << 28;
+    public static final int KM_LONG = 5 << 28;
+    public static final int KM_DATE = 6 << 28;
+    public static final int KM_BOOL = 7 << 28;
+    public static final int KM_BIGNUM = 8 << 28;
+    public static final int KM_BYTES = 9 << 28;
+
+    // Tag values.
+    public static final int KM_TAG_INVALID = KM_INVALID | 0;
+    public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
+    public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
+    public static final int KM_TAG_KEY_SIZE = KM_INT | 3;
+    public static final int KM_TAG_BLOCK_MODE = KM_ENUM | 4;
+    public static final int KM_TAG_DIGEST = KM_ENUM | 5;
+    public static final int KM_TAG_MAC_LENGTH = KM_INT | 6;
+    public static final int KM_TAG_PADDING = KM_ENUM | 7;
+    public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 8;
+    public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 9;
+
+    public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
+    public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
+    public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
+
+    public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_LONG | 200;
+    public static final int KM_TAG_DSA_GENERATOR = KM_BIGNUM | 201;
+    public static final int KM_TAG_DSA_P = KM_BIGNUM | 202;
+    public static final int KM_TAG_DSA_Q = KM_BIGNUM | 203;
+    public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
+    public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
+    public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
+    public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_INT | 403;
+    public static final int KM_TAG_MAX_USES_PER_BOOT = KM_INT | 404;
+
+    public static final int KM_TAG_ALL_USERS = KM_BOOL | 500;
+    public static final int KM_TAG_USER_ID = KM_INT | 501;
+    public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 502;
+    public static final int KM_TAG_USER_AUTH_ID = KM_INT_REP | 503;
+    public static final int KM_TAG_AUTH_TIMEOUT = KM_INT | 504;
+
+    public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
+    public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
+
+    public static final int KM_TAG_APPLICATION_DATA = KM_BYTES | 700;
+    public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
+    public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
+    public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
+    public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+
+    public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
+    public static final int KM_TAG_NONCE = KM_BYTES | 1001;
+    public static final int KM_TAG_CHUNK_LENGTH = KM_INT | 1002;
+
+    // Algorithm values.
+    public static final int KM_ALGORITHM_RSA = 1;
+    public static final int KM_ALGORITHM_DSA = 2;
+    public static final int KM_ALGORITHM_ECDSA = 3;
+    public static final int KM_ALGORITHM_ECIES = 4;
+    public static final int KM_ALGORITHM_AES = 32;
+    public static final int KM_ALGORITHM_3DES = 33;
+    public static final int KM_ALGORITHM_SKIPJACK = 34;
+    public static final int KM_ALGORITHM_MARS = 48;
+    public static final int KM_ALGORITHM_RC6 = 49;
+    public static final int KM_ALGORITHM_SERPENT = 50;
+    public static final int KM_ALGORITHM_TWOFISH = 51;
+    public static final int KM_ALGORITHM_IDEA = 52;
+    public static final int KM_ALGORITHM_RC5 = 53;
+    public static final int KM_ALGORITHM_CAST5 = 54;
+    public static final int KM_ALGORITHM_BLOWFISH = 55;
+    public static final int KM_ALGORITHM_RC4 = 64;
+    public static final int KM_ALGORITHM_CHACHA20 = 65;
+    public static final int KM_ALGORITHM_HMAC = 128;
+
+    // Block modes.
+    public static final int KM_MODE_FIRST_UNAUTHENTICATED = 1;
+    public static final int KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED;
+    public static final int KM_MODE_CBC = 2;
+    public static final int KM_MODE_CBC_CTS = 3;
+    public static final int KM_MODE_CTR = 4;
+    public static final int KM_MODE_OFB = 5;
+    public static final int KM_MODE_CFB = 6;
+    public static final int KM_MODE_XTS = 7;
+    public static final int KM_MODE_FIRST_AUTHENTICATED = 32;
+    public static final int KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED;
+    public static final int KM_MODE_OCB = 33;
+    public static final int KM_MODE_CCM = 34;
+    public static final int KM_MODE_FIRST_MAC = 128;
+    public static final int KM_MODE_CMAC = KM_MODE_FIRST_MAC;
+    public static final int KM_MODE_POLY1305 = 129;
+
+    // Padding modes.
+    public static final int KM_PAD_NONE = 1;
+    public static final int KM_PAD_RSA_OAEP = 2;
+    public static final int KM_PAD_RSA_PSS = 3;
+    public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
+    public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
+    public static final int KM_PAD_ANSI_X923 = 32;
+    public static final int KM_PAD_ISO_10126 = 33;
+    public static final int KM_PAD_ZERO = 64;
+    public static final int KM_PAD_PKCS7 = 65;
+    public static final int KM_PAD_ISO_7816_4 = 66;
+
+    // Digest modes.
+    public static final int KM_DIGEST_NONE = 0;
+    public static final int KM_DIGEST_MD5 = 1;
+    public static final int KM_DIGEST_SHA1 = 2;
+    public static final int KM_DIGEST_SHA_2_224 = 3;
+    public static final int KM_DIGEST_SHA_2_256 = 4;
+    public static final int KM_DIGEST_SHA_2_384 = 5;
+    public static final int KM_DIGEST_SHA_2_512 = 6;
+    public static final int KM_DIGEST_SHA_3_256 = 7;
+    public static final int KM_DIGEST_SHA_3_384 = 8;
+    public static final int KM_DIGEST_SHA_3_512 = 9;
+
+    // Key origins.
+    public static final int KM_ORIGIN_HARDWARE = 0;
+    public static final int KM_ORIGIN_SOFTWARE = 1;
+    public static final int KM_ORIGIN_IMPORTED = 2;
+
+    // Key usability requirements.
+    public static final int KM_BLOB_STANDALONE = 0;
+    public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1;
+
+    // Operation Purposes.
+    public static final int KM_PURPOSE_ENCRYPT = 0;
+    public static final int KM_PURPOSE_DECRYPT = 1;
+    public static final int KM_PURPOSE_SIGN = 2;
+    public static final int KM_PURPOSE_VERIFY = 3;
+
+    // Key formats.
+    public static final int KM_KEY_FORMAT_X509 = 0;
+    public static final int KM_KEY_FORMAT_PKCS8 = 1;
+    public static final int KM_KEY_FORMAT_PKCS12 = 2;
+    public static final int KM_KEY_FORMAT_RAW = 3;
+
+    // Error codes.
+    public static final int KM_ERROR_OK = 0;
+    public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
+    public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2;
+    public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3;
+    public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4;
+    public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5;
+    public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
+    public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
+    public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
+    public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9;
+    public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
+    public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
+    public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
+    public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13;
+    public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14;
+    public static final int KM_ERROR_INVALID_USER_ID = -15;
+    public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16;
+    public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17;
+    public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18;
+    public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19;
+    public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20;
+    public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
+    public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22;
+    public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23;
+    public static final int KM_ERROR_KEY_NOT_YET_VALID = -24;
+    public static final int KM_ERROR_KEY_EXPIRED = -25;
+    public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26;
+    public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27;
+    public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28;
+    public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29;
+    public static final int KM_ERROR_VERIFICATION_FAILED = -30;
+    public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31;
+    public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32;
+    public static final int KM_ERROR_INVALID_KEY_BLOB = -33;
+    public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34;
+    public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35;
+    public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36;
+    public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37;
+    public static final int KM_ERROR_INVALID_ARGUMENT = -38;
+    public static final int KM_ERROR_UNSUPPORTED_TAG = -39;
+    public static final int KM_ERROR_INVALID_TAG = -40;
+    public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
+    public static final int KM_ERROR_INVALID_RESCOPING = -42;
+    public static final int KM_ERROR_INVALID_DSA_PARAMS = -43;
+    public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
+    public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
+    public static final int KM_ERROR_OPERATION_CANCELLED = -46;
+    public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47;
+    public static final int KM_ERROR_SECURE_HW_BUSY = -48;
+    public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49;
+    public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50;
+    public static final int KM_ERROR_UNIMPLEMENTED = -100;
+    public static final int KM_ERROR_VERSION_MISMATCH = -101;
+    public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
+
+    public static int getTagType(int tag) {
+        return tag & (0xF << 28);
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterIntArgument.java b/core/java/android/security/keymaster/KeymasterIntArgument.java
new file mode 100644
index 0000000..71797ae
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterIntArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterIntArgument extends KeymasterArgument {
+    public final int value;
+
+    public KeymasterIntArgument(int tag, int value) {
+        super(tag);
+        this.value = value;
+    }
+
+    public KeymasterIntArgument(int tag, Parcel in) {
+        super(tag);
+        value = in.readInt();
+    }
+
+    @Override
+    public void writeValue(Parcel out) {
+        out.writeInt(value);
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java
new file mode 100644
index 0000000..781b1ab
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterLongArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterLongArgument extends KeymasterArgument {
+    public final long value;
+
+    public KeymasterLongArgument(int tag, long value) {
+        super(tag);
+        this.value = value;
+    }
+
+    public KeymasterLongArgument(int tag, Parcel in) {
+        super(tag);
+        value = in.readLong();
+    }
+
+    @Override
+    public void writeValue(Parcel out) {
+        out.writeLong(value);
+    }
+}
diff --git a/core/java/android/security/keymaster/OperationResult.aidl b/core/java/android/security/keymaster/OperationResult.aidl
new file mode 100644
index 0000000..699e8d0
--- /dev/null
+++ b/core/java/android/security/keymaster/OperationResult.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+/* @hide */
+parcelable OperationResult;
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
new file mode 100644
index 0000000..ad54c96
--- /dev/null
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2015, 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.
+ */
+
+package android.security.keymaster;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class for handling the parceling of return values from keymaster crypto operations
+ * (begin/update/finish).
+ * @hide
+ */
+public class OperationResult implements Parcelable {
+    public final int resultCode;
+    public final IBinder token;
+    public final int inputConsumed;
+    public final byte[] output;
+
+    public static final Parcelable.Creator<OperationResult> CREATOR = new
+            Parcelable.Creator<OperationResult>() {
+                public OperationResult createFromParcel(Parcel in) {
+                    return new OperationResult(in);
+                }
+
+                public OperationResult[] newArray(int length) {
+                    return new OperationResult[length];
+                }
+            };
+
+    protected OperationResult(Parcel in) {
+        resultCode = in.readInt();
+        token = in.readStrongBinder();
+        inputConsumed = in.readInt();
+        output = in.createByteArray();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(resultCode);
+        out.writeStrongBinder(token);
+        out.writeInt(inputConsumed);
+        out.writeByteArray(output);
+    }
+}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d08ab46..fc0148f 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -194,4 +194,19 @@
     void onRectangleOnScreenRequested(IBinder token, in Rect rectangle);
 
     IWindowId getWindowId(IBinder window);
+
+    /**
+     * When the system is dozing in a low-power partially suspended state, pokes a short
+     * lived wake lock and ensures that the display is ready to accept the next frame
+     * of content drawn in the window.
+     *
+     * This mechanism is bound to the window rather than to the display manager or the
+     * power manager so that the system can ensure that the window is actually visible
+     * and prevent runaway applications from draining the battery.  This is similar to how
+     * FLAG_KEEP_SCREEN_ON works.
+     *
+     * This method is synchronous because it may need to acquire a wake lock before returning.
+     * The assumption is that this method will be called rather infrequently.
+     */
+    void pokeDrawLock(IBinder window);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f392682..a5fa5ed 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -47,6 +47,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -838,6 +839,7 @@
                 final int newDisplayState = mDisplay.getState();
                 if (oldDisplayState != newDisplayState) {
                     mAttachInfo.mDisplayState = newDisplayState;
+                    pokeDrawLockIfNeeded();
                     if (oldDisplayState != Display.STATE_UNKNOWN) {
                         final int oldScreenState = toViewScreenState(oldDisplayState);
                         final int newScreenState = toViewScreenState(newDisplayState);
@@ -868,6 +870,19 @@
         }
     };
 
+    void pokeDrawLockIfNeeded() {
+        final int displayState = mAttachInfo.mDisplayState;
+        if (mView != null && mAdded && mTraversalScheduled
+                && (displayState == Display.STATE_DOZE
+                        || displayState == Display.STATE_DOZE_SUSPEND)) {
+            try {
+                mWindowSession.pokeDrawLock(mWindow);
+            } catch (RemoteException ex) {
+                // System server died, oh well.
+            }
+        }
+    }
+
     @Override
     public void requestFitSystemWindows() {
         checkThread();
@@ -1042,6 +1057,7 @@
                 scheduleConsumeBatchedInput();
             }
             notifyRendererOfFramePending();
+            pokeDrawLockIfNeeded();
         }
     }
 
@@ -3084,17 +3100,6 @@
         return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
     }
 
-    private static void forceLayout(View view) {
-        view.forceLayout();
-        if (view instanceof ViewGroup) {
-            ViewGroup group = (ViewGroup) view;
-            final int count = group.getChildCount();
-            for (int i = 0; i < count; i++) {
-                forceLayout(group.getChildAt(i));
-            }
-        }
-    }
-
     private final static int MSG_INVALIDATE = 1;
     private final static int MSG_INVALIDATE_RECT = 2;
     private final static int MSG_DIE = 3;
@@ -3228,10 +3233,6 @@
                         mReportNextDraw = true;
                     }
 
-                    if (mView != null) {
-                        forceLayout(mView);
-                    }
-
                     requestLayout();
                 }
                 break;
@@ -3246,9 +3247,6 @@
                     mWinFrame.top = t;
                     mWinFrame.bottom = t + h;
 
-                    if (mView != null) {
-                        forceLayout(mView);
-                    }
                     requestLayout();
                 }
                 break;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 51c6a66..4c0520e 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1852,6 +1852,12 @@
         <item>users</item>
     </string-array>
 
+    <!-- Number of milliseconds to hold a wake lock to ensure that drawing is fully
+         flushed to the display while dozing.  This value needs to be large enough
+         to account for processing and rendering time plus a frame or two of latency
+         in the display pipeline plus some slack just to be sure. -->
+    <integer name="config_drawLockTimeoutMillis">120</integer>
+
     <!-- default telephony hardware configuration for this platform.
     -->
     <!-- this string array should be overridden by the device to present a list
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c418dc3..d240047 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -31,7 +31,7 @@
     <integer name="max_action_buttons">2</integer>
     <dimen name="toast_y_offset">64dip</dimen>
     <!-- Height of the status bar -->
-    <dimen name="status_bar_height">25dip</dimen>
+    <dimen name="status_bar_height">24dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_height">48dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 37c6d29..615f445 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -345,6 +345,7 @@
   <java-symbol type="integer" name="config_bluetooth_max_advertisers" />
   <java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
+  <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
   <java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 70693f2..c4794d9 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -173,7 +173,7 @@
 
     @Override
     public void setColorFilter(ColorFilter cf) {
-        mDrawableContainerState.mHasColorFilter = (cf != null);
+        mDrawableContainerState.mHasColorFilter = true;
 
         if (mDrawableContainerState.mColorFilter != cf) {
             mDrawableContainerState.mColorFilter = cf;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index e753a7c..bfbf028 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -18,8 +18,14 @@
 
 import com.android.org.conscrypt.NativeCrypto;
 
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.OperationResult;
 import android.util.Log;
 
 import java.util.Locale;
@@ -58,6 +64,8 @@
 
     private final IKeystoreService mBinder;
 
+    private IBinder mToken;
+
     private KeyStore(IKeystoreService binder) {
         mBinder = binder;
     }
@@ -68,6 +76,13 @@
         return new KeyStore(keystore);
     }
 
+    private synchronized IBinder getToken() {
+        if (mToken == null) {
+            mToken = new Binder();
+        }
+        return mToken;
+    }
+
     static int getKeyTypeForAlgorithm(String keyType) {
         if ("RSA".equalsIgnoreCase(keyType)) {
             return NativeCrypto.EVP_PKEY_RSA;
@@ -363,4 +378,100 @@
     public int getLastError() {
         return mError;
     }
+
+    public boolean addRngEntropy(byte[] data) {
+        try {
+            return mBinder.addRngEntropy(data) == NO_ERROR;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return false;
+        }
+    }
+
+    public int generateKey(String alias, KeymasterArguments args, int uid, int flags,
+            KeyCharacteristics outCharacteristics) {
+        try {
+            return mBinder.generateKey(alias, args, uid, flags, outCharacteristics);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return SYSTEM_ERROR;
+        }
+    }
+
+    public int generateKey(String alias, KeymasterArguments args, int flags,
+            KeyCharacteristics outCharacteristics) {
+        return generateKey(alias, args, UID_SELF, flags, outCharacteristics);
+    }
+
+    public int getKeyCharacteristics(String alias, byte[] clientId, byte[] appId,
+            KeyCharacteristics outCharacteristics) {
+        try {
+            return mBinder.getKeyCharacteristics(alias, clientId, appId, outCharacteristics);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return SYSTEM_ERROR;
+        }
+    }
+
+    public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
+            int uid, int flags, KeyCharacteristics outCharacteristics) {
+        try {
+            return mBinder.importKey(alias, args, format, keyData, uid, flags,
+                    outCharacteristics);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return SYSTEM_ERROR;
+        }
+    }
+
+    public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
+            int flags, KeyCharacteristics outCharacteristics) {
+        return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics);
+    }
+
+    public ExportResult exportKey(String alias, int format, byte[] clientId, byte[] appId) {
+        try {
+            return mBinder.exportKey(alias, format, clientId, appId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return null;
+        }
+    }
+
+    public OperationResult begin(String alias, int purpose, boolean pruneable,
+            KeymasterArguments args, KeymasterArguments outArgs) {
+        try {
+            return mBinder.begin(getToken(), alias, purpose, pruneable, args, outArgs);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return null;
+        }
+    }
+
+    public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
+        try {
+            return mBinder.update(token, arguments, input);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return null;
+        }
+    }
+
+    public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) {
+        try {
+            return mBinder.finish(token, arguments, signature);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return null;
+        }
+    }
+
+    public int abort(IBinder token) {
+        try {
+            return mBinder.abort(token);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return SYSTEM_ERROR;
+        }
+    }
 }
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 4b3382e..782806e 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -532,7 +532,7 @@
 void _FileAsset::close(void)
 {
     if (mMap != NULL) {
-        mMap->release();
+        delete mMap;
         mMap = NULL;
     }
     if (mBuf != NULL) {
@@ -612,7 +612,7 @@
 
         map = new FileMap;
         if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
-            map->release();
+            delete map;
             return NULL;
         }
 
@@ -827,7 +827,7 @@
 void _CompressedAsset::close(void)
 {
     if (mMap != NULL) {
-        mMap->release();
+        delete mMap;
         mMap = NULL;
     }
 
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index ef0d072..af3d9b3 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -200,7 +200,7 @@
 
     FileMap* newMap = new FileMap();
     if (!newMap->create(mFileName, fd, ze.offset, actualLen, true)) {
-        newMap->release();
+        delete newMap;
         return NULL;
     }
 
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 81d5afe..97b3f63 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -437,10 +437,7 @@
     public void setCaptureRate(double fps) {
         // Make sure that time lapse is enabled when this method is called.
         setParameter("time-lapse-enable=1");
-
-        double timeBetweenFrameCapture = 1 / fps;
-        long timeBetweenFrameCaptureUs = (long) (1000000 * timeBetweenFrameCapture);
-        setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureUs);
+        setParameter("time-lapse-fps=" + fps);
     }
 
     /**
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..6e29d23
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..6110e9e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..6cca225
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..6445f2a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..78c0294
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..2fcc3b0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..70d35bf
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..2d9b75e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..b6ebe34
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..8b67b91
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..1fa0a3d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..175bd78
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..05b27e8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..6e9f8ae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..5f3371f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..539d77f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..3216776
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..4f381ba
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..c67127d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..d3b356b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..2d38129
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..fb76575
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..d8b68eb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..02cc3af
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..7805b7a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..8127774
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..84b8085
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..289d6ac
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..72bc804
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..e31ce2b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..f23b0e7
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..1e12f96
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..8b547d9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..03c5033
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..b9a9923
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..989e1ab
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..de8c389
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..2eb8a92
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f055a2c..870afeb 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -67,4 +67,87 @@
 
     <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
     <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
+
+    <!-- Bluetooth settings.  Message when a device is disconnected -->
+    <string name="bluetooth_disconnected">Disconnected</string>
+    <!-- Bluetooth settings.  Message when disconnecting from a device -->
+    <string name="bluetooth_disconnecting">Disconnecting\u2026</string>
+     <!-- Bluetooth settings.  Message when connecting to a device -->
+    <string name="bluetooth_connecting">Connecting\u2026</string>
+    <!-- Bluetooth settings.  Message when connected to a device. [CHAR LIMIT=40] -->
+    <string name="bluetooth_connected">Connected</string>
+
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the A2DP profile. -->
+    <string name="bluetooth_profile_a2dp">Media audio</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the headset or handsfree profile. -->
+    <string name="bluetooth_profile_headset">Phone audio</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the OPP profile. -->
+    <string name="bluetooth_profile_opp">File transfer</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. -->
+    <string name="bluetooth_profile_hid">Input device</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
+    <string name="bluetooth_profile_pan">Internet access</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
+    <string name="bluetooth_profile_pbap">Contact sharing</string>
+    <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
+    <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
+    <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the map profile. -->
+    <string name="bluetooth_profile_map">Message Access</string>
+
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference when A2DP is connected. -->
+    <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the headset checkbox preference when headset is connected. -->
+    <string name="bluetooth_headset_profile_summary_connected">Connected to phone audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference when OPP is connected. -->
+    <string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the map checkbox preference when map is connected. -->
+    <string name="bluetooth_map_profile_summary_connected">Connected to map</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference when OPP is not connected. -->
+    <string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string>
+    <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
+    <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string>
+    <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
+    <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string>
+    <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
+    <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string>
+
+    <!-- Bluetooth settings. Connection options screen. The summary
+         for the PAN checkbox preference that describes how checking it
+         will set the PAN profile as preferred. -->
+    <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
+    <!-- Bluetooth settings. Connection options screen.  The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
+    <string name="bluetooth_map_profile_summary_use_for">Use for map</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. -->
+    <string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. -->
+    <string name="bluetooth_headset_profile_summary_use_for">Use for phone audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference that describes how checking it will set the OPP profile as preferred. -->
+    <string name="bluetooth_opp_profile_summary_use_for">Use for file transfer</string>
+    <!-- Bluetooth settings. Connection options screen. The summary
+         for the HID checkbox preference that describes how checking it
+         will set the HID profile as preferred. -->
+    <string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
+
+    <!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] -->
+    <string name="bluetooth_pairing_accept">Pair</string>
+    <!-- Button text for accepting an incoming pairing request in all caps. [CHAR LIMIT=20] -->
+    <string name="bluetooth_pairing_accept_all_caps">PAIR</string>
+    <!-- Button text for declining an incoming pairing request. [CHAR LIMIT=20] -->
+    <string name="bluetooth_pairing_decline">Cancel</string>
+
+    <!-- Message in pairing dialogs.  [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string>
+    <!-- Message for the error dialog when BT pairing fails generically. -->
+    <string name="bluetooth_pairing_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g>.</string>
+
+    <!-- Message for the error dialog when BT pairing fails because the PIN /
+    Passkey entered is incorrect. -->
+    <string name="bluetooth_pairing_pin_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g> because of an incorrect PIN or passkey.</string>
+    <!-- Message for the error dialog when BT pairing fails because the other device is down. -->
+    <string name="bluetooth_pairing_device_down_error_message">Can\'t communicate with <xliff:g id="device_name">%1$s</xliff:g>.</string>
+    <!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. -->
+    <string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
new file mode 100644
index 0000000..58e5e29a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+package com.android.settingslib;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+public class TetherUtil {
+
+    // Types of tethering.
+    public static final int TETHERING_INVALID   = -1;
+    public static final int TETHERING_WIFI      = 0;
+    public static final int TETHERING_USB       = 1;
+    public static final int TETHERING_BLUETOOTH = 2;
+
+    // Extras used for communicating with the TetherService.
+    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+    /**
+     * Tells the service to run a provision check now.
+     */
+    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    /**
+     * Enables wifi tethering if the provision check is successful. Used by
+     * QS to enable tethering.
+     */
+    public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
+
+    public static ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
+            .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
+
+    public static boolean setWifiTethering(boolean enable, Context context) {
+        final WifiManager wifiManager =
+                (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        final ContentResolver cr = context.getContentResolver();
+        /**
+         * Disable Wifi if enabling tethering
+         */
+        int wifiState = wifiManager.getWifiState();
+        if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
+                    (wifiState == WifiManager.WIFI_STATE_ENABLED))) {
+            wifiManager.setWifiEnabled(false);
+            Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
+        }
+
+        boolean success = wifiManager.setWifiApEnabled(null, enable);
+        /**
+         *  If needed, restore Wifi on tether disable
+         */
+        if (!enable) {
+            int wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+            if (wifiSavedState == 1) {
+                wifiManager.setWifiEnabled(true);
+                Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+            }
+        }
+        return success;
+    }
+
+    public static boolean isWifiTetherEnabled(Context context) {
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        return wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED;
+    }
+
+    public static boolean isProvisioningNeeded(Context context) {
+        // Keep in sync with other usage of config_mobile_hotspot_provision_app.
+        // ConnectivityManager#enforceTetherChangePermission
+        String[] provisionApp = context.getResources().getStringArray(
+                com.android.internal.R.array.config_mobile_hotspot_provision_app);
+        if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
+                || provisionApp == null) {
+            return false;
+        }
+        return (provisionApp.length == 2);
+    }
+
+    public static boolean isTetheringSupported(Context context) {
+        final ConnectivityManager cm =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER;
+        return !isSecondaryUser && cm.isTetheringSupported();
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
new file mode 100755
index 0000000..9608daa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class A2dpProfile implements LocalBluetoothProfile {
+    private static final String TAG = "A2dpProfile";
+    private static boolean V = false;
+
+    private BluetoothA2dp mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+
+    static final ParcelUuid[] SINK_UUIDS = {
+        BluetoothUuid.AudioSink,
+        BluetoothUuid.AdvAudioDist,
+    };
+
+    static final String NAME = "A2DP";
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 1;
+
+    // These callbacks run on the main thread.
+    private final class A2dpServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothA2dp) proxy;
+            // We just bound to the service, so refresh the UI for any connected A2DP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    A2dpProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
+                BluetoothProfile.A2DP);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        // Downgrade priority as user is disconnecting the headset.
+        if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
+            mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        }
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+    boolean isA2dpPlaying() {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (!sinks.isEmpty()) {
+            if (mService.isA2dpPlaying(sinks.get(0))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_a2dp;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_a2dp_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_a2dp_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_headphones_a2dp;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up A2DP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
new file mode 100644
index 0000000..b802f58
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+
+/**
+ * BluetoothCallback provides a callback interface for the settings
+ * UI to receive events from {@link BluetoothEventManager}.
+ */
+public interface BluetoothCallback {
+    void onBluetoothStateChanged(int bluetoothState);
+    void onScanningStateChanged(boolean started);
+    void onDeviceAdded(CachedBluetoothDevice cachedDevice);
+    void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
+    void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java
new file mode 100644
index 0000000..8dec86a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+/**
+ * BluetoothDeviceFilter contains a static method that returns a
+ * Filter object that returns whether or not the BluetoothDevice
+ * passed to it matches the specified filter type constant from
+ * {@link android.bluetooth.BluetoothDevicePicker}.
+ */
+public final class BluetoothDeviceFilter {
+    private static final String TAG = "BluetoothDeviceFilter";
+
+    /** The filter interface to external classes. */
+    public interface Filter {
+        boolean matches(BluetoothDevice device);
+    }
+
+    /** All filter singleton (referenced directly). */
+    public static final Filter ALL_FILTER = new AllFilter();
+
+    /** Bonded devices only filter (referenced directly). */
+    public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
+
+    /** Unbonded devices only filter (referenced directly). */
+    public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
+
+    /** Table of singleton filter objects. */
+    private static final Filter[] FILTERS = {
+            ALL_FILTER,             // FILTER_TYPE_ALL
+            new AudioFilter(),      // FILTER_TYPE_AUDIO
+            new TransferFilter(),   // FILTER_TYPE_TRANSFER
+            new PanuFilter(),       // FILTER_TYPE_PANU
+            new NapFilter()         // FILTER_TYPE_NAP
+    };
+
+    /** Private constructor. */
+    private BluetoothDeviceFilter() {
+    }
+
+    /**
+     * Returns the singleton {@link Filter} object for the specified type,
+     * or {@link #ALL_FILTER} if the type value is out of range.
+     *
+     * @param filterType a constant from BluetoothDevicePicker
+     * @return a singleton object implementing the {@link Filter} interface.
+     */
+    public static Filter getFilter(int filterType) {
+        if (filterType >= 0 && filterType < FILTERS.length) {
+            return FILTERS[filterType];
+        } else {
+            Log.w(TAG, "Invalid filter type " + filterType + " for device picker");
+            return ALL_FILTER;
+        }
+    }
+
+    /** Filter that matches all devices. */
+    private static final class AllFilter implements Filter {
+        public boolean matches(BluetoothDevice device) {
+            return true;
+        }
+    }
+
+    /** Filter that matches only bonded devices. */
+    private static final class BondedDeviceFilter implements Filter {
+        public boolean matches(BluetoothDevice device) {
+            return device.getBondState() == BluetoothDevice.BOND_BONDED;
+        }
+    }
+
+    /** Filter that matches only unbonded devices. */
+    private static final class UnbondedDeviceFilter implements Filter {
+        public boolean matches(BluetoothDevice device) {
+            return device.getBondState() != BluetoothDevice.BOND_BONDED;
+        }
+    }
+
+    /** Parent class of filters based on UUID and/or Bluetooth class. */
+    private abstract static class ClassUuidFilter implements Filter {
+        abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
+
+        public boolean matches(BluetoothDevice device) {
+            return matches(device.getUuids(), device.getBluetoothClass());
+        }
+    }
+
+    /** Filter that matches devices that support AUDIO profiles. */
+    private static final class AudioFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) {
+                    return true;
+                }
+                if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) {
+                    return true;
+                }
+            } else if (btClass != null) {
+                if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) ||
+                        btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /** Filter that matches devices that support Object Transfer. */
+    private static final class TransferFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+                    return true;
+                }
+            }
+            return btClass != null
+                    && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP);
+        }
+    }
+
+    /** Filter that matches devices that support PAN User (PANU) profile. */
+    private static final class PanuFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) {
+                    return true;
+                }
+            }
+            return btClass != null
+                    && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU);
+        }
+    }
+
+    /** Filter that matches devices that support NAP profile. */
+    private static final class NapFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) {
+                    return true;
+                }
+            }
+            return btClass != null
+                    && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
new file mode 100644
index 0000000..acb7e7a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+/* Required to handle timeout notification when phone is suspended */
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver {
+    private static final String TAG = "BluetoothDiscoverableTimeoutReceiver";
+
+    private static final String INTENT_DISCOVERABLE_TIMEOUT =
+        "android.bluetooth.intent.DISCOVERABLE_TIMEOUT";
+
+    public static void setDiscoverableAlarm(Context context, long alarmTime) {
+        Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime);
+
+        Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
+        intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
+        PendingIntent pending = PendingIntent.getBroadcast(
+            context, 0, intent, 0);
+        AlarmManager alarmManager =
+              (AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
+
+        if (pending != null) {
+            // Cancel any previous alarms that do the same thing.
+            alarmManager.cancel(pending);
+            Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm");
+        }
+        pending = PendingIntent.getBroadcast(
+            context, 0, intent, 0);
+
+        alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending);
+    }
+
+    public static void cancelDiscoverableAlarm(Context context) {
+        Log.d(TAG, "cancelDiscoverableAlarm(): Enter");
+
+        Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
+        intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
+        PendingIntent pending = PendingIntent.getBroadcast(
+            context, 0, intent, PendingIntent.FLAG_NO_CREATE);
+        if (pending != null) {
+            // Cancel any previous alarms that do the same thing.
+            AlarmManager alarmManager =
+              (AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
+
+            alarmManager.cancel(pending);
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance();
+
+         if(localBluetoothAdapter != null  &&
+            localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
+            Log.d(TAG, "Disable discoverable...");
+
+            localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+         } else {
+            Log.e(TAG, "localBluetoothAdapter is NULL!!");
+        }
+    }
+};
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
new file mode 100755
index 0000000..7c92368
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
+ * API and dispatches the event on the UI thread to the right class in the
+ * Settings.
+ */
+public final class BluetoothEventManager {
+    private static final String TAG = "BluetoothEventManager";
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private LocalBluetoothProfileManager mProfileManager;
+    private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
+    private final Map<String, Handler> mHandlerMap;
+    private Context mContext;
+
+    private final Collection<BluetoothCallback> mCallbacks =
+            new ArrayList<BluetoothCallback>();
+
+    interface Handler {
+        void onReceive(Context context, Intent intent, BluetoothDevice device);
+    }
+
+    void addHandler(String action, Handler handler) {
+        mHandlerMap.put(action, handler);
+        mAdapterIntentFilter.addAction(action);
+    }
+
+    void addProfileHandler(String action, Handler handler) {
+        mHandlerMap.put(action, handler);
+        mProfileIntentFilter.addAction(action);
+    }
+
+    // Set profile manager after construction due to circular dependency
+    void setProfileManager(LocalBluetoothProfileManager manager) {
+        mProfileManager = manager;
+    }
+
+    BluetoothEventManager(LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager, Context context) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mAdapterIntentFilter = new IntentFilter();
+        mProfileIntentFilter = new IntentFilter();
+        mHandlerMap = new HashMap<String, Handler>();
+        mContext = context;
+
+        // Bluetooth on/off broadcasts
+        addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
+
+        // Discovery broadcasts
+        addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
+        addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
+        addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
+        addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
+        addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
+
+        // Pairing broadcasts
+        addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
+        addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
+
+        // Fine-grained state broadcasts
+        addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
+        addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
+
+        // Dock event broadcasts
+        addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
+
+        mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
+    }
+
+    void registerProfileIntentReceiver() {
+        mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter);
+    }
+
+    /** Register to start receiving callbacks for Bluetooth events. */
+    public void registerCallback(BluetoothCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    /** Unregister to stop receiving callbacks for Bluetooth events. */
+    public void unregisterCallback(BluetoothCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent
+                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            Handler handler = mHandlerMap.get(action);
+            if (handler != null) {
+                handler.onReceive(context, intent, device);
+            }
+        }
+    };
+
+    private class AdapterStateChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                                    BluetoothAdapter.ERROR);
+            // update local profiles and get paired devices
+            mLocalAdapter.setBluetoothStateInt(state);
+            // send callback to update UI and possibly start scanning
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onBluetoothStateChanged(state);
+                }
+            }
+            // Inform CachedDeviceManager that the adapter state has changed
+            mDeviceManager.onBluetoothStateChanged(state);
+        }
+    }
+
+    private class ScanningStateChangedHandler implements Handler {
+        private final boolean mStarted;
+
+        ScanningStateChangedHandler(boolean started) {
+            mStarted = started;
+        }
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onScanningStateChanged(mStarted);
+                }
+            }
+            mDeviceManager.onScanningStateChanged(mStarted);
+        }
+    }
+
+    private class DeviceFoundHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
+            BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
+            String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
+            // TODO Pick up UUID. They should be available for 2.1 devices.
+            // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
+                        + cachedDevice);
+                // callback to UI to create Preference for new device
+                dispatchDeviceAdded(cachedDevice);
+            }
+            cachedDevice.setRssi(rssi);
+            cachedDevice.setBtClass(btClass);
+            cachedDevice.setNewName(name);
+            cachedDevice.setVisible(true);
+        }
+    }
+
+    private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+        synchronized (mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onDeviceAdded(cachedDevice);
+            }
+        }
+    }
+
+    private class DeviceDisappearedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
+                return;
+            }
+            if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
+                synchronized (mCallbacks) {
+                    for (BluetoothCallback callback : mCallbacks) {
+                        callback.onDeviceDeleted(cachedDevice);
+                    }
+                }
+            }
+        }
+    }
+
+    private class NameChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            mDeviceManager.onDeviceNameUpdated(device);
+        }
+    }
+
+    private class BondStateChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            if (device == null) {
+                Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+                return;
+            }
+            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                                               BluetoothDevice.ERROR);
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "CachedBluetoothDevice for device " + device +
+                        " not found, calling readPairedDevices().");
+                if (!readPairedDevices()) {
+                    Log.e(TAG, "Got bonding state changed for " + device +
+                            ", but we have no record of that device.");
+                    return;
+                }
+                cachedDevice = mDeviceManager.findDevice(device);
+                if (cachedDevice == null) {
+                    Log.e(TAG, "Got bonding state changed for " + device +
+                            ", but device not added in cache.");
+                    return;
+                }
+            }
+
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onDeviceBondStateChanged(cachedDevice, bondState);
+                }
+            }
+            cachedDevice.onBondingStateChanged(bondState);
+
+            if (bondState == BluetoothDevice.BOND_NONE) {
+                int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
+                        BluetoothDevice.ERROR);
+
+                showUnbondMessage(context, cachedDevice.getName(), reason);
+            }
+        }
+
+        /**
+         * Called when we have reached the unbonded state.
+         *
+         * @param reason one of the error reasons from
+         *            BluetoothDevice.UNBOND_REASON_*
+         */
+        private void showUnbondMessage(Context context, String name, int reason) {
+            int errorMsg;
+
+            switch(reason) {
+            case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
+                errorMsg = R.string.bluetooth_pairing_pin_error_message;
+                break;
+            case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
+                errorMsg = R.string.bluetooth_pairing_rejected_error_message;
+                break;
+            case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
+                errorMsg = R.string.bluetooth_pairing_device_down_error_message;
+                break;
+            case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
+            case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
+            case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
+            case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
+                errorMsg = R.string.bluetooth_pairing_error_message;
+                break;
+            default:
+                Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
+                return;
+            }
+            Utils.showError(context, name, errorMsg);
+        }
+    }
+
+    private class ClassChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            mDeviceManager.onBtClassChanged(device);
+        }
+    }
+
+    private class UuidChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            mDeviceManager.onUuidChanged(device);
+        }
+    }
+
+    private class PairingCancelHandler implements Handler {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            if (device == null) {
+                Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
+                return;
+            }
+            int errorMsg = R.string.bluetooth_pairing_error_message;
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            Utils.showError(context, cachedDevice.getName(), errorMsg);
+        }
+    }
+
+    private class DockEventHandler implements Handler {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            // Remove if unpair device upon undocking
+            int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
+            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
+            if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
+                    CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+                    if (cachedDevice != null) {
+                        cachedDevice.setVisible(false);
+                    }
+                }
+            }
+        }
+    }
+    boolean readPairedDevices() {
+        Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
+        if (bondedDevices == null) {
+            return false;
+        }
+
+        boolean deviceAdded = false;
+        for (BluetoothDevice device : bondedDevices) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+                dispatchDeviceAdded(cachedDevice);
+                deviceAdded = true;
+            }
+        }
+
+        return deviceAdded;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
new file mode 100755
index 0000000..ddcc49f7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.ParcelUuid;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.bluetooth.BluetoothAdapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * CachedBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
+    private static final String TAG = "CachedBluetoothDevice";
+    private static final boolean DEBUG = Utils.V;
+
+    private final Context mContext;
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final LocalBluetoothProfileManager mProfileManager;
+    private final BluetoothDevice mDevice;
+    private String mName;
+    private short mRssi;
+    private BluetoothClass mBtClass;
+    private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
+
+    private final List<LocalBluetoothProfile> mProfiles =
+            new ArrayList<LocalBluetoothProfile>();
+
+    // List of profiles that were previously in mProfiles, but have been removed
+    private final List<LocalBluetoothProfile> mRemovedProfiles =
+            new ArrayList<LocalBluetoothProfile>();
+
+    // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
+    private boolean mLocalNapRoleConnected;
+
+    private boolean mVisible;
+
+    private int mPhonebookPermissionChoice;
+
+    private int mMessagePermissionChoice;
+
+    private int mMessageRejectionCount;
+
+    private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
+
+    // Following constants indicate the user's choices of Phone book/message access settings
+    // User hasn't made any choice or settings app has wiped out the memory
+    public final static int ACCESS_UNKNOWN = 0;
+    // User has accepted the connection and let Settings app remember the decision
+    public final static int ACCESS_ALLOWED = 1;
+    // User has rejected the connection and let Settings app remember the decision
+    public final static int ACCESS_REJECTED = 2;
+
+    // How many times user should reject the connection to make the choice persist.
+    private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2;
+
+    private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
+
+    /**
+     * When we connect to multiple profiles, we only want to display a single
+     * error even if they all fail. This tracks that state.
+     */
+    private boolean mIsConnectingErrorPossible;
+
+    /**
+     * Last time a bt profile auto-connect was attempted.
+     * If an ACTION_UUID intent comes in within
+     * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
+     * again with the new UUIDs
+     */
+    private long mConnectAttempted;
+
+    // See mConnectAttempted
+    private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
+
+    /** Auto-connect after pairing only if locally initiated. */
+    private boolean mConnectAfterPairing;
+
+    /**
+     * Describes the current device and profile for logging.
+     *
+     * @param profile Profile to describe
+     * @return Description of the device and profile
+     */
+    private String describe(LocalBluetoothProfile profile) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Address:").append(mDevice);
+        if (profile != null) {
+            sb.append(" Profile:").append(profile);
+        }
+
+        return sb.toString();
+    }
+
+    void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
+        if (Utils.D) {
+            Log.d(TAG, "onProfileStateChanged: profile " + profile +
+                    " newProfileState " + newProfileState);
+        }
+        if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
+        {
+            if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
+            return;
+        }
+        mProfileConnectionState.put(profile, newProfileState);
+        if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
+            if (profile instanceof MapProfile) {
+                profile.setPreferred(mDevice, true);
+            } else if (!mProfiles.contains(profile)) {
+                mRemovedProfiles.remove(profile);
+                mProfiles.add(profile);
+                if (profile instanceof PanProfile &&
+                        ((PanProfile) profile).isLocalRoleNap(mDevice)) {
+                    // Device doesn't support NAP, so remove PanProfile on disconnect
+                    mLocalNapRoleConnected = true;
+                }
+            }
+        } else if (profile instanceof MapProfile &&
+                newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+            profile.setPreferred(mDevice, false);
+        } else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
+                ((PanProfile) profile).isLocalRoleNap(mDevice) &&
+                newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+            Log.d(TAG, "Removing PanProfile from device after NAP disconnect");
+            mProfiles.remove(profile);
+            mRemovedProfiles.add(profile);
+            mLocalNapRoleConnected = false;
+        }
+    }
+
+    CachedBluetoothDevice(Context context,
+                          LocalBluetoothAdapter adapter,
+                          LocalBluetoothProfileManager profileManager,
+                          BluetoothDevice device) {
+        mContext = context;
+        mLocalAdapter = adapter;
+        mProfileManager = profileManager;
+        mDevice = device;
+        mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
+        fillData();
+    }
+
+    public void disconnect() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            disconnect(profile);
+        }
+        // Disconnect  PBAP server in case its connected
+        // This is to ensure all the profiles are disconnected as some CK/Hs do not
+        // disconnect  PBAP connection when HF connection is brought down
+        PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
+        if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
+        {
+            PbapProfile.disconnect(mDevice);
+        }
+    }
+
+    public void disconnect(LocalBluetoothProfile profile) {
+        if (profile.disconnect(mDevice)) {
+            if (Utils.D) {
+                Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
+            }
+        }
+    }
+
+    public void connect(boolean connectAllProfiles) {
+        if (!ensurePaired()) {
+            return;
+        }
+
+        mConnectAttempted = SystemClock.elapsedRealtime();
+        connectWithoutResettingTimer(connectAllProfiles);
+    }
+
+    void onBondingDockConnect() {
+        // Attempt to connect if UUIDs are available. Otherwise,
+        // we will connect when the ACTION_UUID intent arrives.
+        connect(false);
+    }
+
+    private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+        // Try to initialize the profiles if they were not.
+        if (mProfiles.isEmpty()) {
+            // if mProfiles is empty, then do not invoke updateProfiles. This causes a race
+            // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated
+            // from bluetooth stack but ACTION.uuid is not sent yet.
+            // Eventually ACTION.uuid will be received which shall trigger the connection of the
+            // various profiles
+            // If UUIDs are not available yet, connect will be happen
+            // upon arrival of the ACTION_UUID intent.
+            Log.d(TAG, "No profiles. Maybe we will connect later");
+            return;
+        }
+
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+
+        int preferredProfiles = 0;
+        for (LocalBluetoothProfile profile : mProfiles) {
+            if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
+                if (profile.isPreferred(mDevice)) {
+                    ++preferredProfiles;
+                    connectInt(profile);
+                }
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
+
+        if (preferredProfiles == 0) {
+            connectAutoConnectableProfiles();
+        }
+    }
+
+    private void connectAutoConnectableProfiles() {
+        if (!ensurePaired()) {
+            return;
+        }
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+
+        for (LocalBluetoothProfile profile : mProfiles) {
+            if (profile.isAutoConnectable()) {
+                profile.setPreferred(mDevice, true);
+                connectInt(profile);
+            }
+        }
+    }
+
+    /**
+     * Connect this device to the specified profile.
+     *
+     * @param profile the profile to use with the remote device
+     */
+    public void connectProfile(LocalBluetoothProfile profile) {
+        mConnectAttempted = SystemClock.elapsedRealtime();
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+        connectInt(profile);
+        // Refresh the UI based on profile.connect() call
+        refresh();
+    }
+
+    synchronized void connectInt(LocalBluetoothProfile profile) {
+        if (!ensurePaired()) {
+            return;
+        }
+        if (profile.connect(mDevice)) {
+            if (Utils.D) {
+                Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
+            }
+            return;
+        }
+        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+    }
+
+    private boolean ensurePaired() {
+        if (getBondState() == BluetoothDevice.BOND_NONE) {
+            startPairing();
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public boolean startPairing() {
+        // Pairing is unreliable while scanning, so cancel discovery
+        if (mLocalAdapter.isDiscovering()) {
+            mLocalAdapter.cancelDiscovery();
+        }
+
+        if (!mDevice.createBond()) {
+            return false;
+        }
+
+        mConnectAfterPairing = true;  // auto-connect after pairing
+        return true;
+    }
+
+    /**
+     * Return true if user initiated pairing on this device. The message text is
+     * slightly different for local vs. remote initiated pairing dialogs.
+     */
+    boolean isUserInitiatedPairing() {
+        return mConnectAfterPairing;
+    }
+
+    public void unpair() {
+        int state = getBondState();
+
+        if (state == BluetoothDevice.BOND_BONDING) {
+            mDevice.cancelBondProcess();
+        }
+
+        if (state != BluetoothDevice.BOND_NONE) {
+            final BluetoothDevice dev = mDevice;
+            if (dev != null) {
+                final boolean successful = dev.removeBond();
+                if (successful) {
+                    if (Utils.D) {
+                        Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
+                    }
+                } else if (Utils.V) {
+                    Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
+                            describe(null));
+                }
+            }
+        }
+    }
+
+    public int getProfileConnectionState(LocalBluetoothProfile profile) {
+        if (mProfileConnectionState == null ||
+                mProfileConnectionState.get(profile) == null) {
+            // If cache is empty make the binder call to get the state
+            int state = profile.getConnectionStatus(mDevice);
+            mProfileConnectionState.put(profile, state);
+        }
+        return mProfileConnectionState.get(profile);
+    }
+
+    public void clearProfileConnectionState ()
+    {
+        if (Utils.D) {
+            Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
+        }
+        for (LocalBluetoothProfile profile :getProfiles()) {
+            mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
+        }
+    }
+
+    // TODO: do any of these need to run async on a background thread?
+    private void fillData() {
+        fetchName();
+        fetchBtClass();
+        updateProfiles();
+        migratePhonebookPermissionChoice();
+        migrateMessagePermissionChoice();
+        fetchMessageRejectionCount();
+
+        mVisible = false;
+        dispatchAttributesChanged();
+    }
+
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Populate name from BluetoothDevice.ACTION_FOUND intent
+     */
+    void setNewName(String name) {
+        if (mName == null) {
+            mName = name;
+            if (mName == null || TextUtils.isEmpty(mName)) {
+                mName = mDevice.getAddress();
+            }
+            dispatchAttributesChanged();
+        }
+    }
+
+    /**
+     * user changes the device name
+     */
+    public void setName(String name) {
+        if (!mName.equals(name)) {
+            mName = name;
+            mDevice.setAlias(name);
+            dispatchAttributesChanged();
+        }
+    }
+
+    void refreshName() {
+        fetchName();
+        dispatchAttributesChanged();
+    }
+
+    private void fetchName() {
+        mName = mDevice.getAliasName();
+
+        if (TextUtils.isEmpty(mName)) {
+            mName = mDevice.getAddress();
+            if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
+        }
+    }
+
+    void refresh() {
+        dispatchAttributesChanged();
+    }
+
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    public void setVisible(boolean visible) {
+        if (mVisible != visible) {
+            mVisible = visible;
+            dispatchAttributesChanged();
+        }
+    }
+
+    public int getBondState() {
+        return mDevice.getBondState();
+    }
+
+    void setRssi(short rssi) {
+        if (mRssi != rssi) {
+            mRssi = rssi;
+            dispatchAttributesChanged();
+        }
+    }
+
+    /**
+     * Checks whether we are connected to this device (any profile counts).
+     *
+     * @return Whether it is connected.
+     */
+    public boolean isConnected() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            int status = getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTED) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean isConnectedProfile(LocalBluetoothProfile profile) {
+        int status = getProfileConnectionState(profile);
+        return status == BluetoothProfile.STATE_CONNECTED;
+
+    }
+
+    public boolean isBusy() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            int status = getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTING
+                    || status == BluetoothProfile.STATE_DISCONNECTING) {
+                return true;
+            }
+        }
+        return getBondState() == BluetoothDevice.BOND_BONDING;
+    }
+
+    /**
+     * Fetches a new value for the cached BT class.
+     */
+    private void fetchBtClass() {
+        mBtClass = mDevice.getBluetoothClass();
+    }
+
+    private boolean updateProfiles() {
+        ParcelUuid[] uuids = mDevice.getUuids();
+        if (uuids == null) return false;
+
+        ParcelUuid[] localUuids = mLocalAdapter.getUuids();
+        if (localUuids == null) return false;
+
+        /**
+         * Now we know if the device supports PBAP, update permissions...
+         */
+        processPhonebookAccess();
+
+        mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
+                                       mLocalNapRoleConnected, mDevice);
+
+        if (DEBUG) {
+            Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
+            BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
+
+            if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
+            Log.v(TAG, "UUID:");
+            for (ParcelUuid uuid : uuids) {
+                Log.v(TAG, "  " + uuid);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Refreshes the UI for the BT class, including fetching the latest value
+     * for the class.
+     */
+    void refreshBtClass() {
+        fetchBtClass();
+        dispatchAttributesChanged();
+    }
+
+    /**
+     * Refreshes the UI when framework alerts us of a UUID change.
+     */
+    void onUuidChanged() {
+        updateProfiles();
+
+        if (DEBUG) {
+            Log.e(TAG, "onUuidChanged: Time since last connect"
+                    + (SystemClock.elapsedRealtime() - mConnectAttempted));
+        }
+
+        /*
+         * If a connect was attempted earlier without any UUID, we will do the
+         * connect now.
+         */
+        if (!mProfiles.isEmpty()
+                && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
+                        .elapsedRealtime()) {
+            connectWithoutResettingTimer(false);
+        }
+        dispatchAttributesChanged();
+    }
+
+    void onBondingStateChanged(int bondState) {
+        if (bondState == BluetoothDevice.BOND_NONE) {
+            mProfiles.clear();
+            mConnectAfterPairing = false;  // cancel auto-connect
+            setPhonebookPermissionChoice(ACCESS_UNKNOWN);
+            setMessagePermissionChoice(ACCESS_UNKNOWN);
+            mMessageRejectionCount = 0;
+            saveMessageRejectionCount();
+        }
+
+        refresh();
+
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            if (mDevice.isBluetoothDock()) {
+                onBondingDockConnect();
+            } else if (mConnectAfterPairing) {
+                connect(false);
+            }
+            mConnectAfterPairing = false;
+        }
+    }
+
+    void setBtClass(BluetoothClass btClass) {
+        if (btClass != null && mBtClass != btClass) {
+            mBtClass = btClass;
+            dispatchAttributesChanged();
+        }
+    }
+
+    public BluetoothClass getBtClass() {
+        return mBtClass;
+    }
+
+    public List<LocalBluetoothProfile> getProfiles() {
+        return Collections.unmodifiableList(mProfiles);
+    }
+
+    public List<LocalBluetoothProfile> getConnectableProfiles() {
+        List<LocalBluetoothProfile> connectableProfiles =
+                new ArrayList<LocalBluetoothProfile>();
+        for (LocalBluetoothProfile profile : mProfiles) {
+            if (profile.isConnectable()) {
+                connectableProfiles.add(profile);
+            }
+        }
+        return connectableProfiles;
+    }
+
+    public List<LocalBluetoothProfile> getRemovedProfiles() {
+        return mRemovedProfiles;
+    }
+
+    public void registerCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    public void unregisterCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    private void dispatchAttributesChanged() {
+        synchronized (mCallbacks) {
+            for (Callback callback : mCallbacks) {
+                callback.onDeviceAttributesChanged();
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mDevice.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
+            return false;
+        }
+        return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
+    }
+
+    @Override
+    public int hashCode() {
+        return mDevice.getAddress().hashCode();
+    }
+
+    // This comparison uses non-final fields so the sort order may change
+    // when device attributes change (such as bonding state). Settings
+    // will completely refresh the device list when this happens.
+    public int compareTo(CachedBluetoothDevice another) {
+        // Connected above not connected
+        int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Paired above not paired
+        comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
+            (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Visible above not visible
+        comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Stronger signal above weaker signal
+        comparison = another.mRssi - mRssi;
+        if (comparison != 0) return comparison;
+
+        // Fallback on name
+        return mName.compareTo(another.mName);
+    }
+
+    public interface Callback {
+        void onDeviceAttributesChanged();
+    }
+
+    public int getPhonebookPermissionChoice() {
+        int permission = mDevice.getPhonebookAccessPermission();
+        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+            return ACCESS_ALLOWED;
+        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+            return ACCESS_REJECTED;
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    public void setPhonebookPermissionChoice(int permissionChoice) {
+        int permission = BluetoothDevice.ACCESS_UNKNOWN;
+        if (permissionChoice == ACCESS_ALLOWED) {
+            permission = BluetoothDevice.ACCESS_ALLOWED;
+        } else if (permissionChoice == ACCESS_REJECTED) {
+            permission = BluetoothDevice.ACCESS_REJECTED;
+        }
+        mDevice.setPhonebookAccessPermission(permission);
+    }
+
+    // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+    // app's shared preferences).
+    private void migratePhonebookPermissionChoice() {
+        SharedPreferences preferences = mContext.getSharedPreferences(
+                "bluetooth_phonebook_permission", Context.MODE_PRIVATE);
+        if (!preferences.contains(mDevice.getAddress())) {
+            return;
+        }
+
+        if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+            int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+            if (oldPermission == ACCESS_ALLOWED) {
+                mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+            } else if (oldPermission == ACCESS_REJECTED) {
+                mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+            }
+        }
+
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.remove(mDevice.getAddress());
+        editor.commit();
+    }
+
+    public int getMessagePermissionChoice() {
+        int permission = mDevice.getMessageAccessPermission();
+        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+            return ACCESS_ALLOWED;
+        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+            return ACCESS_REJECTED;
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    public void setMessagePermissionChoice(int permissionChoice) {
+        int permission = BluetoothDevice.ACCESS_UNKNOWN;
+        if (permissionChoice == ACCESS_ALLOWED) {
+            permission = BluetoothDevice.ACCESS_ALLOWED;
+        } else if (permissionChoice == ACCESS_REJECTED) {
+            permission = BluetoothDevice.ACCESS_REJECTED;
+        }
+        mDevice.setMessageAccessPermission(permission);
+    }
+
+    // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+    // app's shared preferences).
+    private void migrateMessagePermissionChoice() {
+        SharedPreferences preferences = mContext.getSharedPreferences(
+                "bluetooth_message_permission", Context.MODE_PRIVATE);
+        if (!preferences.contains(mDevice.getAddress())) {
+            return;
+        }
+
+        if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+            int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+            if (oldPermission == ACCESS_ALLOWED) {
+                mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+            } else if (oldPermission == ACCESS_REJECTED) {
+                mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+            }
+        }
+
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.remove(mDevice.getAddress());
+        editor.commit();
+    }
+
+    /**
+     * @return Whether this rejection should persist.
+     */
+    public boolean checkAndIncreaseMessageRejectionCount() {
+        if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) {
+            mMessageRejectionCount++;
+            saveMessageRejectionCount();
+        }
+        return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST;
+    }
+
+    private void fetchMessageRejectionCount() {
+        SharedPreferences preference = mContext.getSharedPreferences(
+                MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE);
+        mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0);
+    }
+
+    private void saveMessageRejectionCount() {
+        SharedPreferences.Editor editor = mContext.getSharedPreferences(
+                MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit();
+        if (mMessageRejectionCount == 0) {
+            editor.remove(mDevice.getAddress());
+        } else {
+            editor.putInt(mDevice.getAddress(), mMessageRejectionCount);
+        }
+        editor.commit();
+    }
+
+    private void processPhonebookAccess() {
+        if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return;
+
+        ParcelUuid[] uuids = mDevice.getUuids();
+        if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
+            // The pairing dialog now warns of phone-book access for paired devices.
+            // No separate prompt is displayed after pairing.
+            setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
new file mode 100755
index 0000000..65db95f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
+ */
+public final class CachedBluetoothDeviceManager {
+    private static final String TAG = "CachedBluetoothDeviceManager";
+    private static final boolean DEBUG = Utils.D;
+
+    private Context mContext;
+    private final List<CachedBluetoothDevice> mCachedDevices =
+            new ArrayList<CachedBluetoothDevice>();
+
+    CachedBluetoothDeviceManager(Context context) {
+        mContext = context;
+    }
+
+    public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
+        return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
+    }
+
+    public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
+        cachedDevice.setVisible(false);
+        return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
+    }
+
+    public void onDeviceNameUpdated(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.refreshName();
+        }
+    }
+
+    /**
+     * Search for existing {@link CachedBluetoothDevice} or return null
+     * if this device isn't in the cache. Use {@link #addDevice}
+     * to create and return a new {@link CachedBluetoothDevice} for
+     * a newly discovered {@link BluetoothDevice}.
+     *
+     * @param device the address of the Bluetooth device
+     * @return the cached device object for this device, or null if it has
+     *   not been previously seen
+     */
+    public CachedBluetoothDevice findDevice(BluetoothDevice device) {
+        for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+            if (cachedDevice.getDevice().equals(device)) {
+                return cachedDevice;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Create and return a new {@link CachedBluetoothDevice}. This assumes
+     * that {@link #findDevice} has already been called and returned null.
+     * @param device the address of the new Bluetooth device
+     * @return the newly created CachedBluetoothDevice object
+     */
+    public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
+            LocalBluetoothProfileManager profileManager,
+            BluetoothDevice device) {
+        CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
+            profileManager, device);
+        synchronized (mCachedDevices) {
+            mCachedDevices.add(newDevice);
+        }
+        return newDevice;
+    }
+
+    /**
+     * Attempts to get the name of a remote device, otherwise returns the address.
+     *
+     * @param device The remote device.
+     * @return The name, or if unavailable, the address.
+     */
+    public String getName(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            return cachedDevice.getName();
+        }
+
+        String name = device.getAliasName();
+        if (name != null) {
+            return name;
+        }
+
+        return device.getAddress();
+    }
+
+    public synchronized void clearNonBondedDevices() {
+        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                mCachedDevices.remove(i);
+            }
+        }
+    }
+
+    public synchronized void onScanningStateChanged(boolean started) {
+        if (!started) return;
+
+        // If starting a new scan, clear old visibility
+        // Iterate in reverse order since devices may be removed.
+        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            cachedDevice.setVisible(false);
+        }
+    }
+
+    public synchronized void onBtClassChanged(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.refreshBtClass();
+        }
+    }
+
+    public synchronized void onUuidChanged(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.onUuidChanged();
+        }
+    }
+
+    public synchronized void onBluetoothStateChanged(int bluetoothState) {
+        // When Bluetooth is turning off, we need to clear the non-bonded devices
+        // Otherwise, they end up showing up on the next BT enable
+        if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
+            for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+                CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+                if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                    cachedDevice.setVisible(false);
+                    mCachedDevices.remove(i);
+                } else {
+                    // For bonded devices, we need to clear the connection status so that
+                    // when BT is enabled next time, device connection status shall be retrieved
+                    // by making a binder call.
+                    cachedDevice.clearProfileConnectionState();
+                }
+            }
+        }
+    }
+    private void log(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
new file mode 100755
index 0000000..5529866
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HeadsetProfile handles Bluetooth HFP and Headset profiles.
+ */
+public final class HeadsetProfile implements LocalBluetoothProfile {
+    private static final String TAG = "HeadsetProfile";
+    private static boolean V = true;
+
+    private BluetoothHeadset mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final ParcelUuid[] UUIDS = {
+        BluetoothUuid.HSP,
+        BluetoothUuid.Handsfree,
+    };
+
+    static final String NAME = "HEADSET";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 0;
+
+    // These callbacks run on the main thread.
+    private final class HeadsetServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothHeadset) proxy;
+            // We just bound to the service, so refresh the UI for any connected HFP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "HeadsetProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(HeadsetProfile.this,
+                        BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mProfileManager.callServiceConnectedListeners();
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mProfileManager.callServiceDisconnectedListeners();
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    HeadsetProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(),
+                BluetoothProfile.HEADSET);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                Log.d(TAG,"Not disconnecting device = " + sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty()) {
+            for (BluetoothDevice dev : deviceList) {
+                if (dev.equals(device)) {
+                    if (V) Log.d(TAG,"Downgrade priority as user" +
+                                        "is disconnecting the headset");
+                    // Downgrade priority as user is disconnecting the headset.
+                    if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                        mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+                    }
+                    return mService.disconnect(device);
+                }
+            }
+        }
+        return false;
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty()){
+            for (BluetoothDevice dev : deviceList) {
+                if (dev.equals(device)) {
+                    return mService.getConnectionState(device);
+                }
+            }
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_headset;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_headset_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_headset_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_headset_hfp;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HID proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
new file mode 100755
index 0000000..a9e8db5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.List;
+
+/**
+ * HidProfile handles Bluetooth HID profile.
+ */
+public final class HidProfile implements LocalBluetoothProfile {
+    private static final String TAG = "HidProfile";
+    private static boolean V = true;
+
+    private BluetoothInputDevice mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final String NAME = "HID";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 3;
+
+    // These callbacks run on the main thread.
+    private final class InputDeviceServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothInputDevice) proxy;
+            // We just bound to the service, so refresh the UI for any connected HID devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "HidProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    HidProfile(Context context, LocalBluetoothAdapter adapter,
+        CachedBluetoothDeviceManager deviceManager,
+        LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        adapter.getProfileProxy(context, new InputDeviceServiceListener(),
+                BluetoothProfile.INPUT_DEVICE);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        // TODO: distinguish between keyboard and mouse?
+        return R.string.bluetooth_profile_hid;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_hid_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_hid_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        if (btClass == null) {
+            return R.drawable.ic_lockscreen_ime;
+        }
+        return getHidClassDrawable(btClass);
+    }
+
+    public static int getHidClassDrawable(BluetoothClass btClass) {
+        switch (btClass.getDeviceClass()) {
+            case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
+            case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
+                return R.drawable.ic_lockscreen_ime;
+            case BluetoothClass.Device.PERIPHERAL_POINTING:
+                return R.drawable.ic_bt_pointing_hid;
+            default:
+                return R.drawable.ic_bt_misc_hid;
+        }
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HID proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
new file mode 100644
index 0000000..0c1adec
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.Set;
+
+/**
+ * LocalBluetoothAdapter provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothAdapter}, specifically
+ * those related to state transitions of the adapter itself.
+ *
+ * <p>Connection and bonding state changes affecting specific devices
+ * are handled by {@link CachedBluetoothDeviceManager},
+ * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}.
+ */
+public final class LocalBluetoothAdapter {
+    private static final String TAG = "LocalBluetoothAdapter";
+
+    /** This class does not allow direct access to the BluetoothAdapter. */
+    private final BluetoothAdapter mAdapter;
+
+    private LocalBluetoothProfileManager mProfileManager;
+
+    private static LocalBluetoothAdapter sInstance;
+
+    private int mState = BluetoothAdapter.ERROR;
+
+    private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
+
+    private long mLastScan;
+
+    private LocalBluetoothAdapter(BluetoothAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    void setProfileManager(LocalBluetoothProfileManager manager) {
+        mProfileManager = manager;
+    }
+
+    /**
+     * Get the singleton instance of the LocalBluetoothAdapter. If this device
+     * doesn't support Bluetooth, then null will be returned. Callers must be
+     * prepared to handle a null return value.
+     * @return the LocalBluetoothAdapter object, or null if not supported
+     */
+    static synchronized LocalBluetoothAdapter getInstance() {
+        if (sInstance == null) {
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (adapter != null) {
+                sInstance = new LocalBluetoothAdapter(adapter);
+            }
+        }
+
+        return sInstance;
+    }
+
+    // Pass-through BluetoothAdapter methods that we can intercept if necessary
+
+    public void cancelDiscovery() {
+        mAdapter.cancelDiscovery();
+    }
+
+    public boolean enable() {
+        return mAdapter.enable();
+    }
+
+    public boolean disable() {
+        return mAdapter.disable();
+    }
+
+    void getProfileProxy(Context context,
+            BluetoothProfile.ServiceListener listener, int profile) {
+        mAdapter.getProfileProxy(context, listener, profile);
+    }
+
+    public Set<BluetoothDevice> getBondedDevices() {
+        return mAdapter.getBondedDevices();
+    }
+
+    public String getName() {
+        return mAdapter.getName();
+    }
+
+    public int getScanMode() {
+        return mAdapter.getScanMode();
+    }
+
+    public int getState() {
+        return mAdapter.getState();
+    }
+
+    public ParcelUuid[] getUuids() {
+        return mAdapter.getUuids();
+    }
+
+    public boolean isDiscovering() {
+        return mAdapter.isDiscovering();
+    }
+
+    public boolean isEnabled() {
+        return mAdapter.isEnabled();
+    }
+
+    public void setDiscoverableTimeout(int timeout) {
+        mAdapter.setDiscoverableTimeout(timeout);
+    }
+
+    public void setName(String name) {
+        mAdapter.setName(name);
+    }
+
+    public void setScanMode(int mode) {
+        mAdapter.setScanMode(mode);
+    }
+
+    public boolean setScanMode(int mode, int duration) {
+        return mAdapter.setScanMode(mode, duration);
+    }
+
+    public void startScanning(boolean force) {
+        // Only start if we're not already scanning
+        if (!mAdapter.isDiscovering()) {
+            if (!force) {
+                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
+                // unless forced
+                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
+                    return;
+                }
+
+                // If we are playing music, don't scan unless forced.
+                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
+                if (a2dp != null && a2dp.isA2dpPlaying()) {
+                    return;
+                }
+            }
+
+            if (mAdapter.startDiscovery()) {
+                mLastScan = System.currentTimeMillis();
+            }
+        }
+    }
+
+    public void stopScanning() {
+        if (mAdapter.isDiscovering()) {
+            mAdapter.cancelDiscovery();
+        }
+    }
+
+    public synchronized int getBluetoothState() {
+        // Always sync state, in case it changed while paused
+        syncBluetoothState();
+        return mState;
+    }
+
+    synchronized void setBluetoothStateInt(int state) {
+        mState = state;
+
+        if (state == BluetoothAdapter.STATE_ON) {
+            // if mProfileManager hasn't been constructed yet, it will
+            // get the adapter UUIDs in its constructor when it is.
+            if (mProfileManager != null) {
+                mProfileManager.setBluetoothStateOn();
+            }
+        }
+    }
+
+    // Returns true if the state changed; false otherwise.
+    boolean syncBluetoothState() {
+        int currentState = mAdapter.getState();
+        if (currentState != mState) {
+            setBluetoothStateInt(mAdapter.getState());
+            return true;
+        }
+        return false;
+    }
+
+    public void setBluetoothEnabled(boolean enabled) {
+        boolean success = enabled
+                ? mAdapter.enable()
+                : mAdapter.disable();
+
+        if (success) {
+            setBluetoothStateInt(enabled
+                ? BluetoothAdapter.STATE_TURNING_ON
+                : BluetoothAdapter.STATE_TURNING_OFF);
+        } else {
+            if (Utils.V) {
+                Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
+                        "success for enabled: " + enabled);
+            }
+
+            syncBluetoothState();
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000..4adc62e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * LocalBluetoothManager provides a simplified interface on top of a subset of
+ * the Bluetooth API. Note that {@link #getInstance} will return null
+ * if there is no Bluetooth adapter on this device, and callers must be
+ * prepared to handle this case.
+ */
+public final class LocalBluetoothManager {
+    private static final String TAG = "LocalBluetoothManager";
+
+    /** Singleton instance. */
+    private static LocalBluetoothManager sInstance;
+
+    private final Context mContext;
+
+    /** If a BT-related activity is in the foreground, this will be it. */
+    private Context mForegroundActivity;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+
+    private final CachedBluetoothDeviceManager mCachedDeviceManager;
+
+    /** The Bluetooth profile manager. */
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    /** The broadcast receiver event manager. */
+    private final BluetoothEventManager mEventManager;
+
+    public static synchronized LocalBluetoothManager getInstance(Context context,
+            BluetoothManagerCallback onInitCallback) {
+        if (sInstance == null) {
+            LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+            if (adapter == null) {
+                return null;
+            }
+            // This will be around as long as this process is
+            Context appContext = context.getApplicationContext();
+            sInstance = new LocalBluetoothManager(adapter, appContext);
+            if (onInitCallback != null) {
+                onInitCallback.onBluetoothManagerInitialized(appContext, sInstance);
+            }
+        }
+
+        return sInstance;
+    }
+
+    private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
+        mContext = context;
+        mLocalAdapter = adapter;
+
+        mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
+        mEventManager = new BluetoothEventManager(mLocalAdapter,
+                mCachedDeviceManager, context);
+        mProfileManager = new LocalBluetoothProfileManager(context,
+                mLocalAdapter, mCachedDeviceManager, mEventManager);
+    }
+
+    public LocalBluetoothAdapter getBluetoothAdapter() {
+        return mLocalAdapter;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public Context getForegroundActivity() {
+        return mForegroundActivity;
+    }
+
+    public boolean isForegroundActivity() {
+        return mForegroundActivity != null;
+    }
+
+    public synchronized void setForegroundActivity(Context context) {
+        if (context != null) {
+            Log.d(TAG, "setting foreground activity to non-null context");
+            mForegroundActivity = context;
+        } else {
+            if (mForegroundActivity != null) {
+                Log.d(TAG, "setting foreground activity to null");
+                mForegroundActivity = null;
+            }
+        }
+    }
+
+    public CachedBluetoothDeviceManager getCachedDeviceManager() {
+        return mCachedDeviceManager;
+    }
+
+    public BluetoothEventManager getEventManager() {
+        return mEventManager;
+    }
+
+    public LocalBluetoothProfileManager getProfileManager() {
+        return mProfileManager;
+    }
+
+    public interface BluetoothManagerCallback {
+        void onBluetoothManagerInitialized(Context appContext,
+                LocalBluetoothManager bluetoothManager);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
new file mode 100755
index 0000000..abcb989
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * LocalBluetoothProfile is an interface defining the basic
+ * functionality related to a Bluetooth profile.
+ */
+public interface LocalBluetoothProfile {
+
+    /**
+     * Returns true if the user can initiate a connection, false otherwise.
+     */
+    boolean isConnectable();
+
+    /**
+     * Returns true if the user can enable auto connection for this profile.
+     */
+    boolean isAutoConnectable();
+
+    boolean connect(BluetoothDevice device);
+
+    boolean disconnect(BluetoothDevice device);
+
+    int getConnectionStatus(BluetoothDevice device);
+
+    boolean isPreferred(BluetoothDevice device);
+
+    int getPreferred(BluetoothDevice device);
+
+    void setPreferred(BluetoothDevice device, boolean preferred);
+
+    boolean isProfileReady();
+
+    /** Display order for device profile settings. */
+    int getOrdinal();
+
+    /**
+     * Returns the string resource ID for the localized name for this profile.
+     * @param device the Bluetooth device (to distinguish between PAN roles)
+     */
+    int getNameResource(BluetoothDevice device);
+
+    /**
+     * Returns the string resource ID for the summary text for this profile
+     * for the specified device, e.g. "Use for media audio" or
+     * "Connected to media audio".
+     * @param device the device to query for profile connection status
+     * @return a string resource ID for the profile summary text
+     */
+    int getSummaryResourceForDevice(BluetoothDevice device);
+
+    int getDrawableResource(BluetoothClass btClass);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
new file mode 100644
index 0000000..b0a7b27
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+
+/**
+ * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
+ * objects for the available Bluetooth profiles.
+ */
+public final class LocalBluetoothProfileManager {
+    private static final String TAG = "LocalBluetoothProfileManager";
+    private static final boolean DEBUG = Utils.D;
+    /** Singleton instance. */
+    private static LocalBluetoothProfileManager sInstance;
+
+    /**
+     * An interface for notifying BluetoothHeadset IPC clients when they have
+     * been connected to the BluetoothHeadset service.
+     * Only used by com.android.settings.bluetooth.DockService.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when this proxy object has been
+         * connected to the BluetoothHeadset service. Clients must wait for
+         * this callback before making IPC calls on the BluetoothHeadset
+         * service.
+         */
+        void onServiceConnected();
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the BluetoothHeadset service. Clients must not
+         * make IPC calls on the BluetoothHeadset service after this callback.
+         * This callback will currently only occur if the application hosting
+         * the BluetoothHeadset service, but may be called more often in future.
+         */
+        void onServiceDisconnected();
+    }
+
+    private final Context mContext;
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final BluetoothEventManager mEventManager;
+
+    private A2dpProfile mA2dpProfile;
+    private HeadsetProfile mHeadsetProfile;
+    private MapProfile mMapProfile;
+    private final HidProfile mHidProfile;
+    private OppProfile mOppProfile;
+    private final PanProfile mPanProfile;
+    private final PbapServerProfile mPbapProfile;
+
+    /**
+     * Mapping from profile name, e.g. "HEADSET" to profile object.
+     */
+    private final Map<String, LocalBluetoothProfile>
+            mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
+
+    LocalBluetoothProfileManager(Context context,
+            LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            BluetoothEventManager eventManager) {
+        mContext = context;
+
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mEventManager = eventManager;
+        // pass this reference to adapter and event manager (circular dependency)
+        mLocalAdapter.setProfileManager(this);
+        mEventManager.setProfileManager(this);
+
+        ParcelUuid[] uuids = adapter.getUuids();
+
+        // uuids may be null if Bluetooth is turned off
+        if (uuids != null) {
+            updateLocalProfiles(uuids);
+        }
+
+        // Always add HID and PAN profiles
+        mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
+        addProfile(mHidProfile, HidProfile.NAME,
+                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+
+        mPanProfile = new PanProfile(context);
+        addPanProfile(mPanProfile, PanProfile.NAME,
+                BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
+
+        if(DEBUG) Log.d(TAG, "Adding local MAP profile");
+        mMapProfile = new MapProfile(mContext, mLocalAdapter,
+                mDeviceManager, this);
+        addProfile(mMapProfile, MapProfile.NAME,
+                BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
+
+       //Create PBAP server profile, but do not add it to list of profiles
+       // as we do not need to monitor the profile as part of profile list
+        mPbapProfile = new PbapServerProfile(context);
+
+        if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
+    }
+
+    /**
+     * Initialize or update the local profile objects. If a UUID was previously
+     * present but has been removed, we print a warning but don't remove the
+     * profile object as it might be referenced elsewhere, or the UUID might
+     * come back and we don't want multiple copies of the profile objects.
+     * @param uuids
+     */
+    void updateLocalProfiles(ParcelUuid[] uuids) {
+        // A2DP
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
+            if (mA2dpProfile == null) {
+                if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
+                mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
+                addProfile(mA2dpProfile, A2dpProfile.NAME,
+                        BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } else if (mA2dpProfile != null) {
+            Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
+        }
+
+        // Headset / Handsfree
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
+            BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
+            if (mHeadsetProfile == null) {
+                if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
+                mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
+                        mDeviceManager, this);
+                addProfile(mHeadsetProfile, HeadsetProfile.NAME,
+                        BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } else if (mHeadsetProfile != null) {
+            Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
+        }
+
+        // OPP
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+            if (mOppProfile == null) {
+                if(DEBUG) Log.d(TAG, "Adding local OPP profile");
+                mOppProfile = new OppProfile();
+                // Note: no event handler for OPP, only name map.
+                mProfileNameMap.put(OppProfile.NAME, mOppProfile);
+            }
+        } else if (mOppProfile != null) {
+            Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
+        }
+        mEventManager.registerProfileIntentReceiver();
+
+        // There is no local SDP record for HID and Settings app doesn't control PBAP
+    }
+
+    private final Collection<ServiceListener> mServiceListeners =
+            new ArrayList<ServiceListener>();
+
+    private void addProfile(LocalBluetoothProfile profile,
+            String profileName, String stateChangedAction) {
+        mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
+        mProfileNameMap.put(profileName, profile);
+    }
+
+    private void addPanProfile(LocalBluetoothProfile profile,
+            String profileName, String stateChangedAction) {
+        mEventManager.addProfileHandler(stateChangedAction,
+                new PanStateChangedHandler(profile));
+        mProfileNameMap.put(profileName, profile);
+    }
+
+    public LocalBluetoothProfile getProfileByName(String name) {
+        return mProfileNameMap.get(name);
+    }
+
+    // Called from LocalBluetoothAdapter when state changes to ON
+    void setBluetoothStateOn() {
+        ParcelUuid[] uuids = mLocalAdapter.getUuids();
+        if (uuids != null) {
+            updateLocalProfiles(uuids);
+        }
+        mEventManager.readPairedDevices();
+    }
+
+    /**
+     * Generic handler for connection state change events for the specified profile.
+     */
+    private class StateChangedHandler implements BluetoothEventManager.Handler {
+        final LocalBluetoothProfile mProfile;
+
+        StateChangedHandler(LocalBluetoothProfile profile) {
+            mProfile = profile;
+        }
+
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "StateChangedHandler found new device: " + device);
+                cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
+                        LocalBluetoothProfileManager.this, device);
+            }
+            int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+            int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
+            if (newState == BluetoothProfile.STATE_DISCONNECTED &&
+                    oldState == BluetoothProfile.STATE_CONNECTING) {
+                Log.i(TAG, "Failed to connect " + mProfile + " device");
+            }
+
+            cachedDevice.onProfileStateChanged(mProfile, newState);
+            cachedDevice.refresh();
+        }
+    }
+
+    /** State change handler for NAP and PANU profiles. */
+    private class PanStateChangedHandler extends StateChangedHandler {
+
+        PanStateChangedHandler(LocalBluetoothProfile profile) {
+            super(profile);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            PanProfile panProfile = (PanProfile) mProfile;
+            int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
+            panProfile.setLocalRole(device, role);
+            super.onReceive(context, intent, device);
+        }
+    }
+
+    // called from DockService
+    public void addServiceListener(ServiceListener l) {
+        mServiceListeners.add(l);
+    }
+
+    // called from DockService
+    public void removeServiceListener(ServiceListener l) {
+        mServiceListeners.remove(l);
+    }
+
+    // not synchronized: use only from UI thread! (TODO: verify)
+    void callServiceConnectedListeners() {
+        for (ServiceListener l : mServiceListeners) {
+            l.onServiceConnected();
+        }
+    }
+
+    // not synchronized: use only from UI thread! (TODO: verify)
+    void callServiceDisconnectedListeners() {
+        for (ServiceListener listener : mServiceListeners) {
+            listener.onServiceDisconnected();
+        }
+    }
+
+    // This is called by DockService, so check Headset and A2DP.
+    public synchronized boolean isManagerReady() {
+        // Getting just the headset profile is fine for now. Will need to deal with A2DP
+        // and others if they aren't always in a ready state.
+        LocalBluetoothProfile profile = mHeadsetProfile;
+        if (profile != null) {
+            return profile.isProfileReady();
+        }
+        profile = mA2dpProfile;
+        if (profile != null) {
+            return profile.isProfileReady();
+        }
+        return false;
+    }
+
+    public A2dpProfile getA2dpProfile() {
+        return mA2dpProfile;
+    }
+
+    public HeadsetProfile getHeadsetProfile() {
+        return mHeadsetProfile;
+    }
+
+    public PbapServerProfile getPbapProfile(){
+        return mPbapProfile;
+    }
+
+    public MapProfile getMapProfile(){
+        return mMapProfile;
+    }
+
+    /**
+     * Fill in a list of LocalBluetoothProfile objects that are supported by
+     * the local device and the remote device.
+     *
+     * @param uuids of the remote device
+     * @param localUuids UUIDs of the local device
+     * @param profiles The list of profiles to fill
+     * @param removedProfiles list of profiles that were removed
+     */
+    synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
+            Collection<LocalBluetoothProfile> profiles,
+            Collection<LocalBluetoothProfile> removedProfiles,
+            boolean isPanNapConnected, BluetoothDevice device) {
+        // Copy previous profile list into removedProfiles
+        removedProfiles.clear();
+        removedProfiles.addAll(profiles);
+        profiles.clear();
+
+        if (uuids == null) {
+            return;
+        }
+
+        if (mHeadsetProfile != null) {
+            if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
+                    BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
+                    (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
+                            BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
+                profiles.add(mHeadsetProfile);
+                removedProfiles.remove(mHeadsetProfile);
+            }
+        }
+
+        if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
+            mA2dpProfile != null) {
+            profiles.add(mA2dpProfile);
+            removedProfiles.remove(mA2dpProfile);
+        }
+
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
+            mOppProfile != null) {
+            profiles.add(mOppProfile);
+            removedProfiles.remove(mOppProfile);
+        }
+
+        if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
+             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
+            mHidProfile != null) {
+            profiles.add(mHidProfile);
+            removedProfiles.remove(mHidProfile);
+        }
+
+        if(isPanNapConnected)
+            if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
+        if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
+            mPanProfile != null) || isPanNapConnected) {
+            profiles.add(mPanProfile);
+            removedProfiles.remove(mPanProfile);
+        }
+
+        if ((mMapProfile != null) &&
+            (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
+            profiles.add(mMapProfile);
+            removedProfiles.remove(mMapProfile);
+            mMapProfile.setPreferred(device, true);
+        }
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
new file mode 100644
index 0000000..e6a152f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MapProfile handles Bluetooth MAP profile.
+ */
+public final class MapProfile implements LocalBluetoothProfile {
+    private static final String TAG = "MapProfile";
+    private static boolean V = true;
+
+    private BluetoothMap mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final ParcelUuid[] UUIDS = {
+        BluetoothUuid.MAP,
+        BluetoothUuid.MNS,
+        BluetoothUuid.MAS,
+    };
+
+    static final String NAME = "MAP";
+
+    // Order of this profile in device profiles list
+
+    // These callbacks run on the main thread.
+    private final class MapServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothMap) proxy;
+            // We just bound to the service, so refresh the UI for any connected MAP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "MapProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(MapProfile.this,
+                        BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mProfileManager.callServiceConnectedListeners();
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mProfileManager.callServiceDisconnectedListeners();
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady);
+        return mIsProfileReady;
+    }
+
+    MapProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new MapServiceListener(),
+                BluetoothProfile.MAP);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if(V)Log.d(TAG,"connect() - should not get called");
+        return false; // MAP never connects out
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
+            if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+            return mService.disconnect(device);
+        } else {
+            return false;
+        }
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device));
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return BluetoothProfile.MAP;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_map;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_map_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_map_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up MAP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
new file mode 100755
index 0000000..31e675c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import com.android.settingslib.R;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+
+/**
+ * OppProfile handles Bluetooth OPP.
+ */
+final class OppProfile implements LocalBluetoothProfile {
+
+    static final String NAME = "OPP";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 2;
+
+    public boolean isConnectable() {
+        return false;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        return false;
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        return false;
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return false;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    }
+
+    public boolean isProfileReady() {
+        return true;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_opp;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return 0;   // OPP profile not displayed in UI
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return 0;   // no icon for OPP
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
new file mode 100755
index 0000000..3af89e6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * PanProfile handles Bluetooth PAN profile (NAP and PANU).
+ */
+final class PanProfile implements LocalBluetoothProfile {
+    private static final String TAG = "PanProfile";
+    private static boolean V = true;
+
+    private BluetoothPan mService;
+    private boolean mIsProfileReady;
+
+    // Tethering direction for each device
+    private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap =
+            new HashMap<BluetoothDevice, Integer>();
+
+    static final String NAME = "PAN";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 4;
+
+    // These callbacks run on the main thread.
+    private final class PanServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothPan) proxy;
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    PanProfile(Context context) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        adapter.getProfileProxy(context, new PanServiceListener(),
+                BluetoothProfile.PAN);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        // return current connection status so profile checkbox is set correctly
+        return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return -1;
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        // ignore: isPreferred is always true for PAN
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        if (isLocalRoleNap(device)) {
+            return R.string.bluetooth_profile_pan_nap;
+        } else {
+            return R.string.bluetooth_profile_pan;
+        }
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_pan_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                if (isLocalRoleNap(device)) {
+                    return R.string.bluetooth_pan_nap_profile_summary_connected;
+                } else {
+                    return R.string.bluetooth_pan_user_profile_summary_connected;
+                }
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_network_pan;
+    }
+
+    // Tethering direction determines UI strings.
+    void setLocalRole(BluetoothDevice device, int role) {
+        mDeviceRoleMap.put(device, role);
+    }
+
+    boolean isLocalRoleNap(BluetoothDevice device) {
+        if (mDeviceRoleMap.containsKey(device)) {
+            return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE;
+        } else {
+            return false;
+        }
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up PAN proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
new file mode 100755
index 0000000..a552b24a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * PBAPServer Profile
+ */
+public final class PbapServerProfile implements LocalBluetoothProfile {
+    private static final String TAG = "PbapServerProfile";
+    private static boolean V = true;
+
+    private BluetoothPbap mService;
+    private boolean mIsProfileReady;
+
+    static final String NAME = "PBAP Server";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 6;
+
+    // The UUIDs indicate that remote device might access pbap server
+    static final ParcelUuid[] PBAB_CLIENT_UUIDS = {
+        BluetoothUuid.HSP,
+        BluetoothUuid.Handsfree,
+        BluetoothUuid.PBAP_PCE
+    };
+
+    // These callbacks run on the main thread.
+    private final class PbapServiceListener
+            implements BluetoothPbap.ServiceListener {
+
+        public void onServiceConnected(BluetoothPbap proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothPbap) proxy;
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected() {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    PbapServerProfile(Context context) {
+        BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        /*Can't connect from server */
+        return false;
+
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.disconnect();
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        if (mService.isConnected(device))
+            return BluetoothProfile.STATE_CONNECTED;
+        else
+            return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return false;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return -1;
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        // ignore: isPreferred is always true for PBAP
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_pbap;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return R.string.bluetooth_profile_pbap_summary;
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                mService.close();
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up PBAP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
new file mode 100644
index 0000000..c919426
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
@@ -0,0 +1,43 @@
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+
+public class Utils {
+    public static final boolean V = false; // verbose logging
+    public static final boolean D = true;  // regular logging
+
+    private static ErrorListener sErrorListener;
+
+    public static int getConnectionStateSummary(int connectionState) {
+        switch (connectionState) {
+        case BluetoothProfile.STATE_CONNECTED:
+            return R.string.bluetooth_connected;
+        case BluetoothProfile.STATE_CONNECTING:
+            return R.string.bluetooth_connecting;
+        case BluetoothProfile.STATE_DISCONNECTED:
+            return R.string.bluetooth_disconnected;
+        case BluetoothProfile.STATE_DISCONNECTING:
+            return R.string.bluetooth_disconnecting;
+        default:
+            return 0;
+        }
+    }
+
+    static void showError(Context context, String name, int messageResId) {
+        if (sErrorListener != null) {
+            sErrorListener.onShowError(context, name, messageResId);
+        }
+    }
+
+    public static void setErrorListener(ErrorListener listener) {
+        sErrorListener = listener;
+    }
+
+    public interface ErrorListener {
+        void onShowError(Context context, String name, int messageResId);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 25bab17..c23f45d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -114,7 +114,9 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        mDialog.dismiss();
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
     }
 
     @Override
@@ -128,7 +130,9 @@
             Log.e(TAG, "Error granting projection permission", e);
             setResult(RESULT_CANCELED);
         } finally {
-            mDialog.dismiss();
+            if (mDialog != null) {
+                mDialog.dismiss();
+            }
             finish();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 0863c86..7ca91a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -22,7 +22,6 @@
     boolean isHotspotEnabled();
     boolean isHotspotSupported();
     void setHotspotEnabled(boolean enabled);
-    boolean isProvisioningNeeded();
 
     public interface Callback {
         void onHotspotChanged(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 5eff5a6..4bfd528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -16,45 +16,38 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
-import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 
+import com.android.settingslib.TetherUtil;
+
 import java.util.ArrayList;
 
 public class HotspotControllerImpl implements HotspotController {
 
     private static final String TAG = "HotspotController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    // Keep these in sync with Settings TetherService.java
-    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
-    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
-    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
-    public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
-    // Keep this in sync with Settings TetherSettings.java
-    public static final int WIFI_TETHERING = 0;
+    private static final Intent TETHER_SERVICE_INTENT = new Intent()
+            .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI)
+            .putExtra(TetherUtil.EXTRA_SET_ALARM, true)
+            .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true)
+            .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true)
+            .setComponent(TetherUtil.TETHER_SERVICE);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final Receiver mReceiver = new Receiver();
     private final Context mContext;
     private final WifiManager mWifiManager;
-    private final ConnectivityManager mConnectivityManager;
 
     public HotspotControllerImpl(Context context) {
         mContext = context;
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-        mConnectivityManager = (ConnectivityManager)
-                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
     public void addCallback(Callback callback) {
@@ -78,54 +71,17 @@
 
     @Override
     public boolean isHotspotSupported() {
-        final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER;
-        return !isSecondaryUser && mConnectivityManager.isTetheringSupported();
-    }
-
-    @Override
-    public boolean isProvisioningNeeded() {
-        // Keep in sync with other usage of config_mobile_hotspot_provision_app.
-        // TetherSettings#isProvisioningNeeded and
-        // ConnectivityManager#enforceTetherChangePermission
-        String[] provisionApp = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_mobile_hotspot_provision_app);
-        if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
-                || provisionApp == null) {
-            return false;
-        }
-        return (provisionApp.length == 2);
+        return TetherUtil.isTetheringSupported(mContext);
     }
 
     @Override
     public void setHotspotEnabled(boolean enabled) {
         final ContentResolver cr = mContext.getContentResolver();
         // Call provisioning app which is called when enabling Tethering from Settings
-        if (enabled) {
-            if (isProvisioningNeeded()) {
-                String tetherEnable = mContext.getResources().getString(
-                        com.android.internal.R.string.config_wifi_tether_enable);
-                Intent intent = new Intent();
-                intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING);
-                intent.putExtra(EXTRA_SET_ALARM, true);
-                intent.putExtra(EXTRA_RUN_PROVISION, true);
-                intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true);
-                intent.setComponent(ComponentName.unflattenFromString(tetherEnable));
-                mContext.startServiceAsUser(intent, UserHandle.CURRENT);
-            } else {
-                int wifiState = mWifiManager.getWifiState();
-                if ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
-                        (wifiState == WifiManager.WIFI_STATE_ENABLED)) {
-                    mWifiManager.setWifiEnabled(false);
-                    Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
-                }
-                mWifiManager.setWifiApEnabled(null, true);
-            }
+        if (enabled && TetherUtil.isProvisioningNeeded(mContext)) {
+            mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT);
         } else {
-            mWifiManager.setWifiApEnabled(null, false);
-            if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) {
-                mWifiManager.setWifiEnabled(true);
-                Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
-            }
+            TetherUtil.setWifiTethering(enabled, mContext);
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 4d7ebed..acf4d39 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2778,7 +2778,22 @@
 
         @Override
         public void operationComplete() {
-            // Okay, the agent successfully reported back to us!
+            // The agent reported back to us!
+
+            if (mBackupData == null) {
+                // This callback was racing with our timeout, so we've cleaned up the
+                // agent state already and are on to the next thing.  We have nothing
+                // further to do here: agent state having been cleared means that we've
+                // initiated the appropriate next operation.
+                final String pkg = (mCurrentPackage != null)
+                        ? mCurrentPackage.packageName : "[none]";
+                if (DEBUG) {
+                    Slog.i(TAG, "Callback after agent teardown: " + pkg);
+                }
+                addBackupTrace("late opComplete; curPkg = " + pkg);
+                return;
+            }
+
             final String pkgName = mCurrentPackage.packageName;
             final long filepos = mBackupDataName.length();
             FileDescriptor fd = mBackupData.getFileDescriptor();
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8bd7132..2e84fbe 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -318,6 +318,8 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
         BackupManagerService svc = mService;
         if (svc != null) {
             svc.dump(fd, pw, args);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6229778..008d718 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2398,8 +2398,7 @@
             } else {
                 finishRunningVoiceLocked();
             }
-            mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity");
-            if (r != null) {
+            if (r != null && mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) {
                 mWindowManager.setFocusedApp(r.appToken, true);
             }
             applyUpdateLockStateLocked(r);
@@ -2423,6 +2422,7 @@
                 ActivityRecord r = stack.topRunningActivityLocked(null);
                 if (r != null) {
                     setFocusedActivityLocked(r, "setFocusedStack");
+                    mStackSupervisor.resumeTopActivitiesLocked(stack, null, null);
                 }
             }
         }
@@ -7909,6 +7909,25 @@
     }
 
     @Override
+    public void resizeTask(int taskId, Rect bounds) {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "resizeTask()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, true);
+                if (task == null) {
+                    Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
+                    return;
+                }
+                mStackSupervisor.resizeTaskLocked(task, bounds);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public Bitmap getTaskDescriptionIcon(String filename) {
         if (!FileUtils.isValidExtFilename(filename)
                 || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 83a7b68..c073df6 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -152,25 +152,25 @@
      * The back history of all previous (and possibly still
      * running) activities.  It contains #TaskRecord objects.
      */
-    private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>();
+    private ArrayList<TaskRecord> mTaskHistory = new ArrayList<>();
 
     /**
      * Used for validating app tokens with window manager.
      */
-    final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>();
+    final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<>();
 
     /**
      * List of running activities, sorted by recent usage.
      * The first entry in the list is the least recently used.
      * It contains HistoryRecord objects.
      */
-    final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();
+    final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>();
 
     /**
      * Animations that for the current transition have requested not to
      * be considered for the transition animation.
      */
-    final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>();
+    final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>();
 
     /**
      * When we are in the process of pausing an activity, before starting the
@@ -346,6 +346,10 @@
         return count;
     }
 
+    int numTasks() {
+        return mTaskHistory.size();
+    }
+
     ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
             RecentTasks recentTasks) {
         mActivityContainer = activityContainer;
@@ -492,11 +496,19 @@
 
     final void moveToFront(String reason) {
         if (isAttached()) {
-            if (isOnHomeDisplay()) {
-                mStackSupervisor.moveHomeStack(isHomeStack(), reason);
+            final boolean homeStack = isHomeStack()
+                    || (mActivityContainer.mParentActivity != null
+                        && mActivityContainer.mParentActivity.isHomeActivity());
+
+            if (!homeStack) {
+                // Need to move this stack to the front before calling
+                // {@link ActivityStackSupervisor#moveHomeStack} below.
+                mStacks.remove(this);
+                mStacks.add(this);
             }
-            mStacks.remove(this);
-            mStacks.add(this);
+            if (isOnHomeDisplay()) {
+                mStackSupervisor.moveHomeStack(homeStack, reason);
+            }
             final TaskRecord task = topTask();
             if (task != null) {
                 mWindowManager.moveTaskToTop(task.taskId);
@@ -1154,6 +1166,23 @@
         return null;
     }
 
+    private ActivityStack getNextVisibleStackLocked() {
+        ArrayList<ActivityStack> stacks = mStacks;
+        final ActivityRecord parent = mActivityContainer.mParentActivity;
+        if (parent != null) {
+            stacks = parent.task.stack.mStacks;
+        }
+        if (stacks != null) {
+            for (int i = stacks.size() - 1; i >= 0; --i) {
+                ActivityStack stack = stacks.get(i);
+                if (stack != this && stack.isStackVisibleLocked()) {
+                    return stack;
+                }
+            }
+        }
+        return null;
+    }
+
     // Checks if any of the stacks above this one has a fullscreen activity behind it.
     // If so, this stack is hidden, otherwise it is visible.
     private boolean isStackVisibleLocked() {
@@ -1482,7 +1511,7 @@
         return result;
     }
 
-    final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
+    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
         if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
 
         if (!mService.mBooting && !mService.mBooted) {
@@ -1510,8 +1539,17 @@
 
         final TaskRecord prevTask = prev != null ? prev.task : null;
         if (next == null) {
-            // There are no more activities!  Let's just start up the
-            // Launcher...
+            // There are no more activities!
+            final String reason = "noMoreActivities";
+            if (!mFullscreen) {
+                // Try to move focus to the next visible stack with a running activity if this
+                // stack is not covering the entire screen.
+                final ActivityStack stack = getNextVisibleStackLocked();
+                if (adjustFocusToNextVisibleStackLocked(stack, reason)) {
+                    return mStackSupervisor.resumeTopActivitiesLocked(stack, prev, null);
+                }
+            }
+            // Let's just start up the Launcher...
             ActivityOptions.abort(options);
             if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
             if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
@@ -1519,7 +1557,7 @@
             final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
                     HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
             return isOnHomeDisplay() &&
-                    mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, "noMoreActivities");
+                    mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason);
         }
 
         next.delayedResume = false;
@@ -2516,20 +2554,44 @@
     private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
         if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
             ActivityRecord next = topRunningActivityLocked(null);
+            final String myReason = reason + " adjustFocus";
             if (next != r) {
                 final TaskRecord task = r.task;
                 if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
-                    mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(),
-                            reason + " adjustFocus");
+                    // For non-fullscreen stack, we want to move the focus to the next visible
+                    // stack to prevent the home screen from moving to the top and obscuring
+                    // other visible stacks.
+                    if (!mFullscreen
+                            && adjustFocusToNextVisibleStackLocked(null, myReason)) {
+                        return;
+                    }
+                    // Move the home stack to the top if this stack is fullscreen or there is no
+                    // other visible stack.
+                    mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), myReason);
                 }
             }
-            ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
+
+            final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
             if (top != null) {
-                mService.setFocusedActivityLocked(top, reason + " adjustTopFocus");
+                mService.setFocusedActivityLocked(top, myReason);
             }
         }
     }
 
+    private boolean adjustFocusToNextVisibleStackLocked(ActivityStack inStack, String reason) {
+        final ActivityStack stack = (inStack != null) ? inStack : getNextVisibleStackLocked();
+        final String myReason = reason + " adjustFocusToNextVisibleStack";
+        if (stack == null) {
+            return false;
+        }
+        final ActivityRecord top = stack.topRunningActivityLocked(null);
+        if (top == null) {
+            return false;
+        }
+        mService.setFocusedActivityLocked(top, myReason);
+        return true;
+    }
+
     final void stopActivityLocked(ActivityRecord r) {
         if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r);
         if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
@@ -2999,6 +3061,8 @@
      * representation) and cleaning things up as a result of its hosting
      * processing going away, in which case there is no remaining client-side
      * state to destroy so only the cleanup here is needed.
+     *
+     * Note: Call before #removeActivityFromHistoryLocked.
      */
     final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices,
             boolean setState) {
@@ -3410,7 +3474,7 @@
                 if (DEBUG_CLEANUP) Slog.v(
                     TAG, "Record #" + i + " " + r + ": app=" + r.app);
                 if (r.app == app) {
-                    boolean remove;
+                    final boolean remove;
                     if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
                         // Don't currently have state for the activity, or
                         // it is finishing -- always remove it.
@@ -3444,8 +3508,6 @@
                                 mService.updateUsageStats(r, false);
                             }
                         }
-                        removeActivityFromHistoryLocked(r, "appDied");
-
                     } else {
                         // We have the current state for this activity, so
                         // it can be restarted later when needed.
@@ -3464,8 +3526,10 @@
                             r.icicle = null;
                         }
                     }
-
                     cleanUpActivityLocked(r, true, true);
+                    if (remove) {
+                        removeActivityFromHistoryLocked(r, "appDied");
+                    }
                 }
             }
         }
@@ -3599,8 +3663,7 @@
             }
         }
 
-        if (DEBUG_TRANSITION) Slog.v(TAG,
-                "Prepare to back transition: task=" + taskId);
+        if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to back transition: task=" + taskId);
 
         boolean prevIsHome = false;
         if (tr.isOverHomeStack()) {
@@ -4126,8 +4189,14 @@
     }
 
     void removeTask(TaskRecord task, String reason) {
+        removeTask(task, reason, true);
+    }
+
+    void removeTask(TaskRecord task, String reason, boolean removeFromWindowManager) {
         mStackSupervisor.endLockTaskModeIfTaskEnding(task);
-        mWindowManager.removeTask(task.taskId);
+        if (removeFromWindowManager) {
+            mWindowManager.removeTask(task.taskId);
+        }
         final ActivityRecord r = mResumedActivity;
         if (r != null && r.task == task) {
             mResumedActivity = null;
@@ -4161,10 +4230,13 @@
         }
 
         if (mTaskHistory.isEmpty()) {
-            if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this);
+            if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack=" + this);
             final boolean notHomeStack = !isHomeStack();
             if (isOnHomeDisplay()) {
-                mStackSupervisor.moveHomeStack(notHomeStack, reason + " leftTaskHistoryEmpty");
+                String myReason = reason + " leftTaskHistoryEmpty";
+                if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) {
+                    mStackSupervisor.moveHomeStack(notHomeStack, myReason);
+                }
             }
             if (mStacks != null) {
                 mStacks.remove(this);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9fe3c48..f6ef295 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -386,8 +386,8 @@
         return mLastFocusedStack;
     }
 
-    // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the
-    // top of all visible stacks.
+    /** Top of all visible stacks. Use {@link ActivityStack#isStackVisibleLocked} to determine if a
+     * specific stack is visible or not. */
     boolean isFrontStack(ActivityStack stack) {
         final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
         if (parent != null) {
@@ -535,7 +535,7 @@
     }
 
     ActivityRecord resumedAppLocked() {
-        ActivityStack stack = getFocusedStack();
+        ActivityStack stack = mFocusedStack;
         if (stack == null) {
             return null;
         }
@@ -739,7 +739,7 @@
     }
 
     ActivityRecord topRunningActivityLocked() {
-        final ActivityStack focusedStack = getFocusedStack();
+        final ActivityStack focusedStack = mFocusedStack;
         ActivityRecord r = focusedStack.topRunningActivityLocked(null);
         if (r != null) {
             return r;
@@ -885,7 +885,7 @@
 
             final ActivityStack stack;
             if (container == null || container.mStack.isOnHomeDisplay()) {
-                stack = getFocusedStack();
+                stack = mFocusedStack;
             } else {
                 stack = container.mStack;
             }
@@ -1502,7 +1502,7 @@
             outActivity[0] = r;
         }
 
-        final ActivityStack stack = getFocusedStack();
+        final ActivityStack stack = mFocusedStack;
         if (voiceSession == null && (stack.mResumedActivity == null
                 || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
             if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
@@ -1541,25 +1541,27 @@
         return err;
     }
 
-    ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) {
+    ActivityStack computeStackFocus(ActivityRecord r, boolean newTask) {
         final TaskRecord task = r.task;
 
         // On leanback only devices we should keep all activities in the same stack.
         if (!mLeanbackOnlyDevice &&
                 (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
+
+            ActivityStack stack;
+
             if (task != null) {
-                final ActivityStack taskStack = task.stack;
-                if (taskStack.isOnHomeDisplay()) {
-                    if (mFocusedStack != taskStack) {
-                        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Setting " +
+                stack = task.stack;
+                if (stack.isOnHomeDisplay()) {
+                    if (mFocusedStack != stack) {
+                        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "computeStackFocus: Setting " +
                                 "focused stack to r=" + r + " task=" + task);
-                        mFocusedStack = taskStack;
                     } else {
                         if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
-                            "adjustStackFocus: Focused stack already=" + mFocusedStack);
+                            "computeStackFocus: Focused stack already=" + mFocusedStack);
                     }
                 }
-                return taskStack;
+                return stack;
             }
 
             final ActivityContainer container = r.mInitialActivityContainer;
@@ -1572,43 +1574,41 @@
             if (mFocusedStack != mHomeStack && (!newTask ||
                     mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
                 if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
-                        "adjustStackFocus: Have a focused stack=" + mFocusedStack);
+                        "computeStackFocus: Have a focused stack=" + mFocusedStack);
                 return mFocusedStack;
             }
 
             final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
             for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = homeDisplayStacks.get(stackNdx);
+                stack = homeDisplayStacks.get(stackNdx);
                 if (!stack.isHomeStack()) {
                     if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
-                            "adjustStackFocus: Setting focused stack=" + stack);
-                    mFocusedStack = stack;
-                    return mFocusedStack;
+                            "computeStackFocus: Setting focused stack=" + stack);
+                    return stack;
                 }
             }
 
             // Need to create an app stack for this user.
-            mFocusedStack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
-            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r +
-                    " stackId=" + mFocusedStack.mStackId);
-            return mFocusedStack;
+            stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
+            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "computeStackFocus: New stack r=" + r +
+                    " stackId=" + stack.mStackId);
+            return stack;
         }
         return mHomeStack;
     }
 
-    void setFocusedStack(ActivityRecord r, String reason) {
-        if (r != null) {
-            final TaskRecord task = r.task;
-            boolean isHomeActivity = !r.isApplicationActivity();
-            if (!isHomeActivity && task != null) {
-                isHomeActivity = !task.isApplicationTask();
-            }
-            if (!isHomeActivity && task != null) {
-                final ActivityRecord parent = task.stack.mActivityContainer.mParentActivity;
-                isHomeActivity = parent != null && parent.isHomeActivity();
-            }
-            moveHomeStack(isHomeActivity, reason);
+    boolean setFocusedStack(ActivityRecord r, String reason) {
+        if (r == null) {
+            // Not sure what you are trying to do, but it is not going to work...
+            return false;
         }
+        final TaskRecord task = r.task;
+        if (task == null || task.stack == null) {
+            Slog.w(TAG, "Can't set focus stack for r=" + r + " task=" + task);
+            return false;
+        }
+        task.stack.moveToFront(reason);
+        return true;
     }
 
     final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,
@@ -1706,7 +1706,7 @@
         if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
             ActivityRecord checkedCaller = sourceRecord;
             if (checkedCaller == null) {
-                checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop);
+                checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop);
             }
             if (!checkedCaller.realActivity.equals(r.realActivity)) {
                 // Caller is not the same as launcher, so always needed.
@@ -2030,7 +2030,7 @@
             // If the activity being launched is the same as the one currently
             // at the top, then we need to check if it should only be launched
             // once.
-            ActivityStack topStack = getFocusedStack();
+            ActivityStack topStack = mFocusedStack;
             ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
             if (top != null && r.resultTo == null) {
                 if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
@@ -2082,10 +2082,9 @@
                 return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
             }
             newTask = true;
-            targetStack = adjustStackFocus(r, newTask);
-            if (!launchTaskBehind) {
-                targetStack.moveToFront("startingNewTask");
-            }
+            targetStack = computeStackFocus(r, newTask);
+            targetStack.moveToFront("startingNewTask");
+
             if (reuseTask == null) {
                 r.setTask(targetStack.createTaskRecord(getNextTaskId(),
                         newTaskInfo != null ? newTaskInfo : r.info,
@@ -2206,7 +2205,7 @@
             // This not being started from an existing activity, and not part
             // of a new task...  just put it in the top task, though these days
             // this case should never happen.
-            targetStack = adjustStackFocus(r, newTask);
+            targetStack = computeStackFocus(r, newTask);
             targetStack.moveToFront("addingToTopTask");
             ActivityRecord prev = targetStack.topActivity();
             r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
@@ -2482,13 +2481,14 @@
     boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,
             Bundle targetOptions) {
         if (targetStack == null) {
-            targetStack = getFocusedStack();
+            targetStack = mFocusedStack;
         }
         // Do targetStack first.
         boolean result = false;
         if (isFrontStack(targetStack)) {
             result = targetStack.resumeTopActivityLocked(target, targetOptions);
         }
+
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -2640,6 +2640,48 @@
         }
     }
 
+    /** Makes sure the input task is in a stack with the specified bounds by either resizing the
+     * current task stack if it only has one entry, moving the task to a stack that matches the
+     * bounds, or creating a new stack with the required bounds. Also, makes the task resizeable.*/
+    void resizeTaskLocked(TaskRecord task, Rect bounds) {
+        task.mResizeable = true;
+        final ActivityStack currentStack = task.stack;
+        if (currentStack.isHomeStack()) {
+            // Can't move task off the home stack. Sorry!
+            return;
+        }
+
+        final int matchingStackId = mWindowManager.getStackIdWithBounds(bounds);
+        if (matchingStackId != -1) {
+            // There is already a stack with the right bounds!
+            if (currentStack != null && currentStack.mStackId == matchingStackId) {
+                // Nothing to do here. Already in the right stack...
+                return;
+            }
+            // Move task to stack with matching bounds.
+            moveTaskToStackLocked(task.taskId, matchingStackId, true);
+            return;
+        }
+
+        if (currentStack != null && currentStack.numTasks() == 1) {
+            // Just resize the current stack since this is the task in it.
+            resizeStackLocked(currentStack.mStackId, bounds);
+            return;
+        }
+
+        // Create new stack and move the task to it.
+        final int displayId = (currentStack != null && currentStack.mDisplayId != -1)
+                ? currentStack.mDisplayId : Display.DEFAULT_DISPLAY;
+        ActivityStack newStack = createStackOnDisplay(getNextStackId(), displayId);
+
+        if (newStack == null) {
+            Slog.e(TAG, "resizeTaskLocked: Can't create stack for task=" + task);
+            return;
+        }
+        moveTaskToStackLocked(task.taskId, newStack.mStackId, true);
+        resizeStackLocked(newStack.mStackId, bounds);
+    }
+
     ActivityStack createStackOnDisplay(int stackId, int displayId) {
         ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay == null) {
@@ -2718,6 +2760,7 @@
     void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) {
         final TaskRecord task = anyTaskForIdLocked(taskId);
         if (task == null) {
+            Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
             return;
         }
         final ActivityStack stack = getStack(stackId);
@@ -2725,9 +2768,11 @@
             Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
             return;
         }
-        task.stack.removeTask(task, "moveTaskToStack");
+        mWindowManager.moveTaskToStack(taskId, stackId, toTop);
+        if (task.stack != null) {
+            task.stack.removeTask(task, "moveTaskToStack", false);
+        }
         stack.addTask(task, toTop, true);
-        mWindowManager.addTask(taskId, stackId, toTop);
         resumeTopActivitiesLocked();
     }
 
@@ -3068,7 +3113,7 @@
     }
 
     boolean switchUserLocked(int userId, UserStartedState uss) {
-        mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId());
+        mUserStackInFront.put(mCurrentUser, mFocusedStack.getStackId());
         final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
         mCurrentUser = userId;
 
@@ -3192,7 +3237,7 @@
     }
 
     ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
-        return getFocusedStack().getDumpActivitiesLocked(name);
+        return mFocusedStack.getDumpActivitiesLocked(name);
     }
 
     static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7ab3794..6a29d85 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -187,10 +187,12 @@
 
     public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
         mParallelBroadcasts.add(r);
+        r.enqueueClockTime = System.currentTimeMillis();
     }
 
     public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
         mOrderedBroadcasts.add(r);
+        r.enqueueClockTime = System.currentTimeMillis();
     }
 
     public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index b2cfd7a..9a4d7a0 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -52,6 +52,7 @@
     final int appOp;        // an app op that is associated with this broadcast
     final List receivers;   // contains BroadcastFilter and ResolveInfo
     IIntentReceiver resultTo; // who receives final result if non-null
+    long enqueueClockTime;  // the clock time the broadcast was enqueued
     long dispatchTime;      // when dispatch started on this set of receivers
     long dispatchClockTime; // the clock time the dispatch started
     long receiverTime;      // when current receiver started for timeouts.
@@ -102,7 +103,9 @@
             pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission);
                     pw.print("  appOp="); pw.println(appOp);
         }
-        pw.print(prefix); pw.print("dispatchClockTime=");
+        pw.print(prefix); pw.print("enqueueClockTime=");
+                pw.print(new Date(enqueueClockTime));
+                pw.print(" dispatchClockTime=");
                 pw.println(new Date(dispatchClockTime));
         pw.print(prefix); pw.print("dispatchTime=");
                 TimeUtils.formatDuration(dispatchTime, now, pw);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9786b42..66c2f5f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -128,6 +128,7 @@
     private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4;
     private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake
     private static final int WAKE_LOCK_DOZE = 1 << 6;
+    private static final int WAKE_LOCK_DRAW = 1 << 7;
 
     // Summarizes the user activity state.
     private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0;
@@ -1398,12 +1399,15 @@
                     case PowerManager.DOZE_WAKE_LOCK:
                         mWakeLockSummary |= WAKE_LOCK_DOZE;
                         break;
+                    case PowerManager.DRAW_WAKE_LOCK:
+                        mWakeLockSummary |= WAKE_LOCK_DRAW;
+                        break;
                 }
             }
 
             // Cancel wake locks that make no sense based on the current state.
             if (mWakefulness != WAKEFULNESS_DOZING) {
-                mWakeLockSummary &= ~WAKE_LOCK_DOZE;
+                mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
             }
             if (mWakefulness == WAKEFULNESS_ASLEEP
                     || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
@@ -1422,6 +1426,9 @@
                     mWakeLockSummary |= WAKE_LOCK_CPU;
                 }
             }
+            if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+                mWakeLockSummary |= WAKE_LOCK_CPU;
+            }
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness="
@@ -1845,6 +1852,10 @@
 
             if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
                 mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
+                if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+                        && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+                    mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                }
                 mDisplayPowerRequest.dozeScreenBrightness =
                         mDozeScreenBrightnessOverrideFromDreamManager;
             } else {
@@ -2712,6 +2723,8 @@
                     return "PROXIMITY_SCREEN_OFF_WAKE_LOCK";
                 case PowerManager.DOZE_WAKE_LOCK:
                     return "DOZE_WAKE_LOCK                ";
+                case PowerManager.DRAW_WAKE_LOCK:
+                    return "DRAW_WAKE_LOCK                ";
                 default:
                     return "???                           ";
             }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index d68c056..487483e9 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -461,6 +461,16 @@
         return mService.getWindowId(window);
     }
 
+    @Override
+    public void pokeDrawLock(IBinder window) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mService.pokeDrawLock(this, window);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     void windowAddedLocked() {
         if (mSurfaceSession == null) {
             if (WindowManagerService.localLOGV) Slog.v(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a9b26e2..b8f26c9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -72,6 +72,19 @@
         mService.mTaskIdToTask.delete(mTaskId);
     }
 
+    void moveTaskToStack(TaskStack stack, boolean toTop) {
+        if (stack == mStack) {
+            return;
+        }
+        if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+                + " from stack=" + mStack);
+        EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask");
+        if (mStack != null) {
+            mStack.removeTask(this);
+        }
+        stack.addTask(this, toTop);
+    }
+
     boolean removeAppToken(AppWindowToken wtoken) {
         boolean removed = mAppTokens.remove(wtoken);
         if (mAppTokens.size() == 0) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index de8a2fc..fde0bd5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 import static android.view.WindowManager.LayoutParams.*;
 
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -189,6 +190,7 @@
     static final boolean DEBUG_TASK_MOVEMENT = false;
     static final boolean DEBUG_STACK = false;
     static final boolean DEBUG_DISPLAY = false;
+    static final boolean DEBUG_POWER = false;
     static final boolean SHOW_SURFACE_ALLOC = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -327,6 +329,7 @@
     final boolean mHaveInputMethods;
 
     final boolean mHasPermanentDpad;
+    final long mDrawLockTimeoutMillis;
 
     final boolean mAllowBootMessages;
 
@@ -680,11 +683,11 @@
 
     final WindowAnimator mAnimator;
 
-    SparseArray<Task> mTaskIdToTask = new SparseArray<Task>();
+    SparseArray<Task> mTaskIdToTask = new SparseArray<>();
 
     /** All of the TaskStacks in the window manager, unordered. For an ordered list call
      * DisplayContent.getStacks(). */
-    SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>();
+    SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();
 
     private final PointerEventDispatcher mPointerEventDispatcher;
 
@@ -846,6 +849,8 @@
                 com.android.internal.R.bool.config_hasPermanentDpad);
         mInTouchMode = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_defaultInTouchMode);
+        mDrawLockTimeoutMillis = context.getResources().getInteger(
+                com.android.internal.R.integer.config_drawLockTimeoutMillis);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mDisplaySettings = new DisplaySettings();
@@ -2960,6 +2965,15 @@
         }
     }
 
+    public void pokeDrawLock(Session session, IBinder token) {
+        synchronized (mWindowMap) {
+            WindowState window = windowForClientLocked(session, token, false);
+            if (window != null) {
+                window.pokeDrawLockLw(mDrawLockTimeoutMillis);
+            }
+        }
+    }
+
     public int relayoutWindow(Session session, IWindow client, int seq,
             WindowManager.LayoutParams attrs, int requestedWidth,
             int requestedHeight, int viewVisibility, int flags,
@@ -5075,6 +5089,7 @@
                     + " to " + (toTop ? "top" : "bottom"));
             Task task = mTaskIdToTask.get(taskId);
             if (task == null) {
+                if (DEBUG_STACK) Slog.i(TAG, "addTask: could not find taskId=" + taskId);
                 return;
             }
             TaskStack stack = mStackIdToStack.get(stackId);
@@ -5085,6 +5100,27 @@
         }
     }
 
+    public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+        synchronized (mWindowMap) {
+            if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: moving taskId=" + taskId
+                    + " to stackId=" + stackId + " at " + (toTop ? "top" : "bottom"));
+            Task task = mTaskIdToTask.get(taskId);
+            if (task == null) {
+                if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find taskId=" + taskId);
+                return;
+            }
+            TaskStack stack = mStackIdToStack.get(stackId);
+            if (stack == null) {
+                if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find stackId=" + stackId);
+                return;
+            }
+            task.moveTaskToStack(stack, toTop);
+            final DisplayContent displayContent = stack.getDisplayContent();
+            displayContent.layoutNeeded = true;
+            performLayoutAndPlaceSurfacesLocked();
+        }
+    }
+
     /**
      * Re-sizes the specified stack and its containing windows.
      * Returns a {@link Configuration} object that contains configurations settings
@@ -5115,6 +5151,24 @@
         bounds.setEmpty();
     }
 
+    /** Returns the id of an application (non-home stack) stack that match the input bounds.
+     * -1 if no stack matches.*/
+    public int getStackIdWithBounds(Rect bounds) {
+        Rect stackBounds = new Rect();
+        synchronized (mWindowMap) {
+            for (int i = mStackIdToStack.size() - 1; i >= 0; --i) {
+                TaskStack stack = mStackIdToStack.valueAt(i);
+                if (stack.mStackId != HOME_STACK_ID) {
+                    stack.getBounds(stackBounds);
+                    if (stackBounds.equals(bounds)) {
+                        return stack.mStackId;
+                    }
+                }
+            }
+        }
+        return -1;
+    }
+
     /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen.
      * Returns a {@link Configuration} object that contains configurations settings
      * that should be overridden due to the operation.
@@ -9957,7 +10011,9 @@
             if (mAllowTheaterModeWakeFromLayout
                     || Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.THEATER_MODE_ON, 0) == 0) {
-                if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!");
+                if (DEBUG_VISIBILITY || DEBUG_POWER) {
+                    Slog.v(TAG, "Turning screen on after layout!");
+                }
                 mPowerManager.wakeUp(SystemClock.uptimeMillis());
             }
             mTurnOnScreen = false;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d58b2b0..04aea84 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -19,9 +19,9 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
-
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -34,12 +34,15 @@
 
 import android.app.AppOpsManager;
 import android.os.Debug;
+import android.os.PowerManager;
 import android.os.RemoteCallbackList;
 import android.os.SystemClock;
+import android.os.WorkSource;
 import android.util.TimeUtils;
 import android.view.Display;
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
+
 import com.android.server.input.InputWindowHandle;
 
 import android.content.Context;
@@ -343,6 +346,15 @@
     /** When true this window can be displayed on screens owther than mOwnerUid's */
     private boolean mShowToOwnerOnly;
 
+    /**
+     * Wake lock for drawing.
+     * Even though it's slightly more expensive to do so, we will use a separate wake lock
+     * for each app that is requesting to draw while dozing so that we can accurately track
+     * who is preventing the system from suspending.
+     * This lock is only acquired on first use.
+     */
+    PowerManager.WakeLock mDrawLock;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, final DisplayContent displayContent) {
@@ -1269,6 +1281,33 @@
         }
     }
 
+    public void pokeDrawLockLw(long timeout) {
+        if (isVisibleOrAdding()) {
+            if (mDrawLock == null) {
+                // We want the tag name to be somewhat stable so that it is easier to correlate
+                // in wake lock statistics.  So in particular, we don't want to include the
+                // window's hash code as in toString().
+                CharSequence tag = mAttrs.getTitle();
+                if (tag == null) {
+                    tag = mAttrs.packageName;
+                }
+                mDrawLock = mService.mPowerManager.newWakeLock(
+                        PowerManager.DRAW_WAKE_LOCK, "Window:" + tag);
+                mDrawLock.setReferenceCounted(false);
+                mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName));
+            }
+            // Each call to acquire resets the timeout.
+            if (DEBUG_POWER) {
+                Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by "
+                        + mAttrs.packageName);
+            }
+            mDrawLock.acquire(timeout);
+        } else if (DEBUG_POWER) {
+            Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window "
+                    + "owned by " + mAttrs.packageName);
+        }
+    }
+
     @Override
     public boolean isAlive() {
         return mClient.asBinder().isBinderAlive();
@@ -1642,6 +1681,9 @@
                     pw.print(" mWallpaperDisplayOffsetY=");
                     pw.println(mWallpaperDisplayOffsetY);
         }
+        if (mDrawLock != null) {
+            pw.println("mDrawLock=" + mDrawLock);
+        }
     }
 
     String makeInputChannelName() {
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 36102f1..7e4ff69 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -179,7 +179,7 @@
                         XmlPullParser parser = ParserFactory.create(f);
 
                         BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
-                                parser, bridgeContext, false);
+                                parser, bridgeContext, value.isFramework());
 
                         return inflate(bridgeParser, root);
                     } catch (Exception e) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 62a03e1..4289689 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -206,4 +206,9 @@
         // pass for now.
         return null;
     }
+
+    @Override
+    public void pokeDrawLock(IBinder window) {
+        // pass for now.
+    }
 }