Merge "Allow polling for WindowInsets."
diff --git a/api/current.txt b/api/current.txt
index 20ff37a..c075029e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -413,6 +413,7 @@
     field public static final int colorActivatedHighlight = 16843664; // 0x1010390
     field public static final int colorBackground = 16842801; // 0x1010031
     field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
+    field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
     field public static final int colorButtonNormal = 16843819; // 0x101042b
     field public static final int colorControlActivated = 16843818; // 0x101042a
     field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -1723,6 +1724,7 @@
     field public static final int primary = 16908300; // 0x102000c
     field public static final int progress = 16908301; // 0x102000d
     field public static final int redo = 16908339; // 0x1020033
+    field public static final int replaceText = 16908340; // 0x1020034
     field public static final int secondaryProgress = 16908303; // 0x102000f
     field public static final int selectAll = 16908319; // 0x102001f
     field public static final int selectTextMode = 16908333; // 0x102002d
@@ -22310,7 +22312,9 @@
   public final class Looper {
     method public void dump(android.util.Printer, java.lang.String);
     method public static android.os.Looper getMainLooper();
+    method public android.os.MessageQueue getQueue();
     method public java.lang.Thread getThread();
+    method public boolean isCurrentThread();
     method public static void loop();
     method public static android.os.Looper myLooper();
     method public static android.os.MessageQueue myQueue();
@@ -22368,7 +22372,18 @@
 
   public final class MessageQueue {
     method public void addIdleHandler(android.os.MessageQueue.IdleHandler);
+    method public boolean isIdle();
+    method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback);
     method public void removeIdleHandler(android.os.MessageQueue.IdleHandler);
+    method public void unregisterFileDescriptorCallback(java.io.FileDescriptor);
+  }
+
+  public static abstract class MessageQueue.FileDescriptorCallback {
+    ctor public MessageQueue.FileDescriptorCallback();
+    method public int onFileDescriptorEvents(java.io.FileDescriptor, int);
+    field public static final int EVENT_ERROR = 4; // 0x4
+    field public static final int EVENT_INPUT = 1; // 0x1
+    field public static final int EVENT_OUTPUT = 2; // 0x2
   }
 
   public static abstract interface MessageQueue.IdleHandler {
@@ -36926,6 +36941,26 @@
     method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
   }
 
+  public class WebMessage {
+    ctor public WebMessage(java.lang.String);
+    ctor public WebMessage(java.lang.String, android.webkit.WebMessagePort[]);
+    method public java.lang.String getData();
+    method public android.webkit.WebMessagePort[] getPorts();
+  }
+
+  public abstract class WebMessagePort {
+    ctor public WebMessagePort();
+    method public abstract void close();
+    method public abstract void postMessage(android.webkit.WebMessage);
+    method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback);
+    method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback, android.os.Handler);
+  }
+
+  public static abstract class WebMessagePort.WebMessageCallback {
+    ctor public WebMessagePort.WebMessageCallback();
+    method public void onMessage(android.webkit.WebMessagePort, android.webkit.WebMessage);
+  }
+
   public abstract class WebResourceError {
     ctor public WebResourceError();
     method public abstract java.lang.String getDescription();
@@ -37169,6 +37204,7 @@
     method public android.webkit.WebBackForwardList copyBackForwardList();
     method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
     method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+    method public android.webkit.WebMessagePort[] createWebMessageChannel();
     method public void destroy();
     method public void documentHasImages(android.os.Message);
     method public static void enableSlowWholeDocumentDraw();
@@ -37209,6 +37245,7 @@
     method public boolean pageDown(boolean);
     method public boolean pageUp(boolean);
     method public void pauseTimers();
+    method public void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
     method public void postUrl(java.lang.String, byte[]);
     method public void reload();
     method public void removeJavascriptInterface(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 11ae552..eb47c5e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -485,6 +485,7 @@
     field public static final int colorActivatedHighlight = 16843664; // 0x1010390
     field public static final int colorBackground = 16842801; // 0x1010031
     field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
+    field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
     field public static final int colorButtonNormal = 16843819; // 0x101042b
     field public static final int colorControlActivated = 16843818; // 0x101042a
     field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -1799,6 +1800,7 @@
     field public static final int primary = 16908300; // 0x102000c
     field public static final int progress = 16908301; // 0x102000d
     field public static final int redo = 16908339; // 0x1020033
+    field public static final int replaceText = 16908340; // 0x1020034
     field public static final int secondaryProgress = 16908303; // 0x102000f
     field public static final int selectAll = 16908319; // 0x102001f
     field public static final int selectTextMode = 16908333; // 0x102002d
@@ -23905,7 +23907,9 @@
   public final class Looper {
     method public void dump(android.util.Printer, java.lang.String);
     method public static android.os.Looper getMainLooper();
+    method public android.os.MessageQueue getQueue();
     method public java.lang.Thread getThread();
+    method public boolean isCurrentThread();
     method public static void loop();
     method public static android.os.Looper myLooper();
     method public static android.os.MessageQueue myQueue();
@@ -23963,7 +23967,18 @@
 
   public final class MessageQueue {
     method public void addIdleHandler(android.os.MessageQueue.IdleHandler);
+    method public boolean isIdle();
+    method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback);
     method public void removeIdleHandler(android.os.MessageQueue.IdleHandler);
+    method public void unregisterFileDescriptorCallback(java.io.FileDescriptor);
+  }
+
+  public static abstract class MessageQueue.FileDescriptorCallback {
+    ctor public MessageQueue.FileDescriptorCallback();
+    method public int onFileDescriptorEvents(java.io.FileDescriptor, int);
+    field public static final int EVENT_ERROR = 4; // 0x4
+    field public static final int EVENT_INPUT = 1; // 0x1
+    field public static final int EVENT_OUTPUT = 2; // 0x2
   }
 
   public static abstract interface MessageQueue.IdleHandler {
@@ -39155,6 +39170,26 @@
     method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
   }
 
+  public class WebMessage {
+    ctor public WebMessage(java.lang.String);
+    ctor public WebMessage(java.lang.String, android.webkit.WebMessagePort[]);
+    method public java.lang.String getData();
+    method public android.webkit.WebMessagePort[] getPorts();
+  }
+
+  public abstract class WebMessagePort {
+    ctor public WebMessagePort();
+    method public abstract void close();
+    method public abstract void postMessage(android.webkit.WebMessage);
+    method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback);
+    method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback, android.os.Handler);
+  }
+
+  public static abstract class WebMessagePort.WebMessageCallback {
+    ctor public WebMessagePort.WebMessageCallback();
+    method public void onMessage(android.webkit.WebMessagePort, android.webkit.WebMessage);
+  }
+
   public abstract class WebResourceError {
     ctor public WebResourceError();
     method public abstract java.lang.String getDescription();
@@ -39412,6 +39447,7 @@
     method public android.webkit.WebBackForwardList copyBackForwardList();
     method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
     method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+    method public android.webkit.WebMessagePort[] createWebMessageChannel();
     method public void destroy();
     method public void documentHasImages(android.os.Message);
     method public static void enableSlowWholeDocumentDraw();
@@ -39453,6 +39489,7 @@
     method public boolean pageDown(boolean);
     method public boolean pageUp(boolean);
     method public void pauseTimers();
+    method public void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
     method public void postUrl(java.lang.String, byte[]);
     method public void reload();
     method public void removeJavascriptInterface(java.lang.String);
@@ -39665,6 +39702,7 @@
     method public abstract void clearView();
     method public abstract android.webkit.WebBackForwardList copyBackForwardList();
     method public abstract android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+    method public abstract android.webkit.WebMessagePort[] createWebMessageChannel();
     method public abstract void destroy();
     method public abstract void documentHasImages(android.os.Message);
     method public abstract void dumpViewHierarchyWithProperties(java.io.BufferedWriter, int);
@@ -39711,6 +39749,7 @@
     method public abstract boolean pageDown(boolean);
     method public abstract boolean pageUp(boolean);
     method public abstract void pauseTimers();
+    method public abstract void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
     method public abstract void postUrl(java.lang.String, byte[]);
     method public abstract void reload();
     method public abstract void removeJavascriptInterface(java.lang.String);
diff --git a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
index 0699ffb..e19ebf2 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
@@ -96,15 +96,15 @@
     // Blocks until thread is idling
     public void waitUntilIdle() {
         Handler handler = waitAndGetHandler();
-        Looper looper = handler.getLooper();
-        if (looper.isIdling()) {
+        MessageQueue queue = handler.getLooper().getQueue();
+        if (queue.isIdle()) {
             return;
         }
         mIdle.close();
-        looper.getQueue().addIdleHandler(mIdleHandler);
+        queue.addIdleHandler(mIdleHandler);
         // Ensure that the idle handler gets run even if the looper already went idle
         handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
-        if (looper.isIdling()) {
+        if (queue.isIdle()) {
             return;
         }
         mIdle.block();
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 6d7c9cf..34c880f 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Log;
 import android.util.Printer;
 
@@ -24,10 +26,10 @@
   * not have a message loop associated with them; to create one, call
   * {@link #prepare} in the thread that is to run the loop, and then
   * {@link #loop} to have it process messages until the loop is stopped.
-  * 
+  *
   * <p>Most interaction with a message loop is through the
   * {@link Handler} class.
-  * 
+  *
   * <p>This is a typical example of the implementation of a Looper thread,
   * using the separation of {@link #prepare} and {@link #loop} to create an
   * initial Handler to communicate with the Looper.
@@ -50,6 +52,16 @@
   *  }</pre>
   */
 public final class Looper {
+    /*
+     * API Implementation Note:
+     *
+     * This class contains the code required to set up and manage an event loop
+     * based on MessageQueue.  APIs that affect the state of the queue should be
+     * defined on MessageQueue or Handler rather than on Looper itself.  For example,
+     * idle handlers and sync barriers are defined on the queue whereas preparing the
+     * thread, looping, and quitting are defined on the looper.
+     */
+
     private static final String TAG = "Looper";
 
     // sThreadLocal.get() will return null unless you've called prepare().
@@ -94,7 +106,8 @@
         }
     }
 
-    /** Returns the application's main looper, which lives in the main thread of the application.
+    /**
+     * Returns the application's main looper, which lives in the main thread of the application.
      */
     public static Looper getMainLooper() {
         synchronized (Looper.class) {
@@ -157,29 +170,16 @@
      * Return the Looper object associated with the current thread.  Returns
      * null if the calling thread is not associated with a Looper.
      */
-    public static Looper myLooper() {
+    public static @Nullable Looper myLooper() {
         return sThreadLocal.get();
     }
 
     /**
-     * Control logging of messages as they are processed by this Looper.  If
-     * enabled, a log message will be written to <var>printer</var> 
-     * at the beginning and ending of each message dispatch, identifying the
-     * target Handler and message contents.
-     * 
-     * @param printer A Printer object that will receive log messages, or
-     * null to disable message logging.
-     */
-    public void setMessageLogging(Printer printer) {
-        mLogging = printer;
-    }
-    
-    /**
      * Return the {@link MessageQueue} object associated with the current
      * thread.  This must be called from a thread running a Looper, or a
      * NullPointerException will be thrown.
      */
-    public static MessageQueue myQueue() {
+    public static @NonNull MessageQueue myQueue() {
         return myLooper().mQueue;
     }
 
@@ -190,13 +190,25 @@
 
     /**
      * Returns true if the current thread is this looper's thread.
-     * @hide
      */
     public boolean isCurrentThread() {
         return Thread.currentThread() == mThread;
     }
 
     /**
+     * Control logging of messages as they are processed by this Looper.  If
+     * enabled, a log message will be written to <var>printer</var>
+     * at the beginning and ending of each message dispatch, identifying the
+     * target Handler and message contents.
+     *
+     * @param printer A Printer object that will receive log messages, or
+     * null to disable message logging.
+     */
+    public void setMessageLogging(@Nullable Printer printer) {
+        mLogging = printer;
+    }
+
+    /**
      * Quits the looper.
      * <p>
      * Causes the {@link #loop} method to terminate without processing any
@@ -233,74 +245,35 @@
     }
 
     /**
-     * Posts a synchronization barrier to the Looper's message queue.
+     * Gets the Thread associated with this Looper.
      *
-     * Message processing occurs as usual until the message queue encounters the
-     * synchronization barrier that has been posted.  When the barrier is encountered,
-     * later synchronous messages in the queue are stalled (prevented from being executed)
-     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
-     * the token that identifies the synchronization barrier.
-     *
-     * This method is used to immediately postpone execution of all subsequently posted
-     * synchronous messages until a condition is met that releases the barrier.
-     * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
-     * and continue to be processed as usual.
-     *
-     * This call must be always matched by a call to {@link #removeSyncBarrier} with
-     * the same token to ensure that the message queue resumes normal operation.
-     * Otherwise the application will probably hang!
-     *
-     * @return A token that uniquely identifies the barrier.  This token must be
-     * passed to {@link #removeSyncBarrier} to release the barrier.
-     *
-     * @hide
+     * @return The looper's thread.
      */
-    public int postSyncBarrier() {
-        return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
-    }
-
-
-    /**
-     * Removes a synchronization barrier.
-     *
-     * @param token The synchronization barrier token that was returned by
-     * {@link #postSyncBarrier}.
-     *
-     * @throws IllegalStateException if the barrier was not found.
-     *
-     * @hide
-     */
-    public void removeSyncBarrier(int token) {
-        mQueue.removeSyncBarrier(token);
-    }
-
-    /**
-     * Return the Thread associated with this Looper.
-     */
-    public Thread getThread() {
+    public @NonNull Thread getThread() {
         return mThread;
     }
 
-    /** @hide */
-    public MessageQueue getQueue() {
+    /**
+     * Gets this looper's message queue.
+     *
+     * @return The looper's message queue.
+     */
+    public @NonNull MessageQueue getQueue() {
         return mQueue;
     }
 
     /**
-     * Return whether this looper's thread is currently idle, waiting for new work
-     * to do.  This is intrinsically racy, since its state can change before you get
-     * the result back.
-     * @hide
+     * Dumps the state of the looper for debugging purposes.
+     *
+     * @param pw A printer to receive the contents of the dump.
+     * @param prefix A prefix to prepend to each line which is printed.
      */
-    public boolean isIdling() {
-        return mQueue.isIdling();
-    }
-
-    public void dump(Printer pw, String prefix) {
+    public void dump(@NonNull Printer pw, @NonNull String prefix) {
         pw.println(prefix + toString());
         mQueue.dump(pw, prefix + "  ");
     }
 
+    @Override
     public String toString() {
         return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
                 + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 01a23ce..7dd4f94 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,9 +16,15 @@
 
 package android.os;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.util.Log;
 import android.util.Printer;
+import android.util.SparseArray;
 
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -30,6 +36,9 @@
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
 public final class MessageQueue {
+    private static final String TAG = "MessageQueue";
+    private static final boolean DEBUG = false;
+
     // True if the message queue can be quit.
     private final boolean mQuitAllowed;
 
@@ -38,6 +47,7 @@
 
     Message mMessages;
     private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
     private IdleHandler[] mPendingIdleHandlers;
     private boolean mQuitting;
 
@@ -50,56 +60,10 @@
 
     private native static long nativeInit();
     private native static void nativeDestroy(long ptr);
-    private native static void nativePollOnce(long ptr, int timeoutMillis);
+    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
     private native static void nativeWake(long ptr);
-    private native static boolean nativeIsIdling(long ptr);
-
-    /**
-     * Callback interface for discovering when a thread is going to block
-     * waiting for more messages.
-     */
-    public static interface IdleHandler {
-        /**
-         * Called when the message queue has run out of messages and will now
-         * wait for more.  Return true to keep your idle handler active, false
-         * to have it removed.  This may be called if there are still messages
-         * pending in the queue, but they are all scheduled to be dispatched
-         * after the current time.
-         */
-        boolean queueIdle();
-    }
-
-    /**
-     * Add a new {@link IdleHandler} to this message queue.  This may be
-     * removed automatically for you by returning false from
-     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
-     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
-     * 
-     * <p>This method is safe to call from any thread.
-     * 
-     * @param handler The IdleHandler to be added.
-     */
-    public void addIdleHandler(IdleHandler handler) {
-        if (handler == null) {
-            throw new NullPointerException("Can't add a null IdleHandler");
-        }
-        synchronized (this) {
-            mIdleHandlers.add(handler);
-        }
-    }
-
-    /**
-     * Remove an {@link IdleHandler} from the queue that was previously added
-     * with {@link #addIdleHandler}.  If the given object is not currently
-     * in the idle list, nothing is done.
-     * 
-     * @param handler The IdleHandler to be removed.
-     */
-    public void removeIdleHandler(IdleHandler handler) {
-        synchronized (this) {
-            mIdleHandlers.remove(handler);
-        }
-    }
+    private native static boolean nativeIsPolling(long ptr);
+    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
 
     MessageQueue(boolean quitAllowed) {
         mQuitAllowed = quitAllowed;
@@ -124,6 +88,222 @@
         }
     }
 
+    /**
+     * Returns true if the looper has no pending messages which are due to be processed.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @return True if the looper is idle.
+     */
+    public boolean isIdle() {
+        synchronized (this) {
+            final long now = SystemClock.uptimeMillis();
+            return mMessages == null || now < mMessages.when;
+        }
+    }
+
+    /**
+     * Add a new {@link IdleHandler} to this message queue.  This may be
+     * removed automatically for you by returning false from
+     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @param handler The IdleHandler to be added.
+     */
+    public void addIdleHandler(@NonNull IdleHandler handler) {
+        if (handler == null) {
+            throw new NullPointerException("Can't add a null IdleHandler");
+        }
+        synchronized (this) {
+            mIdleHandlers.add(handler);
+        }
+    }
+
+    /**
+     * Remove an {@link IdleHandler} from the queue that was previously added
+     * with {@link #addIdleHandler}.  If the given object is not currently
+     * in the idle list, nothing is done.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @param handler The IdleHandler to be removed.
+     */
+    public void removeIdleHandler(@NonNull IdleHandler handler) {
+        synchronized (this) {
+            mIdleHandlers.remove(handler);
+        }
+    }
+
+    /**
+     * Returns whether this looper's thread is currently polling for more work to do.
+     * This is a good signal that the loop is still alive rather than being stuck
+     * handling a callback.  Note that this method is intrinsically racy, since the
+     * state of the loop can change before you get the result back.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @return True if the looper is currently polling for events.
+     * @hide
+     */
+    public boolean isPolling() {
+        synchronized (this) {
+            return isPollingLocked();
+        }
+    }
+
+    private boolean isPollingLocked() {
+        // If the loop is quitting then it must not be idling.
+        // We can assume mPtr != 0 when mQuitting is false.
+        return !mQuitting && nativeIsPolling(mPtr);
+    }
+
+    /**
+     * Registers a file descriptor callback to receive notification when file descriptor
+     * related events occur.
+     * <p>
+     * If the file descriptor has already been registered, the specified events
+     * and callback will replace any that were previously associated with it.
+     * It is not possible to set more than one callback per file descriptor.
+     * </p><p>
+     * It is important to always unregister the callback when the file descriptor
+     * is no longer of use.
+     * </p>
+     *
+     * @param fd The file descriptor for which a callback will be registered.
+     * @param events The set of events to receive: a combination of the
+     * {@link FileDescriptorCallback#EVENT_INPUT},
+     * {@link FileDescriptorCallback#EVENT_OUTPUT}, and
+     * {@link FileDescriptorCallback#EVENT_ERROR} event masks.  If the requested
+     * set of events is zero, then the callback is unregistered.
+     * @param callback The callback to invoke when file descriptor events occur.
+     *
+     * @see FileDescriptorCallback
+     * @see #unregisterFileDescriptorCallback
+     */
+    public void registerFileDescriptorCallback(@NonNull FileDescriptor fd,
+            @FileDescriptorCallback.Events int events,
+            @NonNull FileDescriptorCallback callback) {
+        if (fd == null) {
+            throw new IllegalArgumentException("fd must not be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+
+        synchronized (this) {
+            setFileDescriptorCallbackLocked(fd, events, callback);
+        }
+    }
+
+    /**
+     * Unregisters a file descriptor callback.
+     * <p>
+     * This method does nothing if no callback has been registered for the
+     * specified file descriptor.
+     * </p>
+     *
+     * @param fd The file descriptor whose callback will be unregistered.
+     *
+     * @see FileDescriptorCallback
+     * @see #registerFileDescriptorCallback
+     */
+    public void unregisterFileDescriptorCallback(@NonNull FileDescriptor fd) {
+        if (fd == null) {
+            throw new IllegalArgumentException("fd must not be null");
+        }
+
+        synchronized (this) {
+            setFileDescriptorCallbackLocked(fd, 0, null);
+        }
+    }
+
+    private void setFileDescriptorCallbackLocked(FileDescriptor fd, int events,
+            FileDescriptorCallback callback) {
+        final int fdNum = fd.getInt$();
+
+        int index = -1;
+        FileDescriptorRecord record = null;
+        if (mFileDescriptorRecords != null) {
+            index = mFileDescriptorRecords.indexOfKey(fdNum);
+            if (index >= 0) {
+                record = mFileDescriptorRecords.valueAt(index);
+                if (record != null && record.mEvents == events) {
+                    return;
+                }
+            }
+        }
+
+        if (events != 0) {
+            events |= FileDescriptorCallback.EVENT_ERROR;
+            if (record == null) {
+                if (mFileDescriptorRecords == null) {
+                    mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+                }
+                record = new FileDescriptorRecord(fd, events, callback);
+                mFileDescriptorRecords.put(fdNum, record);
+            } else {
+                record.mCallback = callback;
+                record.mEvents = events;
+                record.mSeq += 1;
+            }
+            nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+        } else if (record != null) {
+            record.mEvents = 0;
+            mFileDescriptorRecords.removeAt(index);
+        }
+    }
+
+    // Called from native code.
+    private int dispatchEvents(int fd, int events) {
+        // Get the file descriptor record and any state that might change.
+        final FileDescriptorRecord record;
+        final int oldWatchedEvents;
+        final FileDescriptorCallback callback;
+        final int seq;
+        synchronized (this) {
+            record = mFileDescriptorRecords.get(fd);
+            if (record == null) {
+                return 0; // spurious, no callback registered
+            }
+
+            oldWatchedEvents = record.mEvents;
+            events &= oldWatchedEvents; // filter events based on current watched set
+            if (events == 0) {
+                return oldWatchedEvents; // spurious, watched events changed
+            }
+
+            callback = record.mCallback;
+            seq = record.mSeq;
+        }
+
+        // Invoke the callback outside of the lock.
+        int newWatchedEvents = callback.onFileDescriptorEvents(
+                record.mDescriptor, events);
+        if (newWatchedEvents != 0) {
+            newWatchedEvents |= FileDescriptorCallback.EVENT_ERROR;
+        }
+
+        // Update the file descriptor record if the callback changed the set of
+        // events to watch and the callback itself hasn't been updated since.
+        if (newWatchedEvents != oldWatchedEvents) {
+            synchronized (this) {
+                int index = mFileDescriptorRecords.indexOfKey(fd);
+                if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+                        && record.mSeq == seq) {
+                    record.mEvents = newWatchedEvents;
+                    if (newWatchedEvents == 0) {
+                        mFileDescriptorRecords.removeAt(index);
+                    }
+                }
+            }
+        }
+
+        // Return the new set of events to watch for native code to take care of.
+        return newWatchedEvents;
+    }
+
     Message next() {
         // Return here if the message loop has already quit and been disposed.
         // This can happen if the application tries to restart a looper after quit
@@ -167,7 +347,8 @@
                             mMessages = msg.next;
                         }
                         msg.next = null;
-                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
+                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
+                        msg.markInUse();
                         return msg;
                     }
                 } else {
@@ -210,7 +391,7 @@
                 try {
                     keep = idler.queueIdle();
                 } catch (Throwable t) {
-                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
+                    Log.wtf(TAG, "IdleHandler threw exception", t);
                 }
 
                 if (!keep) {
@@ -251,7 +432,34 @@
         }
     }
 
-    int enqueueSyncBarrier(long when) {
+    /**
+     * Posts a synchronization barrier to the Looper's message queue.
+     *
+     * Message processing occurs as usual until the message queue encounters the
+     * synchronization barrier that has been posted.  When the barrier is encountered,
+     * later synchronous messages in the queue are stalled (prevented from being executed)
+     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+     * the token that identifies the synchronization barrier.
+     *
+     * This method is used to immediately postpone execution of all subsequently posted
+     * synchronous messages until a condition is met that releases the barrier.
+     * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+     * and continue to be processed as usual.
+     *
+     * This call must be always matched by a call to {@link #removeSyncBarrier} with
+     * the same token to ensure that the message queue resumes normal operation.
+     * Otherwise the application will probably hang!
+     *
+     * @return A token that uniquely identifies the barrier.  This token must be
+     * passed to {@link #removeSyncBarrier} to release the barrier.
+     *
+     * @hide
+     */
+    public int postSyncBarrier() {
+        return postSyncBarrier(SystemClock.uptimeMillis());
+    }
+
+    private int postSyncBarrier(long when) {
         // Enqueue a new sync barrier token.
         // We don't need to wake the queue because the purpose of a barrier is to stall it.
         synchronized (this) {
@@ -280,7 +488,17 @@
         }
     }
 
-    void removeSyncBarrier(int token) {
+    /**
+     * Removes a synchronization barrier.
+     *
+     * @param token The synchronization barrier token that was returned by
+     * {@link #postSyncBarrier}.
+     *
+     * @throws IllegalStateException if the barrier was not found.
+     *
+     * @hide
+     */
+    public void removeSyncBarrier(int token) {
         // Remove a sync barrier token from the queue.
         // If the queue is no longer stalled by a barrier then wake it.
         synchronized (this) {
@@ -324,7 +542,7 @@
             if (mQuitting) {
                 IllegalStateException e = new IllegalStateException(
                         msg.target + " sending message to a Handler on a dead thread");
-                Log.w("MessageQueue", e.getMessage(), e);
+                Log.w(TAG, e.getMessage(), e);
                 msg.recycle();
                 return false;
             }
@@ -400,18 +618,6 @@
         }
     }
 
-    boolean isIdling() {
-        synchronized (this) {
-            return isIdlingLocked();
-        }
-    }
-
-    private boolean isIdlingLocked() {
-        // If the loop is quitting then it must not be idling.
-        // We can assume mPtr != 0 when mQuitting is false.
-        return !mQuitting && nativeIsIdling(mPtr);
-     }
-
     void removeMessages(Handler h, int what, Object object) {
         if (h == null) {
             return;
@@ -559,8 +765,113 @@
                 pw.println(prefix + "Message " + n + ": " + msg.toString(now));
                 n++;
             }
-            pw.println(prefix + "(Total messages: " + n + ", idling=" + isIdlingLocked()
+            pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
                     + ", quitting=" + mQuitting + ")");
         }
     }
+
+    /**
+     * Callback interface for discovering when a thread is going to block
+     * waiting for more messages.
+     */
+    public static interface IdleHandler {
+        /**
+         * Called when the message queue has run out of messages and will now
+         * wait for more.  Return true to keep your idle handler active, false
+         * to have it removed.  This may be called if there are still messages
+         * pending in the queue, but they are all scheduled to be dispatched
+         * after the current time.
+         */
+        boolean queueIdle();
+    }
+
+    /**
+     * A callback which is invoked when file descriptor related events occur.
+     */
+    public static abstract class FileDescriptorCallback {
+        /**
+         * File descriptor event: Indicates that the file descriptor is ready for input
+         * operations, such as reading.
+         * <p>
+         * The callback should read all available data from the file descriptor
+         * then return <code>true</code> to keep the callback active or <code>false</code>
+         * to remove the callback.
+         * </p><p>
+         * In the case of a socket, this event may be generated to indicate
+         * that there is at least one incoming connection that the callback
+         * should accept.
+         * </p><p>
+         * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+         * specified when the callback was added.
+         * </p>
+         */
+        public static final int EVENT_INPUT = 1 << 0;
+
+        /**
+         * File descriptor event: Indicates that the file descriptor is ready for output
+         * operations, such as writing.
+         * <p>
+         * The callback should write as much data as it needs.  If it could not
+         * write everything at once, then it should return <code>true</code> to
+         * keep the callback active.  Otherwise, it should return <code>false</code>
+         * to remove the callback then re-register it later when it needs to write
+         * something else.
+         * </p><p>
+         * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+         * specified when the callback was added.
+         * </p>
+         */
+        public static final int EVENT_OUTPUT = 1 << 1;
+
+        /**
+         * File descriptor event: Indicates that the file descriptor encountered a
+         * fatal error.
+         * <p>
+         * File descriptor errors can occur for various reasons.  One common error
+         * is when the remote peer of a socket or pipe closes its end of the connection.
+         * </p><p>
+         * This event may be generated at any time regardless of whether the
+         * {@link #EVENT_ERROR} event mask was specified when the callback was added.
+         * </p>
+         */
+        public static final int EVENT_ERROR = 1 << 2;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR})
+        public @interface Events {}
+
+        /**
+         * Called when a file descriptor receives events.
+         * <p>
+         * The default implementation does nothing and returns 0 to unregister the callback.
+         * </p>
+         *
+         * @param fd The file descriptor.
+         * @param events The set of events that occurred: a combination of the
+         * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+         * @return The new set of events to watch, or 0 to unregister the callback.
+         *
+         * @see #EVENT_INPUT
+         * @see #EVENT_OUTPUT
+         * @see #EVENT_ERROR
+         */
+        public @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events) {
+            return 0;
+        }
+    }
+
+    private static final class FileDescriptorRecord {
+        public final FileDescriptor mDescriptor;
+        public int mEvents;
+        public FileDescriptorCallback mCallback;
+        public int mSeq;
+
+        public FileDescriptorRecord(FileDescriptor descriptor,
+                int events, FileDescriptorCallback callback) {
+            mDescriptor = descriptor;
+            mEvents = events;
+            mCallback = callback;
+        }
+    }
 }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 4e8ec890..ba1699e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -19,11 +19,13 @@
 import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.SEEK_SET;
 import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SOCK_SEQPACKET;
 import static android.system.OsConstants.S_ISLNK;
 import static android.system.OsConstants.S_ISREG;
 
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
+import android.os.MessageQueue.FileDescriptorCallback;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -31,7 +33,6 @@
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
-
 import libcore.io.IoUtils;
 import libcore.io.Memory;
 
@@ -220,8 +221,8 @@
      *             be opened with the requested mode.
      * @see #parseMode(String)
      */
-    public static ParcelFileDescriptor open(
-            File file, int mode, Handler handler, OnCloseListener listener) throws IOException {
+    public static ParcelFileDescriptor open(File file, int mode, Handler handler,
+            final OnCloseListener listener) throws IOException {
         if (handler == null) {
             throw new IllegalArgumentException("Handler must not be null");
         }
@@ -235,10 +236,25 @@
         final FileDescriptor[] comm = createCommSocketPair();
         final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]);
 
-        // Kick off thread to watch for status updates
-        IoUtils.setBlocking(comm[1], true);
-        final ListenerBridge bridge = new ListenerBridge(comm[1], handler.getLooper(), listener);
-        bridge.start();
+        handler.getLooper().getQueue().registerFileDescriptorCallback(comm[1],
+                FileDescriptorCallback.EVENT_INPUT, new FileDescriptorCallback() {
+            @Override
+            public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+                Status status = null;
+                if ((events & FileDescriptorCallback.EVENT_INPUT) != 0) {
+                    final byte[] buf = new byte[MAX_STATUS];
+                    status = readCommStatus(fd, buf);
+                } else if ((events & FileDescriptorCallback.EVENT_ERROR) != 0) {
+                    status = new Status(Status.DEAD);
+                }
+                if (status != null) {
+                    IoUtils.closeQuietly(fd);
+                    listener.onClose(status.asIOException());
+                    return 0; // unregister the callback
+                }
+                return EVENT_INPUT;
+            }
+        });
 
         return pfd;
     }
@@ -446,9 +462,12 @@
 
     private static FileDescriptor[] createCommSocketPair() throws IOException {
         try {
+            // Use SOCK_SEQPACKET so that we have a guarantee that the status
+            // is written and read atomically as one unit and is not split
+            // across multiple IO operations.
             final FileDescriptor comm1 = new FileDescriptor();
             final FileDescriptor comm2 = new FileDescriptor();
-            Os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2);
+            Os.socketpair(AF_UNIX, SOCK_SEQPACKET, 0, comm1, comm2);
             IoUtils.setBlocking(comm1, false);
             IoUtils.setBlocking(comm2, false);
             return new FileDescriptor[] { comm1, comm2 };
@@ -709,6 +728,7 @@
                     writePtr += len;
                 }
 
+                // Must write the entire status as a single operation.
                 Os.write(mCommFd, buf, 0, writePtr);
             } catch (ErrnoException e) {
                 // Reporting status is best-effort
@@ -726,6 +746,7 @@
 
     private static Status readCommStatus(FileDescriptor comm, byte[] buf) {
         try {
+            // Must read the entire status as a single operation.
             final int n = Os.read(comm, buf, 0, buf.length);
             if (n == 0) {
                 // EOF means they're dead
@@ -1014,39 +1035,10 @@
                     return new IOException("Unknown status: " + status);
             }
         }
-    }
-
-    /**
-     * Bridge to watch for remote status, and deliver to listener. Currently
-     * requires that communication socket is <em>blocking</em>.
-     */
-    private static final class ListenerBridge extends Thread {
-        // TODO: switch to using Looper to avoid burning a thread
-
-        private FileDescriptor mCommFd;
-        private final Handler mHandler;
-
-        public ListenerBridge(FileDescriptor comm, Looper looper, final OnCloseListener listener) {
-            mCommFd = comm;
-            mHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    final Status s = (Status) msg.obj;
-                    listener.onClose(s != null ? s.asIOException() : null);
-                }
-            };
-        }
 
         @Override
-        public void run() {
-            try {
-                final byte[] buf = new byte[MAX_STATUS];
-                final Status status = readCommStatus(mCommFd, buf);
-                mHandler.obtainMessage(0, status).sendToTarget();
-            } finally {
-                IoUtils.closeQuietly(mCommFd);
-                mCommFd = null;
-            }
+        public String toString() {
+            return "{" + status + ": " + msg + "}";
         }
     }
 }
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index 731709b..6375668 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -98,8 +98,8 @@
     };
 
     public static final class FingerprintItem {
-        CharSequence name;
-        int id;
+        public CharSequence name;
+        public int id;
         FingerprintItem(CharSequence name, int id) {
             this.name = name;
             this.id = id;
@@ -291,4 +291,23 @@
         }
         return false;
     }
+
+    /**
+     * Renames the given fingerprint template
+     * @param fpId the fingerprint id
+     * @param newName the new name
+     * @hide
+     */
+    public void rename(int fpId, String newName) {
+        // Renames the given fpId
+        if (mService != null) {
+            try {
+                mService.rename(fpId, newName);
+            } catch (RemoteException e) {
+                Log.v(TAG, "Remote exception in rename(): ", e);
+            }
+        } else {
+            Log.w(TAG, "rename(): Service not connected!");
+        }
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl
index a7d4090..9b4750b 100644
--- a/core/java/android/service/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/service/fingerprint/IFingerprintService.aidl
@@ -41,4 +41,7 @@
 
     // Determine if HAL is loaded and ready
     boolean isHardwareDetected();
+
+    // Rename the given fingerprint id
+    void rename(int fpId, String name);
 }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 967e80c..0d35f9c 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -540,7 +540,7 @@
                 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
                     int endPos = paraStart + breaks[breakIndex];
 
-                    boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
+                    boolean moreChars = (endPos < bufEnd);
 
                     v = out(source, here, endPos,
                             fmAscent, fmDescent, fmTop, fmBottom,
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 69b4c47..51fefe9 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -102,7 +102,6 @@
     private final float mLightRadius;
     private final int mAmbientShadowAlpha;
     private final int mSpotShadowAlpha;
-    private final float mDensity;
 
     private long mNativeProxy;
     private boolean mInitialized = false;
@@ -119,7 +118,6 @@
                 (int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
         mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
         a.recycle();
-        mDensity = context.getResources().getDisplayMetrics().density;
 
         long rootNodePtr = nCreateRootRenderNode();
         mRootNode = RenderNode.adopt(rootNodePtr);
@@ -128,10 +126,6 @@
 
         AtlasInitializer.sInstance.init(context, mNativeProxy);
 
-        // Setup timing
-        mChoreographer = Choreographer.getInstance();
-        nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
-
         loadSystemProperties();
     }
 
@@ -224,7 +218,7 @@
         mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
         nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
                 lightX, mLightY, mLightZ, mLightRadius,
-                mAmbientShadowAlpha, mSpotShadowAlpha, mDensity);
+                mAmbientShadowAlpha, mSpotShadowAlpha);
     }
 
     @Override
@@ -379,6 +373,7 @@
 
     @Override
     void setName(String name) {
+        nSetName(mNativeProxy, name);
     }
 
     @Override
@@ -487,15 +482,15 @@
     private static native long nCreateProxy(boolean translucent, long rootRenderNode);
     private static native void nDeleteProxy(long nativeProxy);
 
-    private static native void nSetFrameInterval(long nativeProxy, long frameIntervalNanos);
     private static native boolean nLoadSystemProperties(long nativeProxy);
+    private static native void nSetName(long nativeProxy, String name);
 
     private static native boolean nInitialize(long nativeProxy, Surface window);
     private static native void nUpdateSurface(long nativeProxy, Surface window);
     private static native boolean nPauseSurface(long nativeProxy, Surface window);
     private static native void nSetup(long nativeProxy, int width, int height,
             float lightX, float lightY, float lightZ, float lightRadius,
-            int ambientShadowAlpha, int spotShadowAlpha, float density);
+            int ambientShadowAlpha, int spotShadowAlpha);
     private static native void nSetOpaque(long nativeProxy, boolean opaque);
     private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
     private static native void nDestroy(long nativeProxy);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c6f5d8a..f970e88 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1056,7 +1056,7 @@
     void scheduleTraversals() {
         if (!mTraversalScheduled) {
             mTraversalScheduled = true;
-            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
+            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
             mChoreographer.postCallback(
                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
             if (!mUnbufferedInputDispatch) {
@@ -1070,7 +1070,7 @@
     void unscheduleTraversals() {
         if (mTraversalScheduled) {
             mTraversalScheduled = false;
-            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
             mChoreographer.removeCallbacks(
                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         }
@@ -1079,7 +1079,7 @@
     void doTraversal() {
         if (mTraversalScheduled) {
             mTraversalScheduled = false;
-            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
 
             if (mProfile) {
                 Debug.startMethodTracing("ViewAncestor");
diff --git a/core/java/android/webkit/WebMessage.java b/core/java/android/webkit/WebMessage.java
new file mode 100644
index 0000000..7683a40
--- /dev/null
+++ b/core/java/android/webkit/WebMessage.java
@@ -0,0 +1,62 @@
+/*
+ * 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.webkit;
+
+/**
+ * The Java representation of the HTML5 PostMessage event. See
+ * https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
+ * for definition of a MessageEvent in HTML5.
+ *
+ */
+public class WebMessage {
+
+    private String mData;
+    private WebMessagePort[] mPorts;
+
+    /**
+     * Creates a WebMessage.
+     * @param data  the data of the message.
+     */
+    public WebMessage(String data) {
+        mData = data;
+    }
+
+    /**
+     * Creates a WebMessage.
+     * @param data  the data of the message.
+     * @param ports  the ports that are sent with the message.
+     */
+    public WebMessage(String data, WebMessagePort[] ports) {
+        mData = data;
+        mPorts = ports;
+    }
+
+    /**
+     * Returns the data of the message.
+     */
+    public String getData() {
+        return mData;
+    }
+
+    /**
+     * Returns the ports that are sent with the message, or null if no port
+     * is sent.
+     */
+    public WebMessagePort[] getPorts() {
+        return mPorts;
+    }
+}
diff --git a/core/java/android/webkit/WebMessagePort.java b/core/java/android/webkit/WebMessagePort.java
new file mode 100644
index 0000000..eab27bd
--- /dev/null
+++ b/core/java/android/webkit/WebMessagePort.java
@@ -0,0 +1,86 @@
+/*
+ * 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.webkit;
+
+import android.os.Handler;
+
+/**
+ * The Java representation of the HTML5 Message Port. See
+ * https://html.spec.whatwg.org/multipage/comms.html#messageport
+ * for definition of MessagePort in HTML5.
+ *
+ * A Message port represents one endpoint of a Message Channel. In Android
+ * webview, there is no separate Message Channel object. When a message channel
+ * is created, both ports are tangled to each other and started, and then
+ * returned in a MessagePort array, see {@link WebView#createWebMessageChannel}
+ * for creating a message channel.
+ *
+ * When a message port is first created or received via transfer, it does not
+ * have a WebMessageCallback to receive web messages. The messages are queued until
+ * a WebMessageCallback is set.
+ */
+public abstract class WebMessagePort {
+
+    /**
+     * The listener for handling MessagePort events. The message callback
+     * methods are called on the main thread. If the embedder application
+     * wants to receive the messages on a different thread, it can do this
+     * by passing a Handler in
+     *  {@link WebMessagePort#setWebMessageCallback(WebMessageCallback, Handler)}.
+     * In the latter case, the application should be extra careful for thread safety
+     * since WebMessagePort methods should be called on main thread.
+     */
+    public static abstract class WebMessageCallback {
+        /**
+         * Message callback for receiving onMessage events.
+         *
+         * @param port  the WebMessagePort that the message is destined for
+         * @param message  the message from the entangled port.
+         */
+        public void onMessage(WebMessagePort port, WebMessage message) { }
+    }
+
+    /**
+     * Post a WebMessage to the entangled port.
+     *
+     * @param message  the message from Java to JS.
+     *
+     * @throws IllegalStateException If message port is already transferred or closed.
+     */
+    public abstract void postMessage(WebMessage message);
+
+    /**
+     * Close the message port and free any resources associated with it.
+     */
+    public abstract void close();
+
+    /**
+     * Sets a callback to receive message events on the main thread.
+     *
+     * @param callback  the message callback.
+     */
+    public abstract void setWebMessageCallback(WebMessageCallback callback);
+
+    /**
+     * Sets a callback to receive message events on the handler that is provided
+     * by the application.
+     *
+     * @param callback  the message callback.
+     * @param handler   the handler to receive the message messages.
+     */
+    public abstract void setWebMessageCallback(WebMessageCallback callback, Handler handler);
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 01a506c..67ad642 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -27,6 +27,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.http.SslCertificate;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Looper;
@@ -1825,6 +1826,37 @@
     }
 
     /**
+     * Creates a message channel to communicate with JS and returns the message
+     * ports that represent the endpoints of this message channel. The HTML5 message
+     * channel functionality is described here:
+     * https://html.spec.whatwg.org/multipage/comms.html#messagechannel
+     *
+     * The returned message channels are entangled and already in started state.
+     *
+     * @return the two message ports that form the message channel.
+     */
+    public WebMessagePort[] createWebMessageChannel() {
+        checkThread();
+        if (TRACE) Log.d(LOGTAG, "createWebMessageChannel");
+        return mProvider.createWebMessageChannel();
+    }
+
+    /**
+     * Post a message to main frame. The embedded application can restrict the
+     * messages to a certain target origin. See
+     *    https://html.spec.whatwg.org/multipage/comms.html#posting-messages
+     * for how target origin can be used.
+     *
+     * @param message the WebMessage
+     * @param targetOrigin the target origin.
+     */
+    public void postMessageToMainFrame(WebMessage message, Uri targetOrigin) {
+        checkThread();
+        if (TRACE) Log.d(LOGTAG, "postMessageToMainFrame. TargetOrigin=" + targetOrigin);
+        mProvider.postMessageToMainFrame(message, targetOrigin);
+    }
+
+    /**
      * Gets the WebSettings object used to control the settings for this
      * WebView.
      *
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 379a732..0cdb875 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.http.SslCertificate;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Message;
 import android.print.PrintDocumentAdapter;
@@ -227,6 +228,10 @@
 
     public void removeJavascriptInterface(String interfaceName);
 
+    public WebMessagePort[] createWebMessageChannel();
+
+    public void postMessageToMainFrame(WebMessage message, Uri targetOrigin);
+
     public WebSettings getSettings();
 
     public void setMapTrackballToArrowKeys(boolean setMap);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cc44577..81d9d56 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2969,6 +2969,12 @@
                                 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
             }
 
+            if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
+                menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
+                        setShowAsAction(
+                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+            }
+
             styledAttributes.recycle();
 
             if (mCustomSelectionActionModeCallback != null) {
@@ -3000,6 +3006,10 @@
                  mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
+            if (item.getItemId() == TextView.ID_REPLACE) {
+                onReplace();
+                return true;
+            }
             return mTextView.onTextContextMenuItem(item.getItemId());
         }
 
@@ -3028,6 +3038,13 @@
         }
     }
 
+    private void onReplace() {
+        int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
+        stopSelectionActionMode();
+        Selection.setSelection((Spannable) mTextView.getText(), middle);
+        showSuggestions();
+    }
+
     private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
         private static final int POPUP_TEXT_LAYOUT =
                 com.android.internal.R.layout.text_edit_action_popup_text;
@@ -3086,10 +3103,7 @@
                 mTextView.onTextContextMenuItem(TextView.ID_PASTE);
                 hide();
             } else if (view == mReplaceTextView) {
-                int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
-                stopSelectionActionMode();
-                Selection.setSelection((Spannable) mTextView.getText(), middle);
-                showSuggestions();
+                onReplace();
             }
         }
 
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 399f4c5..f676254 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -29,6 +29,8 @@
 import android.os.IBinder;
 import android.transition.Transition;
 import android.transition.Transition.EpicenterCallback;
+import android.transition.Transition.TransitionListener;
+import android.transition.Transition.TransitionListenerAdapter;
 import android.transition.TransitionInflater;
 import android.transition.TransitionManager;
 import android.transition.TransitionSet;
@@ -39,12 +41,13 @@
 import android.view.View;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.ViewTreeObserver.OnScrollChangedListener;
 import android.view.WindowManager;
 
 import java.lang.ref.WeakReference;
-import java.util.List;
 
 /**
  * <p>A popup window that can be used to display an arbitrary view. The popup
@@ -96,14 +99,12 @@
     private WindowManager mWindowManager;
 
     private boolean mIsShowing;
+    private boolean mIsTransitioningToDismiss;
     private boolean mIsDropdown;
 
     /** View that handles event dispatch and content transitions. */
     private PopupDecorView mDecorView;
 
-    /** View that holds the popup background. May be the content view. */
-    private View mBackgroundView;
-
     /** The contents of the popup. */
     private View mContentView;
 
@@ -1183,23 +1184,30 @@
                     + "calling setContentView() before attempting to show the popup.");
         }
 
-        // When a background is available, we embed the content view within
-        // another view that owns the background drawable.
-        if (mBackground != null) {
-            mBackgroundView = createBackgroundView(mContentView);
-            mBackgroundView.setBackground(mBackground);
-        } else {
-            mBackgroundView = mContentView;
+        // The old decor view may be transitioning out. Make sure it finishes
+        // and cleans up before we try to create another one.
+        if (mDecorView != null) {
+            mDecorView.cancelTransitions();
         }
 
-        mDecorView = createDecorView(mBackgroundView);
+        // When a background is available, we embed the content view within
+        // another view that owns the background drawable.
+        final View backgroundView;
+        if (mBackground != null) {
+            backgroundView = createBackgroundView(mContentView);
+            backgroundView.setBackground(mBackground);
+        } else {
+            backgroundView = mContentView;
+        }
+
+        mDecorView = createDecorView(backgroundView);
 
         // The background owner should be elevated so that it casts a shadow.
-        mBackgroundView.setElevation(mElevation);
+        backgroundView.setElevation(mElevation);
 
         // We may wrap that in another view, so we'll need to manually specify
         // the surface insets.
-        final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
+        final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
         p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
         p.hasManualSurfaceInsets = true;
 
@@ -1268,26 +1276,13 @@
             p.packageName = mContext.getPackageName();
         }
 
-        final View rootView = mContentView.getRootView();
-        rootView.setFitsSystemWindows(mLayoutInsetDecor);
+        final PopupDecorView decorView = mDecorView;
+        decorView.setFitsSystemWindows(mLayoutInsetDecor);
+        decorView.requestEnterTransition(mEnterTransition);
+
         setLayoutDirectionFromAnchor();
 
-        mWindowManager.addView(rootView, p);
-
-        // Postpone enter transition until the scene root has been laid out.
-        if (mEnterTransition != null) {
-            mEnterTransition.addTarget(mBackgroundView);
-            mEnterTransition.addListener(new Transition.TransitionListenerAdapter() {
-                @Override
-                public void onTransitionEnd(Transition transition) {
-                    transition.removeListener(this);
-                    transition.removeTarget(mBackgroundView);
-                }
-            });
-
-            mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(
-                    new PostLayoutTransitionListener(mDecorView, mEnterTransition));
-        }
+        mWindowManager.addView(decorView, p);
     }
 
     private void setLayoutDirectionFromAnchor() {
@@ -1591,35 +1586,38 @@
      * @see #showAsDropDown(android.view.View)
      */
     public void dismiss() {
-        if (!isShowing()) {
+        if (!isShowing() || mIsTransitioningToDismiss) {
             return;
         }
 
+        final PopupDecorView decorView = mDecorView;
+        final View contentView = mContentView;
+
+        final ViewGroup contentHolder;
+        final ViewParent contentParent = contentView.getParent();
+        if (contentParent instanceof ViewGroup) {
+            contentHolder = ((ViewGroup) contentParent);
+        } else {
+            contentHolder = null;
+        }
+
+        // Ensure any ongoing or pending transitions are canceled.
+        decorView.cancelTransitions();
+
         unregisterForScrollChanged();
 
         mIsShowing = false;
+        mIsTransitioningToDismiss = true;
 
-        if (mExitTransition != null) {
-            // Cache the content view, since it may change without notice.
-            final View contentView = mContentView;
-
-            mExitTransition.addTarget(mBackgroundView);
-            mExitTransition.addListener(new Transition.TransitionListenerAdapter() {
+        if (mExitTransition != null && decorView.isLaidOut()) {
+            decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
                 @Override
                 public void onTransitionEnd(Transition transition) {
-                    transition.removeListener(this);
-                    transition.removeTarget(mBackgroundView);
-
-                    dismissImmediate(contentView);
+                    dismissImmediate(decorView, contentHolder, contentView);
                 }
             });
-
-            TransitionManager.beginDelayedTransition(mDecorView, mExitTransition);
-
-            // Transition to invisible.
-            mBackgroundView.setVisibility(View.INVISIBLE);
         } else {
-            dismissImmediate(mContentView);
+            dismissImmediate(decorView, contentHolder, contentView);
         }
 
         if (mOnDismissListener != null) {
@@ -1631,24 +1629,22 @@
      * Removes the popup from the window manager and tears down the supporting
      * view hierarchy, if necessary.
      */
-    private void dismissImmediate(View contentView) {
-        if (mDecorView == null || mBackgroundView == null) {
-            throw new RuntimeException("Popup window already dismissed");
+    private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
+        // If this method gets called and the decor view doesn't have a parent,
+        // then it was either never added or was already removed. That should
+        // never happen, but it's worth checking to avoid potential crashes.
+        if (decorView.getParent() != null) {
+            mWindowManager.removeViewImmediate(decorView);
         }
 
-        try {
-            if (mDecorView.isAttachedToWindow()) {
-                mWindowManager.removeViewImmediate(mDecorView);
-            }
-        } finally {
-            mDecorView.removeView(mBackgroundView);
-            mDecorView = null;
-
-            if (mBackgroundView != contentView) {
-                ((ViewGroup) mBackgroundView).removeView(contentView);
-            }
-            mBackgroundView = null;
+        if (contentHolder != null) {
+            contentHolder.removeView(contentView);
         }
+
+        // This needs to stay until after all transitions have ended since we
+        // need the reference to cancel transitions in preparePopup().
+        mDecorView = null;
+        mIsTransitioningToDismiss = false;
     }
 
     /**
@@ -1909,47 +1905,9 @@
         mAnchoredGravity = gravity;
     }
 
-    /**
-     * Layout listener used to run a transition immediately after a view is
-     * laid out. Forces the view to transition from invisible to visible.
-     */
-    private static class PostLayoutTransitionListener implements
-            ViewTreeObserver.OnGlobalLayoutListener {
-        private final ViewGroup mSceneRoot;
-        private final Transition mTransition;
-
-        public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) {
-            mSceneRoot = sceneRoot;
-            mTransition = transition;
-        }
-
-        @Override
-        public void onGlobalLayout() {
-            final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver();
-            if (observer == null) {
-                // View has been detached.
-                return;
-            }
-
-            observer.removeOnGlobalLayoutListener(this);
-
-            // Set all targets to be initially invisible.
-            final List<View> targets = mTransition.getTargets();
-            final int N = targets.size();
-            for (int i = 0; i < N; i++) {
-                targets.get(i).setVisibility(View.INVISIBLE);
-            }
-
-            TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
-
-            // Transition targets to visible.
-            for (int i = 0; i < N; i++) {
-                targets.get(i).setVisibility(View.VISIBLE);
-            }
-        }
-    }
-
     private class PopupDecorView extends FrameLayout {
+        private TransitionListenerAdapter mPendingExitListener;
+
         public PopupDecorView(Context context) {
             super(context);
         }
@@ -2004,6 +1962,100 @@
                 return super.onTouchEvent(event);
             }
         }
+
+        /**
+         * Requests that an enter transition run after the next layout pass.
+         */
+        public void requestEnterTransition(Transition transition) {
+            final ViewTreeObserver observer = getViewTreeObserver();
+            if (observer != null && transition != null) {
+                final Transition enterTransition = transition.clone();
+
+                // Postpone the enter transition after the first layout pass.
+                observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        final ViewTreeObserver observer = getViewTreeObserver();
+                        if (observer != null) {
+                            observer.removeOnGlobalLayoutListener(this);
+                        }
+
+                        startEnterTransition(enterTransition);
+                    }
+                });
+            }
+        }
+
+        /**
+         * Starts the pending enter transition, if one is set.
+         */
+        private void startEnterTransition(Transition enterTransition) {
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                enterTransition.addTarget(child);
+                child.setVisibility(View.INVISIBLE);
+            }
+
+            TransitionManager.beginDelayedTransition(this, enterTransition);
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                child.setVisibility(View.VISIBLE);
+            }
+        }
+
+        /**
+         * Starts an exit transition immediately.
+         * <p>
+         * <strong>Note:</strong> The transition listener is guaranteed to have
+         * its {@code onTransitionEnd} method called even if the transition
+         * never starts; however, it may be called with a {@code null} argument.
+         */
+        public void startExitTransition(Transition transition, final TransitionListener listener) {
+            if (transition == null) {
+                return;
+            }
+
+            // The exit listener MUST be called for cleanup, even if the
+            // transition never starts or ends. Stash it for later.
+            mPendingExitListener = new TransitionListenerAdapter() {
+                @Override
+                public void onTransitionEnd(Transition transition) {
+                    listener.onTransitionEnd(transition);
+
+                    // The listener was called. Our job here is done.
+                    mPendingExitListener = null;
+                }
+            };
+
+            final Transition exitTransition = transition.clone();
+            exitTransition.addListener(mPendingExitListener);
+
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                exitTransition.addTarget(child);
+            }
+
+            TransitionManager.beginDelayedTransition(this, exitTransition);
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                child.setVisibility(View.INVISIBLE);
+            }
+        }
+
+        /**
+         * Cancels all pending or current transitions.
+         */
+        public void cancelTransitions() {
+            TransitionManager.endTransitions(this);
+
+            if (mPendingExitListener != null) {
+                mPendingExitListener.onTransitionEnd(null);
+            }
+        }
     }
 
     private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 3592687..e7031fe 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -727,14 +727,10 @@
                 }
             }
 
-            if (scheduleOtherSpellCheck && wordStart <= end) {
+            if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
                 // Update range span: start new spell check from last wordStart
                 setRangeSpan(editable, wordStart, end);
             } else {
-                if (DBG && scheduleOtherSpellCheck) {
-                    Log.w(TAG, "Trying to schedule spellcheck for invalid region, from "
-                            + wordStart + " to " + end);
-                }
                 removeRangeSpan(editable);
             }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index faf1c40..628833d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8818,6 +8818,7 @@
     static final int ID_COPY = android.R.id.copy;
     static final int ID_PASTE = android.R.id.paste;
     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
+    static final int ID_REPLACE = android.R.id.replaceText;
 
     /**
      * Called when a context menu option for the text view is selected.  Currently
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 2b20b38..7d45071 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -43,8 +43,6 @@
 public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
         ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
         View.OnAttachStateChangeListener, MenuPresenter {
-    private static final String TAG = "MenuPopupHelper";
-
     static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
 
     private final Context mContext;
@@ -132,7 +130,18 @@
         return mPopup;
     }
 
+    /**
+     * Attempts to show the popup anchored to the view specified by
+     * {@link #setAnchorView(View)}.
+     *
+     * @return {@code true} if the popup was shown or was already showing prior
+     *         to calling this method, {@code false} otherwise
+     */
     public boolean tryShow() {
+        if (isShowing()) {
+            return true;
+        }
+
         mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
         mPopup.setOnDismissListener(this);
         mPopup.setOnItemClickListener(this);
@@ -169,6 +178,7 @@
         }
     }
 
+    @Override
     public void onDismiss() {
         mPopup = null;
         mMenu.close();
@@ -190,6 +200,7 @@
         adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
     }
 
+    @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
             dismiss();
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index a10cfca..7a934bd 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -556,24 +556,33 @@
         return NULL;
     }
 
-    SkBitmap* bitmap = new SkBitmap;
+    std::unique_ptr<SkBitmap> bitmap(new SkBitmap);
 
-    bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes);
+    if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes)) {
+        return NULL;
+    }
 
     SkColorTable* ctable = NULL;
     if (colorType == kIndex_8_SkColorType) {
         int count = p->readInt32();
+        if (count < 0 || count > 256) {
+            // The data is corrupt, since SkColorTable enforces a value between 0 and 256,
+            // inclusive.
+            return NULL;
+        }
         if (count > 0) {
             size_t size = count * sizeof(SkPMColor);
             const SkPMColor* src = (const SkPMColor*)p->readInplace(size);
+            if (src == NULL) {
+                return NULL;
+            }
             ctable = new SkColorTable(src, count);
         }
     }
 
-    jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
+    jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable);
     if (NULL == buffer) {
         SkSafeUnref(ctable);
-        delete bitmap;
         return NULL;
     }
 
@@ -585,7 +594,6 @@
     android::status_t status = p->readBlob(size, &blob);
     if (status) {
         doThrowRE(env, "Could not read bitmap from parcel blob.");
-        delete bitmap;
         return NULL;
     }
 
@@ -595,8 +603,8 @@
 
     blob.release();
 
-    return GraphicsJNI::createBitmap(env, bitmap, buffer, getPremulBitmapCreateFlags(isMutable),
-            NULL, NULL, density);
+    return GraphicsJNI::createBitmap(env, bitmap.release(), buffer,
+            getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
 }
 
 static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 5d7877b..6e5e67c 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -29,22 +29,31 @@
 
 static struct {
     jfieldID mPtr;   // native object attached to the DVM MessageQueue
+    jmethodID dispatchEvents;
 } gMessageQueueClassInfo;
 
+// Must be kept in sync with the constants in Looper.FileDescriptorCallback
+static const int CALLBACK_EVENT_INPUT = 1 << 0;
+static const int CALLBACK_EVENT_OUTPUT = 1 << 1;
+static const int CALLBACK_EVENT_ERROR = 1 << 2;
 
-class NativeMessageQueue : public MessageQueue {
+
+class NativeMessageQueue : public MessageQueue, public LooperCallback {
 public:
     NativeMessageQueue();
     virtual ~NativeMessageQueue();
 
     virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);
 
-    void pollOnce(JNIEnv* env, int timeoutMillis);
-
+    void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis);
     void wake();
+    void setFileDescriptorEvents(int fd, int events);
+
+    virtual int handleEvent(int fd, int events, void* data);
 
 private:
-    bool mInCallback;
+    JNIEnv* mPollEnv;
+    jobject mPollObj;
     jthrowable mExceptionObj;
 };
 
@@ -66,10 +75,11 @@
     return false;
 }
 
-NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
+NativeMessageQueue::NativeMessageQueue() :
+        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
     mLooper = Looper::getForThread();
     if (mLooper == NULL) {
-        mLooper = new Looper(false);
+        mLooper = new Looper(true);
         Looper::setForThread(mLooper);
     }
 }
@@ -79,7 +89,7 @@
 
 void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) {
     if (exceptionObj) {
-        if (mInCallback) {
+        if (mPollEnv == env) {
             if (mExceptionObj) {
                 env->DeleteLocalRef(mExceptionObj);
             }
@@ -94,10 +104,13 @@
     }
 }
 
-void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
-    mInCallback = true;
+void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
+    mPollEnv = env;
+    mPollObj = pollObj;
     mLooper->pollOnce(timeoutMillis);
-    mInCallback = false;
+    mPollObj = NULL;
+    mPollEnv = NULL;
+
     if (mExceptionObj) {
         env->Throw(mExceptionObj);
         env->DeleteLocalRef(mExceptionObj);
@@ -109,6 +122,46 @@
     mLooper->wake();
 }
 
+void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) {
+    if (events) {
+        int looperEvents = 0;
+        if (events & CALLBACK_EVENT_INPUT) {
+            looperEvents |= Looper::EVENT_INPUT;
+        }
+        if (events & CALLBACK_EVENT_OUTPUT) {
+            looperEvents |= Looper::EVENT_OUTPUT;
+        }
+        mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
+                reinterpret_cast<void*>(events));
+    } else {
+        mLooper->removeFd(fd);
+    }
+}
+
+int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {
+    int events = 0;
+    if (looperEvents & Looper::EVENT_INPUT) {
+        events |= CALLBACK_EVENT_INPUT;
+    }
+    if (looperEvents & Looper::EVENT_OUTPUT) {
+        events |= CALLBACK_EVENT_OUTPUT;
+    }
+    if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) {
+        events |= CALLBACK_EVENT_ERROR;
+    }
+    int oldWatchedEvents = reinterpret_cast<intptr_t>(data);
+    int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj,
+            gMessageQueueClassInfo.dispatchEvents, fd, events);
+    if (!newWatchedEvents) {
+        return 0; // unregister the fd
+    }
+    if (newWatchedEvents != oldWatchedEvents) {
+        setFileDescriptorEvents(fd, newWatchedEvents);
+    }
+    return 1;
+}
+
+
 // ----------------------------------------------------------------------------
 
 sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
@@ -132,20 +185,26 @@
     nativeMessageQueue->decStrong(env);
 }
 
-static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
+static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
         jlong ptr, jint timeoutMillis) {
     NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
-    nativeMessageQueue->pollOnce(env, timeoutMillis);
+    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
 }
 
 static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
     NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
-    return nativeMessageQueue->wake();
+    nativeMessageQueue->wake();
 }
 
-static jboolean android_os_MessageQueue_nativeIsIdling(JNIEnv* env, jclass clazz, jlong ptr) {
+static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass clazz, jlong ptr) {
     NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
-    return nativeMessageQueue->getLooper()->isIdling();
+    return nativeMessageQueue->getLooper()->isPolling();
+}
+
+static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz,
+        jlong ptr, jint fd, jint events) {
+    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
+    nativeMessageQueue->setFileDescriptorEvents(fd, events);
 }
 
 // ----------------------------------------------------------------------------
@@ -156,7 +215,9 @@
     { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
     { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
     { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
-    { "nativeIsIdling", "(J)Z", (void*)android_os_MessageQueue_nativeIsIdling }
+    { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
+    { "nativeSetFileDescriptorEvents", "(JII)V",
+            (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
 };
 
 int register_android_os_MessageQueue(JNIEnv* env) {
@@ -164,8 +225,9 @@
                                    NELEM(gMessageQueueMethods));
 
     jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
-
     gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
+    gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
+            "dispatchEvents", "(II)I");
 
     return res;
 }
diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h
index 49d2aa0..1e49b5f 100644
--- a/core/jni/android_os_MessageQueue.h
+++ b/core/jni/android_os_MessageQueue.h
@@ -22,7 +22,7 @@
 
 namespace android {
 
-class MessageQueue : public RefBase {
+class MessageQueue : public virtual RefBase {
 public:
     /* Gets the message queue's looper. */
     inline sp<Looper> getLooper() const {
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 39064ed..fff8604 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -489,7 +489,7 @@
     proxy->initialize(surface);
     // Shadows can't be used via this interface, so just set the light source
     // to all 0s. (and width & height are unused, TODO remove them)
-    proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0, 1.0f);
+    proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
     return (jlong) proxy;
 }
 
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index ad93301..3d9a9ed 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -239,18 +239,20 @@
     delete proxy;
 }
 
-static void android_view_ThreadedRenderer_setFrameInterval(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jlong frameIntervalNanos) {
-    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    proxy->setFrameInterval(frameIntervalNanos);
-}
-
 static jboolean android_view_ThreadedRenderer_loadSystemProperties(JNIEnv* env, jobject clazz,
         jlong proxyPtr) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
     return proxy->loadSystemProperties();
 }
 
+static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jstring jname) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    const char* name = env->GetStringUTFChars(jname, NULL);
+    proxy->setName(name);
+    env->ReleaseStringUTFChars(jname, name);
+}
+
 static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject jsurface) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -284,7 +286,7 @@
         jint ambientShadowAlpha, jint spotShadowAlpha, jfloat density) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
     proxy->setup(width, height, (Vector3){lightX, lightY, lightZ}, lightRadius,
-            ambientShadowAlpha, spotShadowAlpha, density);
+            ambientShadowAlpha, spotShadowAlpha);
 }
 
 static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
@@ -424,12 +426,12 @@
     { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
     { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
     { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
-    { "nSetFrameInterval", "(JJ)V", (void*) android_view_ThreadedRenderer_setFrameInterval },
     { "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
+    { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
     { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
     { "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
     { "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
-    { "nSetup", "(JIIFFFFIIF)V", (void*) android_view_ThreadedRenderer_setup },
+    { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup },
     { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
     { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroy", "(J)V", (void*) android_view_ThreadedRenderer_destroy },
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 30ce271..15797dd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -31,8 +31,10 @@
         <attr name="colorForeground" format="color" />
         <!-- Default color of foreground imagery on an inverted background. -->
         <attr name="colorForegroundInverse" format="color" />
-        <!-- Color that matches (as closely as possible) the window background. -->
+        <!-- Default color of background imagery, ex. full-screen windows. -->
         <attr name="colorBackground" format="color" />
+        <!-- Default color of background imagery for floating components, ex. dialogs, popups, and cards. -->
+        <attr name="colorBackgroundFloating" format="color" />
         <!-- This is a hint for a solid color that can be used for caching
              rendered views.  This should be the color of the background when
              there is a solid background color; it should be null when the
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index d657bad..6108b27 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -92,4 +92,5 @@
   <item type="id" name="pasteAsPlainText" />
   <item type="id" name="undo" />
   <item type="id" name="redo" />
+  <item type="id" name="replaceText" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 16f0676..e507b3d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2642,15 +2642,13 @@
   <public type="style" name="Theme.Material.Light.LightStatusBar" />
   <public type="style" name="ThemeOverlay.Material.Dialog" />
 
-  <!-- Context menu ID for the "Paste as plain text" menu item to to copy the current contents
-          of the clipboard into the text view without formatting. -->
   <public type="id" name="pasteAsPlainText" />
-  <!-- Context menu ID for the "Undo" menu item to undo the last text edit operation. -->
   <public type="id" name="undo" />
-  <!-- Context menu ID for the "Redo" menu item to redo the last text edit operation. -->
   <public type="id" name="redo" />
+  <public type="id" name="replaceText" />
 
-  <!-- TextView attribute to control undo behavior. -->
   <public type="attr" name="allowUndo" />
 
+  <public type="attr" name="colorBackgroundFloating" />
+
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 4ba6c0b..9e87b4d 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -45,6 +45,7 @@
         <item name="colorForeground">@color/bright_foreground_dark</item>
         <item name="colorForegroundInverse">@color/bright_foreground_dark_inverse</item>
         <item name="colorBackground">@color/background_dark</item>
+        <item name="colorBackgroundFloating">?attr/colorBackground</item>
         <item name="colorBackgroundCacheHint">?attr/colorBackground</item>
 
         <item name="colorPressedHighlight">@color/legacy_pressed_highlight</item>
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index c30b3d5..701d0ef 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -65,6 +65,7 @@
         <item name="colorForeground">@color/bright_foreground_holo_dark</item>
         <item name="colorForegroundInverse">@color/bright_foreground_inverse_holo_dark</item>
         <item name="colorBackground">@color/background_holo_dark</item>
+        <item name="colorBackgroundFloating">@color/background_holo_dark</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_holo_dark</item>
         <item name="disabledAlpha">0.5</item>
         <item name="backgroundDimAmount">0.6</item>
@@ -404,6 +405,7 @@
         <item name="colorForeground">@color/bright_foreground_holo_light</item>
         <item name="colorForegroundInverse">@color/bright_foreground_inverse_holo_light</item>
         <item name="colorBackground">@color/background_holo_light</item>
+        <item name="colorBackgroundFloating">@color/background_holo_light</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_holo_light</item>
         <item name="disabledAlpha">0.5</item>
         <item name="backgroundDimAmount">0.6</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index a610d07..38cfecd 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -45,6 +45,7 @@
         <item name="colorForeground">@color/foreground_material_dark</item>
         <item name="colorForegroundInverse">@color/foreground_material_light</item>
         <item name="colorBackground">@color/background_material_dark</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
         <item name="disabledAlpha">@dimen/disabled_alpha_material_dark</item>
         <item name="backgroundDimAmount">0.6</item>
@@ -398,6 +399,7 @@
         <item name="colorForeground">@color/foreground_material_light</item>
         <item name="colorForegroundInverse">@color/foreground_material_dark</item>
         <item name="colorBackground">@color/background_material_light</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
         <item name="disabledAlpha">@dimen/disabled_alpha_material_light</item>
         <item name="backgroundDimAmount">0.6</item>
@@ -770,6 +772,7 @@
         <item name="colorForeground">@color/foreground_material_light</item>
         <item name="colorForegroundInverse">@color/foreground_material_dark</item>
         <item name="colorBackground">@color/background_material_light</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
 
         <item name="textColorPrimary">@color/primary_text_material_light</item>
@@ -806,6 +809,7 @@
         <item name="colorForeground">@color/foreground_material_dark</item>
         <item name="colorForegroundInverse">@color/foreground_material_light</item>
         <item name="colorBackground">@color/background_material_dark</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
 
         <item name="textColorPrimary">@color/primary_text_material_dark</item>
@@ -852,7 +856,6 @@
 
     <!-- Theme overlay that overrides window properties to display as a dialog. -->
     <style name="ThemeOverlay.Material.Dialog">
-        <item name="colorBackground">@color/background_floating_material_light</item>
         <item name="colorBackgroundCacheHint">@null</item>
 
         <item name="windowFrame">@null</item>
@@ -1044,7 +1047,7 @@
     <eat-comment />
 
     <style name="Theme.Material.BaseDialog">
-        <item name="colorBackground">@color/background_floating_material_dark</item>
+        <item name="colorBackground">?attr/colorBackgroundFloating</item>
 
         <item name="windowFrame">@null</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
@@ -1155,7 +1158,7 @@
     <!-- Light material dialog themes -->
 
     <style name="Theme.Material.Light.BaseDialog">
-        <item name="colorBackground">@color/background_floating_material_light</item>
+        <item name="colorBackground">?attr/colorBackgroundFloating</item>
 
         <item name="windowFrame">@null</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 46b0945..7df61f27 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -97,8 +97,8 @@
     int64_t totalDuration =
             frame[FrameInfoIndex::kFrameCompleted] - frame[FrameInfoIndex::kIntendedVsync];
     uint32_t framebucket = std::min(
-            static_cast<typeof sizeof(mFrameCounts)>(ns2ms(totalDuration)),
-            sizeof(mFrameCounts) / sizeof(mFrameCounts[0]));
+            static_cast<typeof mFrameCounts.size()>(ns2ms(totalDuration)),
+            mFrameCounts.size());
     // Keep the fast path as fast as possible.
     if (CC_LIKELY(totalDuration < mFrameInterval)) {
         mFrameCounts[framebucket]++;
@@ -137,8 +137,8 @@
 }
 
 void JankTracker::reset() {
-    memset(mBuckets, 0, sizeof(mBuckets));
-    memset(mFrameCounts, 0, sizeof(mFrameCounts));
+    mBuckets.fill({0});
+    mFrameCounts.fill(0);
     mTotalFrameCount = 0;
     mJankFrameCount = 0;
 }
@@ -146,7 +146,7 @@
 uint32_t JankTracker::findPercentile(int percentile) {
     int pos = percentile * mTotalFrameCount / 100;
     int remaining = mTotalFrameCount - pos;
-    for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) {
+    for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
         remaining -= mFrameCounts[i];
         if (remaining <= 0) {
             return i;
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 3d4929b..ae339ec 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -20,6 +20,7 @@
 #include "renderthread/TimeLord.h"
 #include "utils/RingBuffer.h"
 
+#include <array>
 #include <memory>
 
 namespace android {
@@ -56,9 +57,9 @@
 private:
     uint32_t findPercentile(int p);
 
-    JankBucket mBuckets[NUM_BUCKETS];
-    int64_t mThresholds[NUM_BUCKETS];
-    uint32_t mFrameCounts[128];
+    std::array<JankBucket, NUM_BUCKETS> mBuckets;
+    std::array<int64_t, NUM_BUCKETS> mThresholds;
+    std::array<uint32_t, 128> mFrameCounts;
 
     int64_t mFrameInterval;
     uint32_t mTotalFrameCount;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index fcf6eb2..9456073 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -51,6 +51,7 @@
         , mRootRenderNode(rootRenderNode)
         , mCurrentFrameInfo(nullptr) {
     mRenderThread.renderState().registerCanvasContext(this);
+    mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
 }
 
 CanvasContext::~CanvasContext() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9a60dc7..c3904c2 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -33,6 +33,7 @@
 #include <utils/Vector.h>
 
 #include <set>
+#include <string>
 
 namespace android {
 namespace uirenderer {
@@ -106,6 +107,9 @@
     void dumpFrames(int fd);
     void resetFrameStats();
 
+    void setName(const std::string&& name) { mName = name; }
+    const std::string& name() { return mName; }
+
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -139,6 +143,7 @@
     FrameInfo* mCurrentFrameInfo;
     // Ring buffer large enough for 1 second worth of frames
     RingBuffer<FrameInfo, 60> mFrames;
+    std::string mName;
 
     std::set<RenderNode*> mPrefetechedLayers;
 };
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index f48ee41..35391b2 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -34,7 +34,6 @@
 DrawFrameTask::DrawFrameTask()
         : mRenderThread(nullptr)
         , mContext(nullptr)
-        , mDensity(1.0f) // safe enough default
         , mSyncResult(kSync_OK) {
 }
 
@@ -84,7 +83,6 @@
 void DrawFrameTask::run() {
     ATRACE_NAME("DrawFrame");
 
-    mContext->profiler().setDensity(mDensity);
     mContext->profiler().startFrame();
 
     bool canUnblockUiThread;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 0e56bea..8039643 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -62,7 +62,6 @@
     void pushLayerUpdate(DeferredLayerUpdater* layer);
     void removeLayerUpdate(DeferredLayerUpdater* layer);
 
-    void setDensity(float density) { mDensity = density; }
     int drawFrame();
 
     int64_t* frameInfo() { return mFrameInfo; }
@@ -83,7 +82,6 @@
     /*********************************************
      *  Single frame data
      *********************************************/
-    float mDensity;
     std::vector< sp<DeferredLayerUpdater> > mLayers;
 
     int mSyncResult;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0fa2f23..ea4216c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -97,18 +97,6 @@
     }
 }
 
-CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) {
-    args->thread->setFrameInterval(args->frameIntervalNanos);
-    return nullptr;
-}
-
-void RenderProxy::setFrameInterval(nsecs_t frameIntervalNanos) {
-    SETUP_TASK(setFrameInterval);
-    args->thread = &mRenderThread;
-    args->frameIntervalNanos = frameIntervalNanos;
-    post(task);
-}
-
 CREATE_BRIDGE2(setSwapBehavior, CanvasContext* context, SwapBehavior swapBehavior) {
     args->context->setSwapBehavior(args->swapBehavior);
     return nullptr;
@@ -138,6 +126,18 @@
     return (bool) postAndWait(task);
 }
 
+CREATE_BRIDGE2(setName, CanvasContext* context, const char* name) {
+    args->context->setName(std::string(args->name));
+    return nullptr;
+}
+
+void RenderProxy::setName(const char* name) {
+    SETUP_TASK(setName);
+    args->context = mContext;
+    args->name = name;
+    post(task);
+}
+
 CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
     return (void*) args->context->initialize(args->window);
 }
@@ -181,8 +181,7 @@
 }
 
 void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
-        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density) {
-    mDrawFrameTask.setDensity(density);
+        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
     SETUP_TASK(setup);
     args->context = mContext;
     args->width = width;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 19e73e5..43cbe07 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -62,16 +62,16 @@
     ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
     ANDROID_API virtual ~RenderProxy();
 
-    ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos);
     // Won't take effect until next EGLSurface creation
     ANDROID_API void setSwapBehavior(SwapBehavior swapBehavior);
     ANDROID_API bool loadSystemProperties();
+    ANDROID_API void setName(const char* name);
 
     ANDROID_API bool initialize(const sp<ANativeWindow>& window);
     ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
     ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
     ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
-            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density);
+            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     ANDROID_API void setOpaque(bool opaque);
     ANDROID_API int64_t* frameInfo();
     ANDROID_API int syncAndDrawFrame();
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 2a8baa7..3ac2976 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -22,6 +22,8 @@
 #include "RenderProxy.h"
 
 #include <gui/DisplayEventReceiver.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
 #include <sys/resource.h>
 #include <utils/Log.h>
 
@@ -151,11 +153,6 @@
     LOG_ALWAYS_FATAL("Can't destroy the render thread");
 }
 
-void RenderThread::setFrameInterval(nsecs_t frameInterval) {
-    mTimeLord.setFrameInterval(frameInterval);
-    mJankTracker->setFrameInterval(frameInterval);
-}
-
 void RenderThread::initializeDisplayEventReceiver() {
     LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
     mDisplayEventReceiver = new DisplayEventReceiver();
@@ -169,10 +166,16 @@
 }
 
 void RenderThread::initThreadLocals() {
+    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
+            ISurfaceComposer::eDisplayIdMain));
+    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo);
+    LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
+    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps);
+    mTimeLord.setFrameInterval(frameIntervalNanos);
     initializeDisplayEventReceiver();
     mEglManager = new EglManager(*this);
     mRenderState = new RenderState(*this);
-    mJankTracker = new JankTracker(mTimeLord.frameIntervalNanos());
+    mJankTracker = new JankTracker(frameIntervalNanos);
 }
 
 int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index f169424..8096099 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -23,6 +23,7 @@
 #include "TimeLord.h"
 
 #include <cutils/compiler.h>
+#include <ui/DisplayInfo.h>
 #include <utils/Looper.h>
 #include <utils/Mutex.h>
 #include <utils/Singleton.h>
@@ -86,13 +87,13 @@
     // the next vsync. If it is not currently registered this does nothing.
     void pushBackFrameCallback(IFrameCallback* callback);
 
-    void setFrameInterval(nsecs_t frameInterval);
-
     TimeLord& timeLord() { return mTimeLord; }
     RenderState& renderState() { return *mRenderState; }
     EglManager& eglManager() { return *mEglManager; }
     JankTracker& jankTracker() { return *mJankTracker; }
 
+    const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
+
 protected:
     virtual bool threadLoop() override;
 
@@ -122,6 +123,8 @@
     nsecs_t mNextWakeup;
     TaskQueue mQueue;
 
+    DisplayInfo mDisplayInfo;
+
     DisplayEventReceiver* mDisplayEventReceiver;
     bool mVsyncRequested;
     std::set<IFrameCallback*> mFrameCallbacks;
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 0d1e63e..805989b 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -85,7 +85,7 @@
         proxy->initialize(surface);
         float lightX = width / 2.0;
         proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
-                dp(800.0f), 255 * 0.075, 255 * 0.15, gDisplay.density);
+                dp(800.0f), 255 * 0.075, 255 * 0.15);
 
         android::uirenderer::Rect DUMMY;
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 43c394a..db67fbf 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1498,7 +1498,7 @@
     public void setMode(int mode) {
         IAudioService service = getService();
         try {
-            service.setMode(mode, mICallBack);
+            service.setMode(mode, mICallBack, mContext.getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setMode", e);
         }
@@ -3037,7 +3037,8 @@
     public void setWiredDeviceConnectionState(int type, int state, String address, String name) {
         IAudioService service = getService();
         try {
-            service.setWiredDeviceConnectionState(type, state, address, name);
+            service.setWiredDeviceConnectionState(type, state, address, name,
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setWiredDeviceConnectionState "+e);
         }
@@ -3181,7 +3182,7 @@
      */
     public void disableSafeMediaVolume() {
         try {
-            getService().disableSafeMediaVolume();
+            getService().disableSafeMediaVolume(mContext.getOpPackageName());
         } catch (RemoteException e) {
             Log.w(TAG, "Error disabling safe media volume", e);
         }
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 059d940..d456b7e 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -27,8 +27,7 @@
 public abstract class AudioManagerInternal {
 
     public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction,
-            int flags,
-            String callingPackage, int uid);
+            int flags, String callingPackage, int uid);
 
     public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags,
             String callingPackage, int uid);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 20f7d29..2e8e017 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -40,7 +40,7 @@
 interface IAudioService {
 
     void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
-            String callingPackage);
+            String callingPackage, String caller);
 
     void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
 
@@ -80,7 +80,7 @@
 
     boolean shouldVibrate(int vibrateType);
 
-    void setMode(int mode, IBinder cb);
+    void setMode(int mode, IBinder cb, String callingPackage);
 
     int getMode();
 
@@ -181,7 +181,9 @@
     IRingtonePlayer getRingtonePlayer();
     int getUiSoundsStreamType();
 
-    void setWiredDeviceConnectionState(int type, int state, String address, String name);
+    void setWiredDeviceConnectionState(int type, int state, String address, String name,
+            String caller);
+
     int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
 
     AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
@@ -196,7 +198,7 @@
 
     boolean isStreamAffectedByMute(int streamType);
 
-    void disableSafeMediaVolume();
+    void disableSafeMediaVolume(String callingPackage);
 
     int setHdmiSystemAudioSupported(boolean on);
 
diff --git a/media/java/android/media/midi/IMidiDeviceListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
index 17d9bfd..31c66e3 100644
--- a/media/java/android/media/midi/IMidiDeviceListener.aidl
+++ b/media/java/android/media/midi/IMidiDeviceListener.aidl
@@ -17,10 +17,12 @@
 package android.media.midi;
 
 import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
 
 /** @hide */
 oneway interface IMidiDeviceListener
 {
     void onDeviceAdded(in MidiDeviceInfo device);
     void onDeviceRemoved(in MidiDeviceInfo device);
+    void onDeviceStatusChanged(in MidiDeviceStatus status);
 }
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 3331aae..642078a 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -24,4 +24,7 @@
     ParcelFileDescriptor openInputPort(IBinder token, int portNumber);
     ParcelFileDescriptor openOutputPort(IBinder token, int portNumber);
     void closePort(IBinder token);
+
+    // connects the input port pfd to the specified output port
+    void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
 }
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index 617b03e..a3b40d6 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -19,6 +19,7 @@
 import android.media.midi.IMidiDeviceListener;
 import android.media.midi.IMidiDeviceServer;
 import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
 import android.os.Bundle;
 import android.os.IBinder;
 
@@ -44,4 +45,11 @@
     // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
     // manifest's meta-data
     MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
+
+    // used for client's to retrieve a device's MidiDeviceStatus
+    MidiDeviceStatus getDeviceStatus(in MidiDeviceInfo deviceInfo);
+
+    // used by MIDI devices to report their status
+    // the token is used by MidiService for death notification
+    void setDeviceStatus(IBinder token, in MidiDeviceStatus status);
 }
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index af0737d..569f7c6 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -26,6 +26,8 @@
 
 import dalvik.system.CloseGuard;
 
+import libcore.io.IoUtils;
+
 import java.io.Closeable;
 import java.io.IOException;
 
@@ -44,8 +46,29 @@
     private Context mContext;
     private ServiceConnection mServiceConnection;
 
+
     private final CloseGuard mGuard = CloseGuard.get();
 
+    public class MidiConnection implements Closeable {
+        private final IBinder mToken;
+        private final MidiInputPort mInputPort;
+
+        MidiConnection(IBinder token, MidiInputPort inputPort) {
+            mToken = token;
+            mInputPort = inputPort;
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                mDeviceServer.closePort(mToken);
+                IoUtils.closeQuietly(mInputPort);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in MidiConnection.close");
+            }
+        }
+    }
+
     /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
         this(deviceInfo, server, null, null);
     }
@@ -108,6 +131,36 @@
         }
     }
 
+    /**
+     * Connects the supplied {@link MidiInputPort} to the output port of this device
+     * with the specified port number. Once the connection is made, the MidiInput port instance
+     * can no longer receive data via its {@link MidiReciever.receive} method.
+     * This method returns a {@link #MidiConnection} object, which can be used to close the connection
+     * @param inputPort the inputPort to connect
+     * @param outputPortNumber the port number of the output port to connect inputPort to.
+     * @return {@link #MidiConnection} object if the connection is successful, or null in case of failure
+     */
+    public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) {
+        if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) {
+            throw new IllegalArgumentException("outputPortNumber out of range");
+        }
+
+        ParcelFileDescriptor pfd = inputPort.claimFileDescriptor();
+        if (pfd == null) {
+            return null;
+        }
+         try {
+            IBinder token = new Binder();
+            mDeviceServer.connectPorts(token, pfd, outputPortNumber);
+            // close our copy of the file descriptor
+            IoUtils.closeQuietly(pfd);
+            return new MidiConnection(token, inputPort);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in connectPorts");
+            return null;
+        }
+    }
+
     @Override
     public void close() throws IOException {
         synchronized (mGuard) {
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 3b4b6f0..b3c0e3a 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -16,8 +16,8 @@
 
 package android.media.midi;
 
-import android.os.IBinder;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
@@ -61,7 +61,25 @@
     private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
             = new CopyOnWriteArrayList<MidiInputPort>();
 
+
+    // for reporting device status
+    private final IBinder mDeviceStatusToken = new Binder();
+    private final boolean[] mInputPortBusy;
+    private final int[] mOutputPortOpenCount;
+
     private final CloseGuard mGuard = CloseGuard.get();
+    private boolean mIsClosed;
+
+    private final Callback mCallback;
+
+    public interface Callback {
+        /**
+         * Called to notify when an our device status has changed
+         * @param server the {@link MidiDeviceServer} that changed
+         * @param status the {@link MidiDeviceStatus} for the device
+         */
+        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
+    }
 
     abstract private class PortClient implements IBinder.DeathRecipient {
         final IBinder mToken;
@@ -96,7 +114,10 @@
         void close() {
             mToken.unlinkToDeath(this, 0);
             synchronized (mInputPortOutputPorts) {
-                mInputPortOutputPorts[mOutputPort.getPortNumber()] = null;
+                int portNumber = mOutputPort.getPortNumber();
+                mInputPortOutputPorts[portNumber] = null;
+                mInputPortBusy[portNumber] = false;
+                updateDeviceStatus();
             }
             IoUtils.closeQuietly(mOutputPort);
         }
@@ -113,7 +134,15 @@
         @Override
         void close() {
             mToken.unlinkToDeath(this, 0);
-            mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort);
+            int portNumber = mInputPort.getPortNumber();
+            MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+            synchronized (dispatcher) {
+                dispatcher.getSender().disconnect(mInputPort);
+                int openCount = dispatcher.getReceiverCount();
+                mOutputPortOpenCount[portNumber] = openCount;
+                updateDeviceStatus();
+           }
+
             mInputPorts.remove(mInputPort);
             IoUtils.closeQuietly(mInputPort);
         }
@@ -153,6 +182,8 @@
                     synchronized (mPortClients) {
                         mPortClients.put(token, client);
                     }
+                    mInputPortBusy[portNumber] = true;
+                    updateDeviceStatus();
                     return pair[1];
                 } catch (IOException e) {
                     Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
@@ -178,7 +209,14 @@
                 ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
                                                     OsConstants.SOCK_SEQPACKET);
                 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
-                mOutputPortDispatchers[portNumber].getSender().connect(inputPort);
+                MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+                synchronized (dispatcher) {
+                    dispatcher.getSender().connect(inputPort);
+                    int openCount = dispatcher.getReceiverCount();
+                    mOutputPortOpenCount[portNumber] = openCount;
+                    updateDeviceStatus();
+                }
+
                 mInputPorts.add(inputPort);
                 OutputPortClient client = new OutputPortClient(token, inputPort);
                 synchronized (mPortClients) {
@@ -200,14 +238,27 @@
                 }
             }
         }
+
+        @Override
+        public void connectPorts(IBinder token, ParcelFileDescriptor pfd,
+                int outputPortNumber) {
+            MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
+            mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort);
+            mInputPorts.add(inputPort);
+            OutputPortClient client = new OutputPortClient(token, inputPort);
+            synchronized (mPortClients) {
+                mPortClients.put(token, client);
+            }
+        }
     };
 
     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
-            int numOutputPorts) {
+            int numOutputPorts, Callback callback) {
         mMidiManager = midiManager;
         mInputPortReceivers = inputPortReceivers;
         mInputPortCount = inputPortReceivers.length;
         mOutputPortCount = numOutputPorts;
+        mCallback = callback;
 
         mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
 
@@ -216,6 +267,9 @@
             mOutputPortDispatchers[i] = new MidiDispatcher();
         }
 
+        mInputPortBusy = new boolean[mInputPortCount];
+        mOutputPortOpenCount = new int[numOutputPorts];
+
         mGuard.open("close");
     }
 
@@ -230,9 +284,28 @@
         mDeviceInfo = deviceInfo;
     }
 
+    private void updateDeviceStatus() {
+        // clear calling identity, since we may be in a Binder call from one of our clients
+        long identityToken = Binder.clearCallingIdentity();
+
+        MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortBusy,
+                mOutputPortOpenCount);
+        if (mCallback != null) {
+            mCallback.onDeviceStatusChanged(this, status);
+        }
+        try {
+            mMidiManager.setDeviceStatus(mDeviceStatusToken, status);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in updateDeviceStatus");
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+        }
+    }
+
     @Override
     public void close() throws IOException {
         synchronized (mGuard) {
+            if (mIsClosed) return;
             mGuard.close();
 
             for (int i = 0; i < mInputPortCount; i++) {
@@ -251,6 +324,7 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException in unregisterDeviceServer");
             }
+            mIsClosed = true;
         }
     }
 
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 64f69cd..5f55ae2 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -57,6 +57,13 @@
     private MidiDeviceServer mServer;
     private MidiDeviceInfo mDeviceInfo;
 
+    private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+        @Override
+        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+            MidiDeviceService.this.onDeviceStatusChanged(status);
+        }
+    };
+
     @Override
     public void onCreate() {
         mMidiManager = IMidiManager.Stub.asInterface(
@@ -75,7 +82,7 @@
                 inputPortReceivers = new MidiReceiver[0];
             }
             server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
-                    deviceInfo.getOutputPortCount());
+                    deviceInfo.getOutputPortCount(), mCallback);
             server.setDeviceInfo(deviceInfo);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
@@ -114,6 +121,13 @@
         return mDeviceInfo;
     }
 
+    /**
+     * Called to notify when an our {@link MidiDeviceStatus} has changed
+     * @param status the number of the port that was opened
+     */
+    public void onDeviceStatusChanged(MidiDeviceStatus status) {
+    }
+
     @Override
     public IBinder onBind(Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
diff --git a/media/java/android/media/midi/MidiDeviceStatus.aidl b/media/java/android/media/midi/MidiDeviceStatus.aidl
new file mode 100644
index 0000000..1a848c0
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.midi;
+
+parcelable MidiDeviceStatus;
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
new file mode 100644
index 0000000..cc04889
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -0,0 +1,141 @@
+/*
+ * 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.media.midi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This is an immutable class that describes the current status of a MIDI device's ports.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public final class MidiDeviceStatus implements Parcelable {
+
+    private static final String TAG = "MidiDeviceStatus";
+
+    private final MidiDeviceInfo mDeviceInfo;
+    // true if input ports are busy
+    private final boolean mInputPortBusy[];
+    // open counts for output ports
+    private final int mOutputPortOpenCount[];
+
+    /**
+     * @hide
+     */
+    public MidiDeviceStatus(MidiDeviceInfo deviceInfo, boolean inputPortBusy[],
+            int outputPortOpenCount[]) {
+        // MidiDeviceInfo is immutable so we can share references
+        mDeviceInfo = deviceInfo;
+
+        // make copies of the arrays
+        mInputPortBusy = new boolean[inputPortBusy.length];
+        System.arraycopy(inputPortBusy, 0, mInputPortBusy, 0, inputPortBusy.length);
+        mOutputPortOpenCount = new int[outputPortOpenCount.length];
+        System.arraycopy(outputPortOpenCount, 0, mOutputPortOpenCount, 0,
+                outputPortOpenCount.length);
+    }
+
+    /**
+     * Creates a MidiDeviceStatus with false for all input port busy values
+     * and zero for all output port open counts
+     * @hide
+     */
+    public MidiDeviceStatus(MidiDeviceInfo deviceInfo) {
+        mDeviceInfo = deviceInfo;
+        mInputPortBusy = new boolean[deviceInfo.getInputPortCount()];
+        mOutputPortOpenCount = new int[deviceInfo.getOutputPortCount()];
+    }
+
+    /**
+     * Returns the {@link MidiDeviceInfo} of the device.
+     *
+     * @return the device info
+     */
+    public MidiDeviceInfo getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    /**
+     * Returns true if an input port is busy.
+     *
+     * @param input port's port number
+     * @return input port busy status
+     */
+    public boolean isInputPortBusy(int portNumber) {
+        return mInputPortBusy[portNumber];
+    }
+
+    /**
+     * Returns the open count for an output port.
+     *
+     * @param output port's port number
+     * @return output port open count
+     */
+    public int getOutputPortOpenCount(int portNumber) {
+        return mOutputPortOpenCount[portNumber];
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder(mDeviceInfo.toString());
+        int inputPortCount = mDeviceInfo.getInputPortCount();
+        int outputPortCount = mDeviceInfo.getOutputPortCount();
+        builder.append(" mInputPortBusy=[");
+        for (int i = 0; i < inputPortCount; i++) {
+            builder.append(mInputPortBusy[i]);
+            if (i < inputPortCount -1) {
+                builder.append(",");
+            }
+        }
+        builder.append("] mOutputPortOpenCount=[");
+        for (int i = 0; i < outputPortCount; i++) {
+            builder.append(mOutputPortOpenCount[i]);
+            if (i < outputPortCount -1) {
+                builder.append(",");
+            }
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    public static final Parcelable.Creator<MidiDeviceStatus> CREATOR =
+        new Parcelable.Creator<MidiDeviceStatus>() {
+        public MidiDeviceStatus createFromParcel(Parcel in) {
+            ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
+            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
+            boolean[] inputPortBusy = in.createBooleanArray();
+            int[] outputPortOpenCount = in.createIntArray();
+            return new MidiDeviceStatus(deviceInfo, inputPortBusy, outputPortOpenCount);
+        }
+
+        public MidiDeviceStatus[] newArray(int size) {
+            return new MidiDeviceStatus[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mDeviceInfo, flags);
+        parcel.writeBooleanArray(mInputPortBusy);
+        parcel.writeIntArray(mOutputPortOpenCount);
+   }
+}
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
index 90789e5..d13ca74 100644
--- a/media/java/android/media/midi/MidiDispatcher.java
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -55,11 +55,11 @@
     };
 
     /**
-     * Returns whether this dispatcher contains any receivers.
-     * @return true if the receiver list is not empty
+     * Returns the number of {@link MidiReceiver}s this dispatcher contains.
+     * @return the number of receivers
      */
-    public boolean hasReceivers() {
-        return mReceivers.size() > 0;
+    public int getReceiverCount() {
+        return mReceivers.size();
     }
 
     /**
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 74e1fa4..752075e1 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -41,7 +41,8 @@
     private IMidiDeviceServer mDeviceServer;
     private final IBinder mToken;
     private final int mPortNumber;
-    private final FileOutputStream mOutputStream;
+    private ParcelFileDescriptor mParcelFileDescriptor;
+    private FileOutputStream mOutputStream;
 
     private final CloseGuard mGuard = CloseGuard.get();
     private boolean mIsClosed;
@@ -53,8 +54,9 @@
             ParcelFileDescriptor pfd, int portNumber) {
         mDeviceServer = server;
         mToken = token;
+        mParcelFileDescriptor = pfd;
         mPortNumber = portNumber;
-        mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+        mOutputStream = new FileOutputStream(pfd.getFileDescriptor());
         mGuard.open("close");
     }
 
@@ -89,11 +91,27 @@
         }
 
         synchronized (mBuffer) {
+            if (mOutputStream == null) {
+                throw new IOException("MidiInputPort is closed");
+            }
             int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
             mOutputStream.write(mBuffer, 0, length);
         }
     }
 
+    // used by MidiDevice.connectInputPort() to connect our socket directly to another device
+    /* package */ ParcelFileDescriptor claimFileDescriptor() {
+        synchronized (mBuffer) {
+            ParcelFileDescriptor pfd = mParcelFileDescriptor;
+            if (pfd != null) {
+                IoUtils.closeQuietly(mOutputStream);
+                mParcelFileDescriptor = null;
+                mOutputStream = null;
+            }
+            return pfd;
+        }
+    }
+
     @Override
     public int getMaxMessageSize() {
         return MidiPortImpl.MAX_PACKET_DATA_SIZE;
@@ -104,7 +122,16 @@
         synchronized (mGuard) {
             if (mIsClosed) return;
             mGuard.close();
-            mOutputStream.close();
+            synchronized (mBuffer) {
+                if (mParcelFileDescriptor != null) {
+                    mParcelFileDescriptor.close();
+                    mParcelFileDescriptor = null;
+                }
+                if (mOutputStream != null) {
+                    mOutputStream.close();
+                    mOutputStream = null;
+                }
+            }
             if (mDeviceServer != null) {
                 try {
                     mDeviceServer.closePort(mToken);
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index d7b8c57..bab9064 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -62,6 +62,7 @@
             mHandler = handler;
         }
 
+        @Override
         public void onDeviceAdded(MidiDeviceInfo device) {
             if (mHandler != null) {
                 final MidiDeviceInfo deviceF = device;
@@ -75,6 +76,7 @@
             }
         }
 
+        @Override
         public void onDeviceRemoved(MidiDeviceInfo device) {
             if (mHandler != null) {
                 final MidiDeviceInfo deviceF = device;
@@ -87,25 +89,49 @@
                 mCallback.onDeviceRemoved(device);
             }
         }
+
+        @Override
+        public void onDeviceStatusChanged(MidiDeviceStatus status) {
+            if (mHandler != null) {
+                final MidiDeviceStatus statusF = status;
+                mHandler.post(new Runnable() {
+                        @Override public void run() {
+                            mCallback.onDeviceStatusChanged(statusF);
+                        }
+                    });
+            } else {
+                mCallback.onDeviceStatusChanged(status);
+            }
+        }
     }
 
     /**
      * Callback class used for clients to receive MIDI device added and removed notifications
      */
-    abstract public static class DeviceCallback {
+    public static class DeviceCallback {
         /**
          * Called to notify when a new MIDI device has been added
          *
          * @param device a {@link MidiDeviceInfo} for the newly added device
          */
-        abstract public void onDeviceAdded(MidiDeviceInfo device);
+        public void onDeviceAdded(MidiDeviceInfo device) {
+        }
 
         /**
          * Called to notify when a MIDI device has been removed
          *
          * @param device a {@link MidiDeviceInfo} for the removed device
          */
-        abstract public void onDeviceRemoved(MidiDeviceInfo device);
+        public void onDeviceRemoved(MidiDeviceInfo device) {
+        }
+
+        /**
+         * Called to notify when the status of a MIDI device has changed
+         *
+         * @param device a {@link MidiDeviceStatus} for the changed device
+         */
+        public void onDeviceStatusChanged(MidiDeviceStatus status) {
+        }
     }
 
     /**
@@ -251,10 +277,10 @@
 
     /** @hide */
     public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
-            int numOutputPorts, Bundle properties, int type) {
+            int numOutputPorts, Bundle properties, int type, MidiDeviceServer.Callback callback) {
         try {
             MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
-                    numOutputPorts);
+                    numOutputPorts, callback);
             MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
                     inputPortReceivers.length, numOutputPorts, properties, type);
             if (deviceInfo == null) {
diff --git a/native/graphics/jni/Android.mk b/native/graphics/jni/Android.mk
index 14575ee..b7f0fbd 100644
--- a/native/graphics/jni/Android.mk
+++ b/native/graphics/jni/Android.mk
@@ -31,7 +31,7 @@
 LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
 
 # TODO: This is to work around b/19059885. Remove after root cause is fixed
-LOCAL_LDFLAGS := -Wl,--hash-style=both
+LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
 
 include $(BUILD_SHARED_LIBRARY)
 
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 870afeb..b5e49ce 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -76,6 +76,17 @@
     <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 screen, summary text under individual Bluetooth devices when pairing -->
+    <string name="bluetooth_pairing">Pairing\u2026</string>
+
+    <!-- Bluetooth settings.  Message when connected to a device, except for phone audio. [CHAR LIMIT=40] -->
+    <string name="bluetooth_connected_no_headset">Connected (no phone)</string>
+    <!-- Bluetooth settings.  Message when connected to a device, except for media audio. [CHAR LIMIT=40] -->
+    <string name="bluetooth_connected_no_a2dp">Connected (no media)</string>
+    <!-- Bluetooth settings.  Message when connected to a device, except for map. [CHAR LIMIT=40] -->
+    <string name="bluetooth_connected_no_map">Connected (no message access)</string>
+    <!-- Bluetooth settings.  Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] -->
+    <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)</string>
 
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the A2DP profile. -->
     <string name="bluetooth_profile_a2dp">Media audio</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index b802f58..4c41b49 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -27,4 +27,5 @@
     void onDeviceAdded(CachedBluetoothDevice cachedDevice);
     void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
     void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
+    void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 7c92368..5d6b2f1 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -81,6 +81,9 @@
 
         // Bluetooth on/off broadcasts
         addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
+        // Generic connected/not broadcast
+        addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
+                new ConnectionStateChangedHandler());
 
         // Discovery broadcasts
         addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
@@ -183,8 +186,6 @@
                 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);
@@ -193,7 +194,25 @@
         }
     }
 
-    private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+    private class ConnectionStateChangedHandler implements Handler {
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                    BluetoothAdapter.ERROR);
+            dispatchConnectionStateChanged(cachedDevice, state);
+        }
+    }
+
+    private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        synchronized (mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onConnectionStateChanged(cachedDevice, state);
+            }
+        }
+    }
+
+    void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
         synchronized (mCallbacks) {
             for (BluetoothCallback callback : mCallbacks) {
                 callback.onDeviceAdded(cachedDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index ddcc49f7..e1cb878 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -28,6 +28,8 @@
 import android.util.Log;
 import android.bluetooth.BluetoothAdapter;
 
+import com.android.settingslib.R;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -784,4 +786,62 @@
             setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
         }
     }
+
+    public int getMaxConnectionState() {
+        int maxState = BluetoothProfile.STATE_DISCONNECTED;
+        for (LocalBluetoothProfile profile : getProfiles()) {
+            int connectionStatus = getProfileConnectionState(profile);
+            if (connectionStatus > maxState) {
+                maxState = connectionStatus;
+            }
+        }
+        return maxState;
+    }
+
+    /**
+     * @return resource for string that discribes the connection state of this device.
+     */
+    public int getConnectionSummary() {
+        boolean profileConnected = false;       // at least one profile is connected
+        boolean a2dpNotConnected = false;       // A2DP is preferred but not connected
+        boolean headsetNotConnected = false;    // Headset is preferred but not connected
+
+        for (LocalBluetoothProfile profile : getProfiles()) {
+            int connectionStatus = getProfileConnectionState(profile);
+
+            switch (connectionStatus) {
+                case BluetoothProfile.STATE_CONNECTING:
+                case BluetoothProfile.STATE_DISCONNECTING:
+                    return Utils.getConnectionStateSummary(connectionStatus);
+
+                case BluetoothProfile.STATE_CONNECTED:
+                    profileConnected = true;
+                    break;
+
+                case BluetoothProfile.STATE_DISCONNECTED:
+                    if (profile.isProfileReady()) {
+                        if (profile instanceof A2dpProfile) {
+                            a2dpNotConnected = true;
+                        } else if (profile instanceof HeadsetProfile) {
+                            headsetNotConnected = true;
+                        }
+                    }
+                    break;
+            }
+        }
+
+        if (profileConnected) {
+            if (a2dpNotConnected && headsetNotConnected) {
+                return R.string.bluetooth_connected_no_headset_no_a2dp;
+            } else if (a2dpNotConnected) {
+                return R.string.bluetooth_connected_no_a2dp;
+            } else if (headsetNotConnected) {
+                return R.string.bluetooth_connected_no_headset;
+            } else {
+                return R.string.bluetooth_connected;
+            }
+        }
+
+        return getBondState() == BluetoothDevice.BOND_BONDING ? R.string.bluetooth_pairing : 0;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 65db95f..a9f4bd3 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -35,9 +35,11 @@
     private Context mContext;
     private final List<CachedBluetoothDevice> mCachedDevices =
             new ArrayList<CachedBluetoothDevice>();
+    private final LocalBluetoothManager mBtManager;
 
-    CachedBluetoothDeviceManager(Context context) {
+    CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
         mContext = context;
+        mBtManager = localBtManager;
     }
 
     public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
@@ -88,6 +90,7 @@
             profileManager, device);
         synchronized (mCachedDevices) {
             mCachedDevices.add(newDevice);
+            mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
         }
         return newDevice;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 0c1adec..e3d2a99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -122,6 +122,10 @@
         return mAdapter.isEnabled();
     }
 
+    public int getConnectionState() {
+        return mAdapter.getConnectionState();
+    }
+
     public void setDiscoverableTimeout(int timeout) {
         mAdapter.setDiscoverableTimeout(timeout);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 4adc62e..623ccc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -68,7 +68,7 @@
         mContext = context;
         mLocalAdapter = adapter;
 
-        mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
+        mCachedDeviceManager = new CachedBluetoothDeviceManager(context, this);
         mEventManager = new BluetoothEventManager(mLocalAdapter,
                 mCachedDeviceManager, context);
         mProfileManager = new LocalBluetoothProfileManager(context,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3a8216d..3bf6828 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -470,7 +470,7 @@
     private void skipEmptyTextTags(XmlPullParser parser)
             throws IOException, XmlPullParserException {
         while (accept(parser, XmlPullParser.TEXT, null)
-                && "\n".equals(parser.getText())) {
+                && parser.isWhitespace()) {
             parser.next();
         }
     }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e8260bb..158e133 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -47,6 +47,7 @@
     <!-- Networking and telephony -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index c15566f..b42b5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
 import android.provider.Settings;
@@ -23,13 +25,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
 
+import java.util.Collection;
 import java.util.Set;
 
 /** Quick settings tile: Bluetooth **/
@@ -143,7 +146,7 @@
             refreshState();
         }
         @Override
-        public void onBluetoothPairedDevicesChanged() {
+        public void onBluetoothDevicesChanged() {
             mUiHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -199,19 +202,21 @@
         private void updateItems() {
             if (mItems == null) return;
             Item[] items = null;
-            final Set<PairedDevice> devices = mController.getPairedDevices();
+            final Collection<CachedBluetoothDevice> devices = mController.getDevices();
             if (devices != null) {
-                items = new Item[devices.size()];
+                items = new Item[getBondedCount(devices)];
                 int i = 0;
-                for (PairedDevice device : devices) {
+                for (CachedBluetoothDevice device : devices) {
+                    if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
                     final Item item = new Item();
                     item.icon = R.drawable.ic_qs_bluetooth_on;
-                    item.line1 = device.name;
-                    if (device.state == PairedDevice.STATE_CONNECTED) {
+                    item.line1 = device.getName();
+                    int state = device.getMaxConnectionState();
+                    if (state == BluetoothProfile.STATE_CONNECTED) {
                         item.icon = R.drawable.ic_qs_bluetooth_connected;
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
                         item.canDisconnect = true;
-                    } else if (device.state == PairedDevice.STATE_CONNECTING) {
+                    } else if (state == BluetoothProfile.STATE_CONNECTING) {
                         item.icon = R.drawable.ic_qs_bluetooth_connecting;
                         item.line2 = mContext.getString(R.string.quick_settings_connecting);
                     }
@@ -222,11 +227,22 @@
             mItems.setItems(items);
         }
 
+        private int getBondedCount(Collection<CachedBluetoothDevice> devices) {
+            int ct = 0;
+            for (CachedBluetoothDevice device : devices) {
+                if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+                    ct++;
+                }
+            }
+            return ct;
+        }
+
         @Override
         public void onDetailItemClick(Item item) {
             if (item == null || item.tag == null) return;
-            final PairedDevice device = (PairedDevice) item.tag;
-            if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+            if (device != null && device.getMaxConnectionState()
+                    == BluetoothProfile.STATE_DISCONNECTED) {
                 mController.connect(device);
             }
         }
@@ -234,7 +250,7 @@
         @Override
         public void onDetailItemDisconnect(Item item) {
             if (item == null || item.tag == null) return;
-            final PairedDevice device = (PairedDevice) item.tag;
+            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
             if (device != null) {
                 mController.disconnect(device);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index cbdd138..cbe4c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
-import java.util.Set;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import java.util.Collection;
 
 public interface BluetoothController {
     void addStateChangedCallback(Callback callback);
@@ -28,32 +30,12 @@
     boolean isBluetoothConnecting();
     String getLastDeviceName();
     void setBluetoothEnabled(boolean enabled);
-    Set<PairedDevice> getPairedDevices();
-    void connect(PairedDevice device);
-    void disconnect(PairedDevice device);
+    Collection<CachedBluetoothDevice> getDevices();
+    void connect(CachedBluetoothDevice device);
+    void disconnect(CachedBluetoothDevice device);
 
     public interface Callback {
         void onBluetoothStateChange(boolean enabled, boolean connecting);
-        void onBluetoothPairedDevicesChanged();
-    }
-
-    public static final class PairedDevice {
-        public static int STATE_DISCONNECTED = 0;
-        public static int STATE_CONNECTING = 1;
-        public static int STATE_CONNECTED = 2;
-        public static int STATE_DISCONNECTING = 3;
-
-        public String id;
-        public String name;
-        public int state = STATE_DISCONNECTED;
-        public Object tag;
-
-        public static String stateToString(int state) {
-            if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
-            if (state == STATE_CONNECTING) return "STATE_CONNECTING";
-            if (state == STATE_CONNECTED) return "STATE_CONNECTED";
-            if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
-            return "UNKNOWN";
-        }
+        void onBluetoothDevicesChanged();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 81e1e45..8d4f302 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,138 +16,57 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static android.bluetooth.BluetoothAdapter.ERROR;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
-import android.util.SparseArray;
 
-import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Set;
+import java.util.Collection;
 
-public class BluetoothControllerImpl implements BluetoothController {
+public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
+        CachedBluetoothDevice.Callback {
     private static final String TAG = "BluetoothController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    // This controls the order in which we check the states.  Since a device can only have
-    // one state on screen, but can have multiple profiles, the later states override the
-    // value of earlier states.  So if a device has a profile in CONNECTING and one in
-    // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
-    // but seemed worth noting.
-    private static final int[] CONNECTION_STATES = {
-        BluetoothProfile.STATE_DISCONNECTED,
-        BluetoothProfile.STATE_DISCONNECTING,
-        BluetoothProfile.STATE_CONNECTING,
-        BluetoothProfile.STATE_CONNECTED,
-    };
-    // Update all the BT device states.
-    private static final int MSG_UPDATE_CONNECTION_STATES = 1;
-    // Update just one BT device.
-    private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
-    // Update whether devices are bonded or not.
-    private static final int MSG_UPDATE_BONDED_DEVICES = 3;
 
-    private static final int MSG_ADD_PROFILE = 4;
-    private static final int MSG_REM_PROFILE = 5;
-
-    private final Context mContext;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-    private final BluetoothAdapter mAdapter;
-    private final Receiver mReceiver = new Receiver();
-    private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
-    private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
-
-    private final H mHandler;
+    private final LocalBluetoothManager mLocalBluetoothManager;
 
     private boolean mEnabled;
     private boolean mConnecting;
-    private BluetoothDevice mLastDevice;
+    private CachedBluetoothDevice mLastDevice;
 
     public BluetoothControllerImpl(Context context, Looper bgLooper) {
-        mContext = context;
-        mHandler = new H(bgLooper);
-
-        final BluetoothManager bluetoothManager =
-                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
-        mAdapter = bluetoothManager.getAdapter();
-        if (mAdapter == null) {
-            Log.w(TAG, "Default BT adapter not found");
-            return;
+        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+        if (mLocalBluetoothManager != null) {
+            mLocalBluetoothManager.getEventManager().registerCallback(this);
+            onBluetoothStateChanged(
+                    mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
         }
-
-        mReceiver.register();
-        setAdapterState(mAdapter.getState());
-        updateBondedDevices();
-        bindAllProfiles();
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("BluetoothController state:");
-        pw.print("  mAdapter="); pw.println(mAdapter);
+        pw.print("  mLocalBluetoothManager="); pw.println(mLocalBluetoothManager);
         pw.print("  mEnabled="); pw.println(mEnabled);
         pw.print("  mConnecting="); pw.println(mConnecting);
         pw.print("  mLastDevice="); pw.println(mLastDevice);
         pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
-        pw.print("  mProfiles="); pw.println(profilesToString(mProfiles));
-        pw.print("  mDeviceInfo.size="); pw.println(mDeviceInfo.size());
-        for (int i = 0; i < mDeviceInfo.size(); i++) {
-            final BluetoothDevice device = mDeviceInfo.keyAt(i);
-            final DeviceInfo info = mDeviceInfo.valueAt(i);
-            pw.print("    "); pw.print(deviceToString(device));
-            pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
-            pw.print("    "); pw.println(infoToString(info));
+        pw.println("  Bluetooth Devices:");
+        for (CachedBluetoothDevice device :
+                mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
+            pw.println("    " + getDeviceString(device));
         }
     }
 
-    private static String infoToString(DeviceInfo info) {
-        return info == null ? null : ("connectionState=" +
-                connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
-                + ",bonded=" + info.bonded + ",profiles="
-                + profilesToString(info.connectedProfiles));
-    }
-
-    private static String profilesToString(SparseArray<?> profiles) {
-        final int N = profiles.size();
-        final StringBuffer buffer = new StringBuffer();
-        buffer.append('[');
-        for (int i = 0; i < N; i++) {
-            if (i != 0) {
-                buffer.append(',');
-            }
-            buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
-        }
-        buffer.append(']');
-        return buffer.toString();
+    private String getDeviceString(CachedBluetoothDevice device) {
+        return device.getName() + " " + device.getBondState() + " " + device.isConnected();
     }
 
     public void addStateChangedCallback(Callback cb) {
@@ -162,266 +81,63 @@
 
     @Override
     public boolean isBluetoothEnabled() {
-        return mAdapter != null && mAdapter.isEnabled();
+        return mEnabled;
     }
 
     @Override
     public boolean isBluetoothConnected() {
-        return mAdapter != null
-                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
+        return mLocalBluetoothManager != null
+                && mLocalBluetoothManager.getBluetoothAdapter().getConnectionState()
+                == BluetoothAdapter.STATE_CONNECTED;
     }
 
     @Override
     public boolean isBluetoothConnecting() {
-        return mAdapter != null
-                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
+        return mConnecting;
     }
 
     @Override
     public void setBluetoothEnabled(boolean enabled) {
-        if (mAdapter != null) {
-            if (enabled) {
-                mAdapter.enable();
-            } else {
-                mAdapter.disable();
-            }
+        if (mLocalBluetoothManager != null) {
+            mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
         }
     }
 
     @Override
     public boolean isBluetoothSupported() {
-        return mAdapter != null;
+        return mLocalBluetoothManager != null;
     }
 
     @Override
-    public ArraySet<PairedDevice> getPairedDevices() {
-        final ArraySet<PairedDevice> rt = new ArraySet<>();
-        for (int i = 0; i < mDeviceInfo.size(); i++) {
-            final BluetoothDevice device = mDeviceInfo.keyAt(i);
-            final DeviceInfo info = mDeviceInfo.valueAt(i);
-            if (!info.bonded) continue;
-            final PairedDevice paired = new PairedDevice();
-            paired.id = device.getAddress();
-            paired.tag = device;
-            paired.name = device.getAliasName();
-            paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
-            rt.add(paired);
-        }
-        return rt;
-    }
-
-    private static int connectionStateToPairedDeviceState(int index) {
-        int state = CONNECTION_STATES[index];
-        if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
-        if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
-        if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
-        return PairedDevice.STATE_DISCONNECTED;
+    public void connect(final CachedBluetoothDevice device) {
+        if (mLocalBluetoothManager == null || device == null) return;
+        device.connect(true);
     }
 
     @Override
-    public void connect(final PairedDevice pd) {
-        connect(pd, true);
-    }
-
-    @Override
-    public void disconnect(PairedDevice pd) {
-        connect(pd, false);
-    }
-
-    private void connect(PairedDevice pd, final boolean connect) {
-        if (mAdapter == null || pd == null || pd.tag == null) return;
-        final BluetoothDevice device = (BluetoothDevice) pd.tag;
-        final DeviceInfo info = mDeviceInfo.get(device);
-        final String action = connect ? "connect" : "disconnect";
-        if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
-        final ParcelUuid[] uuids = device.getUuids();
-        if (uuids == null) {
-            Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
-            return;
-        }
-        SparseArray<Boolean> profiles = new SparseArray<>();
-        if (connect) {
-            // When connecting add every profile we can recognize by uuid.
-            for (ParcelUuid uuid : uuids) {
-                final int profile = uuidToProfile(uuid);
-                if (profile == 0) {
-                    Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
-                            + uuidToString(uuid));
-                    continue;
-                }
-                final boolean connected = info.connectedProfiles.get(profile, false);
-                if (!connected) {
-                    profiles.put(profile, true);
-                }
-            }
-        } else {
-            // When disconnecting, just add every profile we know they are connected to.
-            profiles = info.connectedProfiles;
-        }
-        for (int i = 0; i < profiles.size(); i++) {
-            final int profile = profiles.keyAt(i);
-            if (mProfiles.indexOfKey(profile) >= 0) {
-                final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
-                final boolean ok = connect ? p.connect(device) : p.disconnect(device);
-                if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
-                        + (ok ? "succeeded" : "failed"));
-            } else {
-                Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
-            }
-        }
+    public void disconnect(CachedBluetoothDevice device) {
+        if (mLocalBluetoothManager == null || device == null) return;
+        device.disconnect();
     }
 
     @Override
     public String getLastDeviceName() {
-        return mLastDevice != null ? mLastDevice.getAliasName() : null;
+        return mLastDevice != null ? mLastDevice.getName() : null;
     }
 
-    private void updateBondedDevices() {
-        mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
-        mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
-    }
-
-    private void updateConnectionStates() {
-        mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
-        mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
-        mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
-    }
-
-    private void updateConnectionState(BluetoothDevice device, int profile, int state) {
-        if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) {
-            // If we are about to update all the devices, then we don't need to update this one.
-            return;
-        }
-        mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device)
-                .sendToTarget();
-    }
-
-    private void handleUpdateBondedDevices() {
-        if (mAdapter == null) return;
-        final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
-        for (DeviceInfo info : mDeviceInfo.values()) {
-            info.bonded = false;
-        }
-        int bondedCount = 0;
-        BluetoothDevice lastBonded = null;
-        if (bondedDevices != null) {
-            for (BluetoothDevice bondedDevice : bondedDevices) {
-                final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
-                updateInfo(bondedDevice).bonded = bonded;
-                if (bonded) {
-                    bondedCount++;
-                    lastBonded = bondedDevice;
-                }
-            }
-        }
-        if (mLastDevice == null && bondedCount == 1) {
-            mLastDevice = lastBonded;
-        }
-        updateConnectionStates();
-        firePairedDevicesChanged();
-    }
-
-    private void handleUpdateConnectionStates() {
-        final int N = mDeviceInfo.size();
-        for (int i = 0; i < N; i++) {
-            BluetoothDevice device = mDeviceInfo.keyAt(i);
-            DeviceInfo info = updateInfo(device);
-            info.connectionStateIndex = 0;
-            info.connectedProfiles.clear();
-            for (int j = 0; j < mProfiles.size(); j++) {
-                int state = mProfiles.valueAt(j).getConnectionState(device);
-                handleUpdateConnectionState(device, mProfiles.keyAt(j), state);
-            }
-        }
-        handleConnectionChange();
-        firePairedDevicesChanged();
-    }
-
-    private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) {
-        if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device)
-                + " " + BluetoothUtil.profileToString(profile)
-                + " " + BluetoothUtil.connectionStateToString(state));
-        DeviceInfo info = updateInfo(device);
-        int stateIndex = 0;
-        for (int i = 0; i < CONNECTION_STATES.length; i++) {
-            if (CONNECTION_STATES[i] == state) {
-                stateIndex = i;
-                break;
-            }
-        }
-        info.profileStates.put(profile, stateIndex);
-
-        info.connectionStateIndex = 0;
-        final int N = info.profileStates.size();
-        for (int i = 0; i < N; i++) {
-            if (info.profileStates.valueAt(i) > info.connectionStateIndex) {
-                info.connectionStateIndex = info.profileStates.valueAt(i);
-            }
-        }
-        if (state == BluetoothProfile.STATE_CONNECTED) {
-            info.connectedProfiles.put(profile, true);
-        } else {
-            info.connectedProfiles.remove(profile);
-        }
-    }
-
-    private void handleConnectionChange() {
-        // If we are no longer connected to the current device, see if we are connected to
-        // something else, so we don't display a name we aren't connected to.
-        if (mLastDevice != null &&
-                CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex]
-                        != BluetoothProfile.STATE_CONNECTED) {
-            // Make sure we don't keep this device while it isn't connected.
-            mLastDevice = null;
-            // Look for anything else connected.
-            final int size = mDeviceInfo.size();
-            for (int i = 0; i < size; i++) {
-                BluetoothDevice device = mDeviceInfo.keyAt(i);
-                DeviceInfo info = mDeviceInfo.valueAt(i);
-                if (CONNECTION_STATES[info.connectionStateIndex]
-                        == BluetoothProfile.STATE_CONNECTED) {
-                    mLastDevice = device;
-                    break;
-                }
-            }
-        }
-    }
-
-    private void bindAllProfiles() {
-        // Note: This needs to contain all of the types that can be returned by BluetoothUtil
-        // otherwise we can't find the profiles we need when we connect/disconnect.
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
-        // Note Health is not in this list because health devices aren't 'connected'.
-        // If profiles are expanded to use more than just connection state and connect/disconnect
-        // then it should be added.
+    @Override
+    public Collection<CachedBluetoothDevice> getDevices() {
+        return mLocalBluetoothManager != null
+                ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+                : null;
     }
 
     private void firePairedDevicesChanged() {
         for (Callback cb : mCallbacks) {
-            cb.onBluetoothPairedDevicesChanged();
+            cb.onBluetoothDevicesChanged();
         }
     }
 
-    private void setAdapterState(int adapterState) {
-        final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
-        if (mEnabled == enabled) return;
-        mEnabled = enabled;
-        fireStateChange();
-    }
-
-    private void setConnecting(boolean connecting) {
-        if (mConnecting == connecting) return;
-        mConnecting = connecting;
-        fireStateChange();
-    }
-
     private void fireStateChange() {
         for (Callback cb : mCallbacks) {
             fireStateChange(cb);
@@ -432,141 +148,59 @@
         cb.onBluetoothStateChange(mEnabled, mConnecting);
     }
 
-    private static int getProfileFromAction(String action) {
-        if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.A2DP;
-        } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.HEADSET;
-        } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.A2DP_SINK;
-        } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.HEADSET_CLIENT;
-        } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.INPUT_DEVICE;
-        } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.MAP;
-        } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
-            return BluetoothProfile.PAN;
+    private void updateConnected() {
+        if (mLastDevice != null && mLastDevice.isConnected()) {
+            // Our current device is still valid.
+            return;
         }
-        if (DEBUG) Log.d(TAG, "Unknown action " + action);
-        return -1;
-    }
-
-    private final ServiceListener mProfileListener = new ServiceListener() {
-        @Override
-        public void onServiceDisconnected(int profile) {
-            if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile));
-            // We lost a profile, don't do any updates until it gets removed.
-            mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
-            mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
-            mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget();
-        }
-
-        @Override
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile));
-            mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget();
-        }
-    };
-
-    private final class Receiver extends BroadcastReceiver {
-        public void register() {
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-            filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-            filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
-            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
-            filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
-            mContext.registerReceiver(this, filter);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
-                updateBondedDevices();
-                if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
-            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
-                updateInfo(device);
-                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
-                        ERROR);
+        for (CachedBluetoothDevice device : getDevices()) {
+            if (device.isConnected()) {
                 mLastDevice = device;
-                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
-                        + connectionStateToString(state) + " " + deviceToString(device));
-                setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
-            } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
-                updateInfo(device);
-                mLastDevice = device;
-            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
-                if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
-                updateBondedDevices();
-            } else {
-                int profile = getProfileFromAction(intent.getAction());
-                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
-                        + BluetoothUtil.profileToString(profile)
-                        + " " + BluetoothUtil.connectionStateToString(state));
-                if ((profile != -1) && (state != -1)) {
-                    updateConnectionState(device, profile, state);
-                }
             }
         }
     }
 
-    private DeviceInfo updateInfo(BluetoothDevice device) {
-        DeviceInfo info = mDeviceInfo.get(device);
-        info = info != null ? info : new DeviceInfo();
-        mDeviceInfo.put(device, info);
-        return info;
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
+        mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
+        fireStateChange();
     }
 
-    private class H extends Handler {
-        public H(Looper l) {
-            super(l);
-        }
+    @Override
+    public void onScanningStateChanged(boolean started) {
+        // Don't care.
+    }
 
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_UPDATE_CONNECTION_STATES:
-                    handleUpdateConnectionStates();
-                    firePairedDevicesChanged();
-                    break;
-                case MSG_UPDATE_SINGLE_CONNECTION_STATE:
-                    handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
-                    handleConnectionChange();
-                    firePairedDevicesChanged();
-                    break;
-                case MSG_UPDATE_BONDED_DEVICES:
-                    handleUpdateBondedDevices();
-                    firePairedDevicesChanged();
-                    break;
-                case MSG_ADD_PROFILE:
-                    mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj);
-                    handleUpdateConnectionStates();
-                    firePairedDevicesChanged();
-                    break;
-                case MSG_REM_PROFILE:
-                    mProfiles.remove(msg.arg1);
-                    handleUpdateConnectionStates();
-                    firePairedDevicesChanged();
-                    break;
-            }
-        };
-    };
+    @Override
+    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+        cachedDevice.registerCallback(this);
+        updateConnected();
+        firePairedDevicesChanged();
+    }
 
-    private static class DeviceInfo {
-        int connectionStateIndex = 0;
-        boolean bonded;  // per getBondedDevices
-        SparseArray<Boolean> connectedProfiles = new SparseArray<>();
-        SparseArray<Integer> profileStates = new SparseArray<>();
+    @Override
+    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+        updateConnected();
+        firePairedDevicesChanged();
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        updateConnected();
+        firePairedDevicesChanged();
+    }
+
+    @Override
+    public void onDeviceAttributesChanged() {
+        updateConnected();
+        firePairedDevicesChanged();
+    }
+
+    @Override
+    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        mConnecting = state == BluetoothAdapter.STATE_CONNECTING;
+        mLastDevice = cachedDevice;
+        updateConnected();
+        fireStateChange();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
deleted file mode 100644
index ed8ac2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.statusbar.policy;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.os.ParcelUuid;
-import android.text.TextUtils;
-
-public class BluetoothUtil {
-
-    public static String profileToString(int profile) {
-        if (profile == BluetoothProfile.HEADSET) return "HEADSET";
-        if (profile == BluetoothProfile.A2DP) return "A2DP";
-        if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
-        if (profile == BluetoothProfile.PAN) return "PAN";
-        if (profile == BluetoothProfile.INPUT_DEVICE) return "INPUT_DEVICE";
-        if (profile == BluetoothProfile.MAP) return "MAP";
-        return "UNKNOWN(" + profile + ")";
-    }
-
-    public static String profileStateToString(int state) {
-        if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
-        if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
-        if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
-        if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
-        return "STATE_UNKNOWN";
-    }
-
-    public static String uuidToString(ParcelUuid uuid) {
-        if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
-        if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
-        if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
-        if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
-        if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
-        if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
-        if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
-        if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
-        if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
-        if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
-        if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
-        if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
-        if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
-        if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
-        if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
-        if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
-        if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
-        if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
-        if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
-        return uuid != null ? uuid.toString() : null;
-    }
-
-    public static String connectionStateToString(int connectionState) {
-        if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
-        if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
-        if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
-        if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
-        return "ERROR";
-    }
-
-    public static String deviceToString(BluetoothDevice device) {
-        return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
-    }
-
-    public static String uuidsToString(BluetoothDevice device) {
-        if (device == null) return null;
-        final ParcelUuid[] ids = device.getUuids();
-        if (ids == null) return null;
-        final String[] tokens = new String[ids.length];
-        for (int i = 0; i < tokens.length; i++) {
-            tokens[i] = uuidToString(ids[i]);
-        }
-        return TextUtils.join(",", tokens);
-    }
-
-    public static int uuidToProfile(ParcelUuid uuid) {
-        if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
-        if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
-
-        if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
-        if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
-
-        if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
-        if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
-        if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
-
-        if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
-
-        if (BluetoothUuid.Hid.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
-        if (BluetoothUuid.Hogp.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
-
-        if (BluetoothUuid.NAP.equals(uuid)) return BluetoothProfile.PAN;
-
-        return 0;
-    }
-
-    public static Profile getProfile(BluetoothProfile p) {
-        if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
-        if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
-        if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
-        if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
-        if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
-        if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
-        if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
-        return null;
-    }
-
-    private static Profile newProfile(final BluetoothA2dp a2dp) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return a2dp.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return a2dp.disconnect(device);
-            }
-        };
-    }
-
-    private static Profile newProfile(final BluetoothHeadset headset) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return headset.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return headset.disconnect(device);
-            }
-        };
-    }
-
-    private static Profile newProfile(final BluetoothA2dpSink sink) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return sink.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return sink.disconnect(device);
-            }
-        };
-    }
-
-    private static Profile newProfile(final BluetoothHeadsetClient client) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return client.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return client.disconnect(device);
-            }
-        };
-    }
-
-    private static Profile newProfile(final BluetoothInputDevice input) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return input.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return input.disconnect(device);
-            }
-        };
-    }
-
-    private static Profile newProfile(final BluetoothMap map) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return map.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return map.disconnect(device);
-            }
-        };
-    }
-
-    private static Profile newProfile(final BluetoothPan pan) {
-        return new Profile() {
-            @Override
-            public boolean connect(BluetoothDevice device) {
-                return pan.connect(device);
-            }
-
-            @Override
-            public boolean disconnect(BluetoothDevice device) {
-                return pan.disconnect(device);
-            }
-        };
-    }
-
-    // common abstraction for supported profiles
-    public interface Profile {
-        boolean connect(BluetoothDevice device);
-        boolean disconnect(BluetoothDevice device);
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 26510328..d35609e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3862,6 +3862,14 @@
                             Slog.d(TAG, "Ignoring non-agent system package " + pkg);
                         }
                         continue;
+                    } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+                        // Cull any packages in the 'stopped' state: they've either just been
+                        // installed or have explicitly been force-stopped by the user.  In both
+                        // cases we do not want to launch them for backup.
+                        if (MORE_DEBUG) {
+                            Slog.d(TAG, "Ignoring stopped package " + pkg);
+                        }
+                        continue;
                     }
                     mPackages.add(info);
                 } catch (NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index f04487e..983d83a 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -216,3 +216,8 @@
 # ---------------------------
 2755 fstrim_start (time|2|3)
 2756 fstrim_finish (time|2|3)
+
+# ---------------------------
+# AudioService.java
+# ---------------------------
+40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3)
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
index 7f98b30..d534548 100644
--- a/services/core/java/com/android/server/MidiService.java
+++ b/services/core/java/com/android/server/MidiService.java
@@ -29,6 +29,7 @@
 import android.media.midi.IMidiManager;
 import android.media.midi.MidiDeviceInfo;
 import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -147,6 +148,19 @@
             }
         }
 
+        public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
+            // ignore private devices that our client cannot access
+            if (!device.isUidAllowed(mUid)) return;
+
+            try {
+                for (IMidiDeviceListener listener : mListeners) {
+                    listener.onDeviceStatusChanged(status);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "remote exception", e);
+            }
+        }
+
         public void binderDied() {
             removeClient(mToken);
         }
@@ -187,6 +201,8 @@
     private final class Device implements IBinder.DeathRecipient {
         private final IMidiDeviceServer mServer;
         private final MidiDeviceInfo mDeviceInfo;
+        private MidiDeviceStatus mDeviceStatus;
+        private IBinder mDeviceStatusToken;
         // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
         private final ServiceInfo mServiceInfo;
         // UID of device implementation
@@ -204,6 +220,33 @@
             return mDeviceInfo;
         }
 
+        public MidiDeviceStatus getDeviceStatus() {
+            return mDeviceStatus;
+        }
+
+        public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+            mDeviceStatus = status;
+
+            if (mDeviceStatusToken == null && token != null) {
+                // register a death recipient so we can clear the status when the device dies
+                try {
+                    token.linkToDeath(new IBinder.DeathRecipient() {
+                        @Override
+                        public void binderDied() {
+                            // reset to default status and clear the token
+                            mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+                            mDeviceStatusToken = null;
+                            notifyDeviceStatusChanged(Device.this, mDeviceStatus);
+                        }
+                    }, 0);
+                    mDeviceStatusToken = token;
+                } catch (RemoteException e) {
+                    // reset to default status
+                    mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+                }
+            }
+        }
+
         public IMidiDeviceServer getDeviceServer() {
             return mServer;
         }
@@ -216,6 +259,10 @@
             return (mServiceInfo == null ? null : mServiceInfo.packageName);
         }
 
+        public int getUid() {
+            return mUid;
+        }
+
         public boolean isUidAllowed(int uid) {
             return (!mDeviceInfo.isPrivate() || mUid == uid);
         }
@@ -302,13 +349,14 @@
     @Override
     public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
             int numOutputPorts, Bundle properties, int type) {
-        if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
+        int uid = Binder.getCallingUid();
+        if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) {
             throw new SecurityException("only system can create non-virtual devices");
         }
 
         synchronized (mDevicesByInfo) {
             return addDeviceLocked(type, numInputPorts, numOutputPorts, properties,
-            server, null, false, -1);
+            server, null, false, uid);
         }
     }
 
@@ -337,6 +385,39 @@
         }
     }
 
+    @Override
+    public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) {
+        Device device = mDevicesByInfo.get(deviceInfo);
+        if (device == null) {
+            throw new IllegalArgumentException("no such device for " + deviceInfo);
+        }
+        return device.getDeviceStatus();
+    }
+
+    @Override
+    public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+        MidiDeviceInfo deviceInfo = status.getDeviceInfo();
+        Device device = mDevicesByInfo.get(deviceInfo);
+        if (device == null) {
+            // Just return quietly here if device no longer exists
+            return;
+        }
+        if (Binder.getCallingUid() != device.getUid()) {
+            throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid()
+                    + " does not match device's UID " + device.getUid());
+        }
+        device.setDeviceStatus(token, status);
+        notifyDeviceStatusChanged(device, status);
+    }
+
+    private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) {
+        synchronized (mClients) {
+            for (Client c : mClients.values()) {
+                c.deviceStatusChanged(device, status);
+            }
+        }
+    }
+
     // synchronize on mDevicesByInfo
     private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
             Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo,
@@ -469,17 +550,15 @@
                                 continue;
                             }
 
-                            int uid = -1;
-                            if (isPrivate) {
-                                try {
-                                    ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
-                                            serviceInfo.packageName, 0);
-                                    uid = appInfo.uid;
-                                } catch (PackageManager.NameNotFoundException e) {
-                                    Log.e(TAG, "could not fetch ApplicationInfo for "
-                                            + serviceInfo.packageName);
-                                    continue;
-                                }
+                            int uid;
+                            try {
+                                ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+                                        serviceInfo.packageName, 0);
+                                uid = appInfo.uid;
+                            } catch (PackageManager.NameNotFoundException e) {
+                                Log.e(TAG, "could not fetch ApplicationInfo for "
+                                        + serviceInfo.packageName);
+                                continue;
                             }
 
                             synchronized (mDevicesByInfo) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 8e46c4d..794e1b0 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -106,8 +106,8 @@
         }
 
         public void scheduleCheckLocked() {
-            if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) {
-                // If the target looper is or just recently was idling, then
+            if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
+                // If the target looper has recently been polling, then
                 // there is no reason to enqueue our checker on it since that
                 // is as good as it not being deadlocked.  This avoid having
                 // to do a context switch to check the thread.  Note that we
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8b7a6af..7d7520a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2508,9 +2508,11 @@
         }
 
         int taskNdx = mTaskHistory.indexOf(task);
-        do {
-            taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
-        } while (taskTop == null && taskNdx >= 0);
+        if (taskNdx >= 0) {
+            do {
+                taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
+            } while (taskTop == null && taskNdx >= 0);
+        }
 
         if (topOptions != null) {
             // If we got some ActivityOptions from an activity on top that
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fc949e0..1208c04 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -96,6 +96,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.util.XmlUtils;
+import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -618,7 +619,7 @@
 
         // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
         // array initialized by updateStreamVolumeAlias()
-        updateStreamVolumeAlias(false /*updateVolumes*/);
+        updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
         readPersistedSettings();
         mSettingsObserver = new SettingsObserver();
         createStreamStates();
@@ -712,7 +713,7 @@
                 SENDMSG_REPLACE,
                 0,
                 0,
-                null,
+                TAG,
                 SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
 
         StreamOverride.init(mContext);
@@ -745,7 +746,8 @@
             for (int streamType = 0; streamType < numStreamTypes; streamType++) {
                 if (streamType != mStreamVolumeAlias[streamType]) {
                     mStreamStates[streamType].
-                                    setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]);
+                                    setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]],
+                                            TAG);
                 }
                 // apply stream volume
                 if (!mStreamStates[streamType].mIsMuted) {
@@ -791,7 +793,7 @@
         pw.println(Integer.toHexString(mMuteAffectedStreams));
     }
 
-    private void updateStreamVolumeAlias(boolean updateVolumes) {
+    private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
         int dtmfStreamAlias;
 
         switch (mPlatformType) {
@@ -821,7 +823,8 @@
 
         mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
         if (updateVolumes) {
-            mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
+            mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias],
+                    caller);
             // apply stream mute states according to new value of mRingerModeAffectedStreams
             setRingerModeInt(getRingerModeInternal(), false);
             sendMsg(mAudioHandler,
@@ -950,15 +953,15 @@
     ///////////////////////////////////////////////////////////////////////////
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
-            String callingPackage) {
+            String callingPackage, String caller) {
         adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
-                Binder.getCallingUid());
+                caller, Binder.getCallingUid());
     }
 
     private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
-            String callingPackage, int uid) {
-        if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType
-                + ", flags=" + flags);
+            String callingPackage, String caller, int uid) {
+        if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+                + ", flags=" + flags + ", caller=" + caller);
         int streamType;
         boolean isMute = isMuteAdjust(direction);
         if (mVolumeControlStream != -1) {
@@ -983,22 +986,23 @@
             if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
         }
 
-        adjustStreamVolume(streamType, direction, flags, callingPackage, uid);
+        adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
     }
 
     /** @see AudioManager#adjustStreamVolume(int, int, int) */
     public void adjustStreamVolume(int streamType, int direction, int flags,
             String callingPackage) {
-        adjustStreamVolume(streamType, direction, flags, callingPackage, Binder.getCallingUid());
+        adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
+                Binder.getCallingUid());
     }
 
     private void adjustStreamVolume(int streamType, int direction, int flags,
-            String callingPackage, int uid) {
+            String callingPackage, String caller, int uid) {
         if (mUseFixedVolume) {
             return;
         }
-        if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction
-                + ", flags="+flags);
+        if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
+                + ", flags=" + flags + ", caller=" + caller);
 
         ensureValidDirection(direction);
         ensureValidStreamType(streamType);
@@ -1119,7 +1123,8 @@
                     !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
                 Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
                 mVolumeController.postDisplaySafeVolumeWarning(flags);
-            } else if (streamState.adjustIndex(direction * step, device) || streamState.mIsMuted) {
+            } else if (streamState.adjustIndex(direction * step, device, caller)
+                    || streamState.mIsMuted) {
                 // Post message to set system volume (it in turn will post a
                 // message to persist).
                 if (streamState.mIsMuted) {
@@ -1220,8 +1225,9 @@
         }
     };
 
-    private void onSetStreamVolume(int streamType, int index, int flags, int device) {
-        setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false);
+    private void onSetStreamVolume(int streamType, int index, int flags, int device,
+            String caller) {
+        setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, caller);
         // setting volume on ui sounds stream type also controls silent mode
         if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
                 (mStreamVolumeAlias[streamType] == getUiSoundsStreamType())) {
@@ -1239,11 +1245,12 @@
 
     /** @see AudioManager#setStreamVolume(int, int, int) */
     public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
-        setStreamVolume(streamType, index, flags, callingPackage, Binder.getCallingUid());
+        setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
+                Binder.getCallingUid());
     }
 
     private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
-            int uid) {
+            String caller, int uid) {
         if (mUseFixedVolume) {
             return;
         }
@@ -1310,7 +1317,7 @@
                 mPendingVolumeCommand = new StreamVolumeCommand(
                                                     streamType, index, flags, device);
             } else {
-                onSetStreamVolume(streamType, index, flags, device);
+                onSetStreamVolume(streamType, index, flags, device, caller);
                 index = mStreamStates[streamType].getIndex(device);
             }
         }
@@ -1442,10 +1449,11 @@
     private void setStreamVolumeInt(int streamType,
                                     int index,
                                     int device,
-                                    boolean force) {
+                                    boolean force,
+                                    String caller) {
         VolumeStreamState streamState = mStreamStates[streamType];
 
-        if (streamState.setIndex(index, device) || force) {
+        if (streamState.setIndex(index, device, caller) || force) {
             // Post message to set system volume (it in turn will post a message
             // to persist).
             sendMsg(mAudioHandler,
@@ -1874,7 +1882,7 @@
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
                 } else {
-                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid);
+                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
                 }
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
@@ -1904,8 +1912,8 @@
     }
 
     /** @see AudioManager#setMode(int) */
-    public void setMode(int mode, IBinder cb) {
-        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ")"); }
+    public void setMode(int mode, IBinder cb, String callingPackage) {
+        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
@@ -1928,7 +1936,7 @@
             if (mode == AudioSystem.MODE_CURRENT) {
                 mode = mMode;
             }
-            newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid());
+            newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode
@@ -1940,8 +1948,9 @@
     // must be called synchronized on mSetModeDeathHandlers
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
-    private int setModeInt(int mode, IBinder cb, int pid) {
-        if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ")"); }
+    private int setModeInt(int mode, IBinder cb, int pid, String caller) {
+        if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
+                + caller + ")"); }
         int newModeOwnerPid = 0;
         if (cb == null) {
             Log.e(TAG, "setModeInt() called with null binder");
@@ -2021,9 +2030,9 @@
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
             int device = getDeviceForStream(streamType);
             int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
-            setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true);
+            setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller);
 
-            updateStreamVolumeAlias(true /*updateVolumes*/);
+            updateStreamVolumeAlias(true /*updateVolumes*/, caller);
         }
         return newModeOwnerPid;
     }
@@ -2278,7 +2287,7 @@
                     Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT),
                     0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
-                enforceSafeMediaVolume();
+                enforceSafeMediaVolume(TAG);
             }
         }
     }
@@ -2838,7 +2847,7 @@
         }
     };
 
-    private void onCheckMusicActive() {
+    private void onCheckMusicActive(String caller) {
         synchronized (mSafeMediaVolumeState) {
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
                 int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
@@ -2849,7 +2858,7 @@
                             SENDMSG_REPLACE,
                             0,
                             0,
-                            null,
+                            caller,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                     int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
                     if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
@@ -2857,7 +2866,7 @@
                         // Approximate cumulative active music time
                         mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
-                            setSafeMediaVolumeEnabled(true);
+                            setSafeMediaVolumeEnabled(true, caller);
                             mMusicActiveMs = 0;
                         }
                         saveMusicActiveMs();
@@ -2871,7 +2880,7 @@
         mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
     }
 
-    private void onConfigureSafeVolume(boolean force) {
+    private void onConfigureSafeVolume(boolean force, String caller) {
         synchronized (mSafeMediaVolumeState) {
             int mcc = mContext.getResources().getConfiguration().mcc;
             if ((mMcc != mcc) || ((mMcc == 0) && force)) {
@@ -2893,7 +2902,7 @@
                     if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
                         if (mMusicActiveMs == 0) {
                             mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
-                            enforceSafeMediaVolume();
+                            enforceSafeMediaVolume(caller);
                         } else {
                             // We have existing playback time recorded, already confirmed.
                             mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
@@ -3293,21 +3302,24 @@
      * A class just for packaging up a set of connection parameters.
      */
     private class WiredDeviceConnectionState {
-        public int mType;
-        public int mState;
-        public String mAddress;
-        public String mName;
+        public final int mType;
+        public final int mState;
+        public final String mAddress;
+        public final String mName;
+        public final String mCaller;
 
-        public WiredDeviceConnectionState(int type, int state, String address, String name) {
+        public WiredDeviceConnectionState(int type, int state, String address, String name,
+                String caller) {
             mType = type;
             mState = state;
             mAddress = address;
             mName = name;
+            mCaller = caller;
         }
     }
 
-    public void setWiredDeviceConnectionState(int type, int state, String address,
-            String name) {
+    public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+            String caller) {
         synchronized (mConnectedDevices) {
             if (DEBUG_DEVICES) {
                 Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
@@ -3318,7 +3330,7 @@
                     MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
                     0,
                     0,
-                    new WiredDeviceConnectionState(type, state, address, name),
+                    new WiredDeviceConnectionState(type, state, address, name, caller),
                     delay);
         }
     }
@@ -3482,12 +3494,11 @@
             }
         }
 
-        public boolean adjustIndex(int deltaIndex, int device) {
-            return setIndex(getIndex(device) + deltaIndex,
-                            device);
+        public boolean adjustIndex(int deltaIndex, int device, String caller) {
+            return setIndex(getIndex(device) + deltaIndex, device, caller);
         }
 
-        public boolean setIndex(int index, int device) {
+        public boolean setIndex(int index, int device, String caller) {
             boolean changed = false;
             int oldIndex;
             synchronized (VolumeStreamState.class) {
@@ -3511,11 +3522,10 @@
                         if (streamType != mStreamType &&
                                 mStreamVolumeAlias[streamType] == mStreamType) {
                             int scaledIndex = rescaleIndex(index, mStreamType, streamType);
-                            mStreamStates[streamType].setIndex(scaledIndex,
-                                                               device);
+                            mStreamStates[streamType].setIndex(scaledIndex, device, caller);
                             if (currentDevice) {
                                 mStreamStates[streamType].setIndex(scaledIndex,
-                                                                   getDeviceForStream(streamType));
+                                        getDeviceForStream(streamType), caller);
                             }
                         }
                     }
@@ -3524,6 +3534,15 @@
             if (changed) {
                 oldIndex = (oldIndex + 5) / 10;
                 index = (index + 5) / 10;
+                // log base stream changes to the event log
+                if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+                    if (caller == null) {
+                        Log.w(TAG, "No caller for volume_changed event", new Throwable());
+                    }
+                    EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
+                            caller);
+                }
+                // fire changed intents for all streams
                 mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
                 mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
                 sendBroadcastToAll(mVolumeChanged);
@@ -3546,7 +3565,7 @@
             return mIndexMax;
         }
 
-        public void setAllIndexes(VolumeStreamState srcStream) {
+        public void setAllIndexes(VolumeStreamState srcStream, String caller) {
             synchronized (VolumeStreamState.class) {
                 int srcStreamType = srcStream.getStreamType();
                 // apply default device volume from source stream to all devices first in case
@@ -3563,7 +3582,7 @@
                     index = srcMap.valueAt(i);
                     index = rescaleIndex(index, srcStreamType, mStreamType);
 
-                    setIndex(index, device);
+                    setIndex(index, device, caller);
                 }
             }
         }
@@ -4160,7 +4179,7 @@
                     {   WiredDeviceConnectionState connectState =
                             (WiredDeviceConnectionState)msg.obj;
                         onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
-                                connectState.mAddress, connectState.mName);
+                                connectState.mAddress, connectState.mName, connectState.mCaller);
                         mAudioEventWakeLock.release();
                     }
                     break;
@@ -4196,7 +4215,7 @@
                 }
 
                 case MSG_CHECK_MUSIC_ACTIVE:
-                    onCheckMusicActive();
+                    onCheckMusicActive((String) msg.obj);
                     break;
 
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -4205,7 +4224,8 @@
 
                 case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
                 case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
-                    onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED));
+                    onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
+                            (String) msg.obj);
                     break;
                 case MSG_PERSIST_SAFE_VOLUME_STATE:
                     onPersistSafeVolumeState(msg.arg1);
@@ -4592,11 +4612,13 @@
     }
 
     private void onSetWiredDeviceConnectionState(int device, int state, String address,
-            String deviceName) {
+            String deviceName, String caller) {
         if (DEBUG_DEVICES) {
-            Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device) +
-                    " state:" + Integer.toHexString(state) + " address:" + address +
-                    " deviceName:" + deviceName + ");");
+            Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
+                    + " state:" + Integer.toHexString(state)
+                    + " address:" + address
+                    + " deviceName:" + deviceName
+                    + " caller: " + caller + ");");
         }
 
         synchronized (mConnectedDevices) {
@@ -4621,7 +4643,7 @@
                             SENDMSG_REPLACE,
                             0,
                             0,
-                            null,
+                            caller,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                 }
                 // Television devices without CEC service apply software volume on HDMI output
@@ -4974,7 +4996,7 @@
                     SENDMSG_REPLACE,
                     0,
                     0,
-                    null,
+                    TAG,
                     0);
 
             boolean cameraSoundForced = readCameraSoundForced();
@@ -4994,7 +5016,7 @@
                             mRingerModeAffectedStreams &=
                                     ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
                         } else {
-                            s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
+                            s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG);
                             mRingerModeAffectedStreams |=
                                     (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
                         }
@@ -5159,13 +5181,13 @@
     private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
     private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;  // 30s after boot completed
 
-    private void setSafeMediaVolumeEnabled(boolean on) {
+    private void setSafeMediaVolumeEnabled(boolean on, String caller) {
         synchronized (mSafeMediaVolumeState) {
             if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
                     (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
                 if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
-                    enforceSafeMediaVolume();
+                    enforceSafeMediaVolume(caller);
                 } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
                     mMusicActiveMs = 1;  // nonzero = confirmed
@@ -5175,14 +5197,14 @@
                             SENDMSG_REPLACE,
                             0,
                             0,
-                            null,
+                            caller,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                 }
             }
         }
     }
 
-    private void enforceSafeMediaVolume() {
+    private void enforceSafeMediaVolume(String caller) {
         VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
         int devices = mSafeMediaVolumeDevices;
         int i = 0;
@@ -5194,7 +5216,7 @@
             }
             int index = streamState.getIndex(device);
             if (index > mSafeMediaVolumeIndex) {
-                streamState.setIndex(mSafeMediaVolumeIndex, device);
+                streamState.setIndex(mSafeMediaVolumeIndex, device, caller);
                 sendMsg(mAudioHandler,
                         MSG_SET_DEVICE_VOLUME,
                         SENDMSG_QUEUE,
@@ -5220,15 +5242,16 @@
     }
 
     @Override
-    public void disableSafeMediaVolume() {
+    public void disableSafeMediaVolume(String callingPackage) {
         enforceVolumeController("disable the safe media volume");
         synchronized (mSafeMediaVolumeState) {
-            setSafeMediaVolumeEnabled(false);
+            setSafeMediaVolumeEnabled(false, callingPackage);
             if (mPendingVolumeCommand != null) {
                 onSetStreamVolume(mPendingVolumeCommand.mStreamType,
                                   mPendingVolumeCommand.mIndex,
                                   mPendingVolumeCommand.mFlags,
-                                  mPendingVolumeCommand.mDevice);
+                                  mPendingVolumeCommand.mDevice,
+                                  callingPackage);
                 mPendingVolumeCommand = null;
             }
         }
@@ -5604,19 +5627,21 @@
                 String callingPackage, int uid) {
             // direction and stream type swap here because the public
             // adjustSuggested has a different order than the other methods.
-            adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, uid);
+            adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage,
+                    callingPackage, uid);
         }
 
         @Override
         public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
                 String callingPackage, int uid) {
-            adjustStreamVolume(streamType, direction, flags, callingPackage, uid);
+            adjustStreamVolume(streamType, direction, flags, callingPackage,
+                    callingPackage, uid);
         }
 
         @Override
         public void setStreamVolumeForUid(int streamType, int direction, int flags,
                 String callingPackage, int uid) {
-            setStreamVolume(streamType, direction, flags, callingPackage, uid);
+            setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 2ac6ccc..b398f41 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -320,6 +320,12 @@
             checkPermission(USE_FINGERPRINT);
             return mHalDeviceId != 0;
         }
+
+        @Override
+        public void rename(int fpId, String name) {
+            checkPermission(MANAGE_FINGERPRINT);
+            // TODO
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 72205d6..a530dfa 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -880,7 +880,7 @@
                 try {
                     String packageName = getContext().getOpPackageName();
                     mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
-                            flags, packageName);
+                            flags, packageName, TAG);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error adjusting default volume.", e);
                 }
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
index b8a3155..b99c436 100644
--- a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -25,7 +25,11 @@
 import android.content.res.Resources;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.Build;
+import android.os.Handler;
 import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
 import android.view.Display;
 
 import com.android.server.LocalServices;
@@ -37,7 +41,7 @@
     private static final String TAG = "BurnInProtection";
 
     // Default value when max burnin radius is not set.
-    public static final int BURN_IN_RADIUS_MAX_DEFAULT = -1;
+    public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1;
 
     private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
     private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10);
@@ -74,23 +78,19 @@
             updateBurnInProtection();
         }
     };
-
-    public BurnInProtectionHelper(Context context) {
+    
+    public BurnInProtectionHelper(Context context, int minHorizontalOffset,
+            int maxHorizontalOffset, int minVerticalOffset, int maxVerticalOffset,
+            int maxOffsetRadius) {
         final Resources resources = context.getResources();
-        mMinHorizontalBurnInOffset = resources.getInteger(
-                com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
-        mMaxHorizontalBurnInOffset = resources.getInteger(
-                com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
-        mMinVerticalBurnInOffset = resources.getInteger(
-                com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
-        mMaxVerticalBurnInOffset = resources.getInteger(
-                com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
-        int burnInRadiusMax = resources.getInteger(
-                com.android.internal.R.integer.config_burnInProtectionMaxRadius);
-        if (burnInRadiusMax != BURN_IN_RADIUS_MAX_DEFAULT) {
-            mBurnInRadiusMaxSquared = burnInRadiusMax * burnInRadiusMax;
+        mMinHorizontalBurnInOffset = minHorizontalOffset;
+        mMaxHorizontalBurnInOffset = maxHorizontalOffset;
+        mMinVerticalBurnInOffset = minVerticalOffset;
+        mMaxVerticalBurnInOffset = maxHorizontalOffset;
+        if (maxOffsetRadius != BURN_IN_MAX_RADIUS_DEFAULT) {
+            mBurnInRadiusMaxSquared = maxOffsetRadius * maxOffsetRadius;
         } else {
-            mBurnInRadiusMaxSquared = BURN_IN_RADIUS_MAX_DEFAULT;
+            mBurnInRadiusMaxSquared = BURN_IN_MAX_RADIUS_DEFAULT;
         }
 
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -175,7 +175,7 @@
                 }
             }
             // If we are outside of the radius, let's try again.
-        } while (mBurnInRadiusMaxSquared != BURN_IN_RADIUS_MAX_DEFAULT
+        } while (mBurnInRadiusMaxSquared != BURN_IN_MAX_RADIUS_DEFAULT
                 && mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset
                         > mBurnInRadiusMaxSquared);
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 62e7af4..cdd6c7f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -49,6 +49,7 @@
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.media.session.MediaSessionLegacyHelper;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.FactoryTest;
@@ -94,6 +95,7 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -1185,6 +1187,12 @@
         }
     };
 
+    private boolean isRoundWindow() {
+        return mContext.getResources().getBoolean(com.android.internal.R.bool.config_windowIsRound)
+                || (Build.HARDWARE.contains("goldfish")
+                && SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false));
+    }
+
     /** {@inheritDoc} */
     @Override
     public void init(Context context, IWindowManager windowManager,
@@ -1194,9 +1202,40 @@
         mWindowManagerFuncs = windowManagerFuncs;
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
-        if (context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableBurnInProtection)){
-            mBurnInProtectionHelper = new BurnInProtectionHelper(context);
+
+        // Init display burn-in protection
+        boolean burnInProtectionEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableBurnInProtection);
+        // Allow a system property to override this. Used by developer settings.
+        boolean burnInProtectionDevMode =
+                SystemProperties.getBoolean("persist.debug.force_burn_in", false);
+        if (burnInProtectionEnabled || burnInProtectionDevMode) {
+            final int minHorizontal;
+            final int maxHorizontal;
+            final int minVertical;
+            final int maxVertical;
+            final int maxRadius;
+            if (burnInProtectionDevMode) {
+                minHorizontal = -8;
+                maxHorizontal = 8;
+                minVertical = -8;
+                maxVertical = -4;
+                maxRadius = (isRoundWindow()) ? 6 : -1;
+            } else {
+                Resources resources = context.getResources();
+                minHorizontal = resources.getInteger(
+                        com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
+                maxHorizontal = resources.getInteger(
+                        com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
+                minVertical = resources.getInteger(
+                        com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
+                maxVertical = resources.getInteger(
+                        com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
+                maxRadius = resources.getInteger(
+                        com.android.internal.R.integer.config_burnInProtectionMaxRadius);
+            }
+            mBurnInProtectionHelper = new BurnInProtectionHelper(
+                    context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
         }
 
         mHandler = new PolicyHandler();
@@ -4872,7 +4911,7 @@
             case KeyEvent.KEYCODE_VOLUME_UP:
                 try {
                     getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
-                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
+                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
                 }
@@ -4880,7 +4919,7 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
                 try {
                     getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
-                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
+                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
                 }
@@ -4890,7 +4929,7 @@
                     if (event.getRepeatCount() == 0) {
                         getAudioService().adjustSuggestedStreamVolume(
                                 AudioManager.ADJUST_TOGGLE_MUTE,
-                                AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
+                                AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
                     }
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ef70895..4c06cbe 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -977,7 +977,9 @@
                             + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
                             + " transit=" + transit + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
-            a = createClipRevealAnimationLocked(transit, enter, appWidth, appHeight);
+            a = createClipRevealAnimationLocked(transit, enter,
+                    containingFrame.right - containingFrame.left,
+                    containingFrame.bottom - containingFrame.top);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                             + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 89ed5b7..ac1b0f1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1139,6 +1139,8 @@
                     mShownAlpha *= appTransformation.getAlpha();
                     if (appTransformation.hasClipRect()) {
                         mClipRect.set(appTransformation.getClipRect());
+                        // Account for non-fullscreen windows
+                        mClipRect.offset(frame.left, frame.top);
                         if (mWin.mHScale > 0) {
                             mClipRect.left /= mWin.mHScale;
                             mClipRect.right /= mWin.mHScale;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index b0e30b5..78fbe16 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -189,7 +189,7 @@
                             " addr:" + address + " name:" + audioDevice.mDeviceName);
                 }
                 mAudioService.setWiredDeviceConnectionState(
-                        device, state, address, audioDevice.mDeviceName);
+                        device, state, address, audioDevice.mDeviceName, TAG);
             }
 
             // Capture Device
@@ -198,7 +198,7 @@
                         AudioSystem.DEVICE_IN_USB_ACCESSORY :
                         AudioSystem.DEVICE_IN_USB_DEVICE);
                 mAudioService.setWiredDeviceConnectionState(
-                        device, state, address, audioDevice.mDeviceName);
+                        device, state, address, audioDevice.mDeviceName, TAG);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index f927965..725f393 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -121,7 +121,7 @@
 
         int outputCount = mOutputStreams.length;
         mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
-                properties, MidiDeviceInfo.TYPE_USB);
+                properties, MidiDeviceInfo.TYPE_USB, null);
         if (mServer == null) {
             return false;
         }