Merge "Code cleaning" into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index 73decfb..2bd4234 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1015,6 +1015,7 @@
     field public static final int selectAllOnFocus = 16843102; // 0x101015e
     field public static final int selectable = 16843238; // 0x10101e6
     field public static final int selectableItemBackground = 16843534; // 0x101030e
+    field public static final int selectableItemBackgroundBorderless = 16843867; // 0x101045b
     field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
     field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
     field public static final int sessionService = 16843841; // 0x1010441
@@ -5029,6 +5030,11 @@
     method public boolean[] supportsCommands(java.lang.String[]);
   }
 
+  public static class VoiceInteractor.AbortVoiceRequest extends android.app.VoiceInteractor.Request {
+    ctor public VoiceInteractor.AbortVoiceRequest(java.lang.CharSequence, android.os.Bundle);
+    method public void onAbortResult(android.os.Bundle);
+  }
+
   public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request {
     ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle);
     method public void onCommandResult(android.os.Bundle);
@@ -6902,6 +6908,7 @@
     method public abstract java.io.File[] getExternalCacheDirs();
     method public abstract java.io.File getExternalFilesDir(java.lang.String);
     method public abstract java.io.File[] getExternalFilesDirs(java.lang.String);
+    method public abstract java.io.File[] getExternalMediaDirs();
     method public abstract java.io.File getFileStreamPath(java.lang.String);
     method public abstract java.io.File getFilesDir();
     method public abstract android.os.Looper getMainLooper();
@@ -7070,6 +7077,7 @@
     method public java.io.File[] getExternalCacheDirs();
     method public java.io.File getExternalFilesDir(java.lang.String);
     method public java.io.File[] getExternalFilesDirs(java.lang.String);
+    method public java.io.File[] getExternalMediaDirs();
     method public java.io.File getFileStreamPath(java.lang.String);
     method public java.io.File getFilesDir();
     method public android.os.Looper getMainLooper();
@@ -11463,6 +11471,7 @@
   }
 
   public class RippleDrawable extends android.graphics.drawable.LayerDrawable {
+    ctor public RippleDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
   }
 
   public class RotateDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
@@ -17145,86 +17154,6 @@
     method public void setWorkSource(android.os.WorkSource);
   }
 
-  public class WifiScanner {
-    method public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.HotspotInfo[]);
-    method public void resetHotlist(android.net.wifi.WifiScanner.HotlistListener);
-    method public void retrieveScanResults(boolean, android.net.wifi.WifiScanner.ScanListener);
-    method public void setHotlist(android.net.wifi.WifiScanner.HotspotInfo[], int, android.net.wifi.WifiScanner.HotlistListener);
-    method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
-    method public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
-    method public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener);
-    method public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
-    field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000
-    field public static final int MIN_SCAN_PERIOD_MS = 2000; // 0x7d0
-    field public static final int REASON_CONFLICTING_REQUEST = -4; // 0xfffffffc
-    field public static final int REASON_INVALID_LISTENER = -2; // 0xfffffffe
-    field public static final int REASON_INVALID_REQUEST = -3; // 0xfffffffd
-    field public static final int REASON_SUCCEEDED = 0; // 0x0
-    field public static final int REASON_UNSPECIFIED = -1; // 0xffffffff
-    field public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; // 0x0
-    field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1
-    field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2
-    field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
-    field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
-    field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4
-    field public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; // 0x6
-    field public static final int WIFI_BAND_BOTH = 3; // 0x3
-    field public static final int WIFI_BAND_BOTH_WITH_DFS = 7; // 0x7
-    field public static final int WIFI_BAND_UNSPECIFIED = 0; // 0x0
-  }
-
-  public static class WifiScanner.ChannelSpec {
-    ctor public WifiScanner.ChannelSpec(int);
-    field public int frequency;
-  }
-
-  public static class WifiScanner.FullScanResult implements android.os.Parcelable {
-    ctor public WifiScanner.FullScanResult();
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public android.net.wifi.WifiScanner.InformationElement[] informationElements;
-    field public android.net.wifi.ScanResult result;
-  }
-
-  public static abstract interface WifiScanner.HotlistListener {
-    method public abstract void onFound(android.net.wifi.ScanResult[]);
-  }
-
-  public static class WifiScanner.HotspotInfo {
-    ctor public WifiScanner.HotspotInfo();
-    field public java.lang.String bssid;
-    field public int frequencyHint;
-    field public int high;
-    field public int low;
-  }
-
-  public static class WifiScanner.InformationElement {
-    ctor public WifiScanner.InformationElement();
-    field public byte[] bytes;
-    field public int id;
-  }
-
-  public static abstract interface WifiScanner.ScanListener {
-    method public abstract void onFullResult(android.net.wifi.WifiScanner.FullScanResult);
-    method public abstract void onPeriodChanged(int);
-    method public abstract void onResults(android.net.wifi.ScanResult[]);
-  }
-
-  public static class WifiScanner.ScanSettings implements android.os.Parcelable {
-    ctor public WifiScanner.ScanSettings();
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public int band;
-    field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
-    field public int periodInMs;
-    field public int reportEvents;
-  }
-
-  public static abstract interface WifiScanner.WifiChangeListener {
-    method public abstract void onChanging(android.net.wifi.ScanResult[]);
-    method public abstract void onQuiescence(android.net.wifi.ScanResult[]);
-  }
-
   public class WpsInfo implements android.os.Parcelable {
     ctor public WpsInfo();
     ctor public WpsInfo(android.net.wifi.WpsInfo);
@@ -26193,16 +26122,17 @@
     method public android.view.LayoutInflater getLayoutInflater();
     method public android.app.Dialog getWindow();
     method public void hideWindow();
+    method public void onAbortVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
     method public void onBackPressed();
     method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request);
     method public void onCloseSystemDialogs();
     method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
     method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets);
-    method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
+    method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
     method public void onCreate(android.os.Bundle);
     method public android.view.View onCreateContentView();
     method public void onDestroy();
-    method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]);
+    method public boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]);
     method public boolean onKeyDown(int, android.view.KeyEvent);
     method public boolean onKeyLongPress(int, android.view.KeyEvent);
     method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -26229,6 +26159,7 @@
   }
 
   public static class VoiceInteractionSession.Request {
+    method public void sendAbortVoiceResult(android.os.Bundle);
     method public void sendCancelResult();
     method public void sendCommandResult(boolean, android.os.Bundle);
     method public void sendConfirmResult(boolean, android.os.Bundle);
@@ -28268,6 +28199,7 @@
     method public java.io.File[] getExternalCacheDirs();
     method public java.io.File getExternalFilesDir(java.lang.String);
     method public java.io.File[] getExternalFilesDirs(java.lang.String);
+    method public java.io.File[] getExternalMediaDirs();
     method public java.io.File getFileStreamPath(java.lang.String);
     method public java.io.File getFilesDir();
     method public android.os.Looper getMainLooper();
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index f05f4c7..d4c4318 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -26,6 +26,7 @@
 import android.util.AttributeSet;
 import android.view.ActionMode;
 import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -1013,6 +1014,26 @@
         return null;
     }
 
+    /** @hide */
+    public boolean openOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean invalidateOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    /** @hide */
+    public boolean collapseActionView() {
+        return false;
+    }
+
     /**
      * Listener interface for ActionBar navigation events.
      *
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5257430..23b5f29 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2075,15 +2075,16 @@
      * <p>In order to use a Toolbar within the Activity's window content the application
      * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
      *
-     * @param actionBar Toolbar to set as the Activity's action bar
+     * @param toolbar Toolbar to set as the Activity's action bar
      */
-    public void setActionBar(@Nullable Toolbar actionBar) {
+    public void setActionBar(@Nullable Toolbar toolbar) {
         if (getActionBar() instanceof WindowDecorActionBar) {
             throw new IllegalStateException("This Activity already has an action bar supplied " +
                     "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
                     "android:windowActionBar to false in your theme to use a Toolbar instead.");
         }
-        mActionBar = new ToolbarActionBar(actionBar);
+        mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
+        mActionBar.invalidateOptionsMenu();
     }
     
     /**
@@ -2449,6 +2450,10 @@
      * but you can override this to do whatever you want.
      */
     public void onBackPressed() {
+        if (mActionBar != null && mActionBar.collapseActionView()) {
+            return;
+        }
+
         if (!mFragments.popBackStackImmediate()) {
             finishAfterTransition();
         }
@@ -2660,6 +2665,14 @@
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
         onUserInteraction();
+
+        // Let action bars open menus in response to the menu key prioritized over
+        // the window handling it
+        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
+                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+            return true;
+        }
+
         Window win = getWindow();
         if (win.superDispatchKeyEvent(event)) {
             return true;
@@ -2907,7 +2920,9 @@
      * time it needs to be displayed.
      */
     public void invalidateOptionsMenu() {
-        mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+        if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) {
+            mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+        }
     }
     
     /**
@@ -3117,7 +3132,9 @@
      * open, this method does nothing.
      */
     public void openOptionsMenu() {
-        mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+        if (mActionBar == null || !mActionBar.openOptionsMenu()) {
+            mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+        }
     }
     
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ff8688d..e66534b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -58,7 +58,9 @@
 import android.hardware.SerialManager;
 import android.hardware.SystemSensorManager;
 import android.hardware.hdmi.HdmiCecManager;
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.IHdmiCecService;
+import android.hardware.hdmi.IHdmiControlService;
 import android.hardware.camera2.CameraManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputManager;
@@ -249,6 +251,8 @@
     private File[] mExternalFilesDirs;
     @GuardedBy("mSync")
     private File[] mExternalCacheDirs;
+    @GuardedBy("mSync")
+    private File[] mExternalMediaDirs;
 
     private static final String[] EMPTY_FILE_LIST = {};
 
@@ -386,6 +390,11 @@
                     return new HdmiCecManager(IHdmiCecService.Stub.asInterface(b));
                 }});
 
+        registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() {
+                public Object createStaticService() {
+                    IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE);
+                    return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
+                }});
 
         registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
@@ -1032,6 +1041,18 @@
     }
 
     @Override
+    public File[] getExternalMediaDirs() {
+        synchronized (mSync) {
+            if (mExternalMediaDirs == null) {
+                mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+            }
+
+            // Create dirs if needed
+            return ensureDirsExistOrFilter(mExternalMediaDirs);
+        }
+    }
+
+    @Override
     public File getFileStreamPath(String name) {
         return makeFilename(getFilesDir(), name);
     }
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index fe85ef4..f332c9d 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -33,7 +33,26 @@
 import java.util.ArrayList;
 
 /**
- * Interface for an {@link Activity} to interact with the user through voice.
+ * Interface for an {@link Activity} to interact with the user through voice.  Use
+ * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
+ * to retrieve the interface, if the activity is currently involved in a voice interaction.
+ *
+ * <p>The voice interactor revolves around submitting voice interaction requests to the
+ * back-end voice interaction service that is working with the user.  These requests are
+ * submitted with {@link #submitRequest}, providing a new instance of a
+ * {@link Request} subclass describing the type of operation to perform -- currently the
+ * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}.
+ *
+ * <p>Once a request is submitted, the voice system will process it and evetually deliver
+ * the result to the request object.  The application can cancel a pending request at any
+ * time.
+ *
+ * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that
+ * if an activity is being restarted with retained state, it will retain the current
+ * VoiceInteractor and any outstanding requests.  Because of this, you should always use
+ * {@link Request#getActivity() Request.getActivity} to get back to the activity of a
+ * request, rather than holding on to the actvitity instance yourself, either explicitly
+ * or implicitly through a non-static inner class.
  */
 public class VoiceInteractor {
     static final String TAG = "VoiceInteractor";
@@ -62,6 +81,16 @@
                         request.clear();
                     }
                     break;
+                case MSG_ABORT_VOICE_RESULT:
+                    request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+                    if (DEBUG) Log.d(TAG, "onAbortVoice: req="
+                            + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+                            + " result=" + args.arg1);
+                    if (request != null) {
+                        ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2);
+                        request.clear();
+                    }
+                    break;
                 case MSG_COMMAND_RESULT:
                     request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
                     if (DEBUG) Log.d(TAG, "onCommandResult: req="
@@ -96,6 +125,12 @@
         }
 
         @Override
+        public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+                    MSG_ABORT_VOICE_RESULT, request, result));
+        }
+
+        @Override
         public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
                 Bundle result) {
             mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
@@ -112,8 +147,9 @@
     final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
 
     static final int MSG_CONFIRMATION_RESULT = 1;
-    static final int MSG_COMMAND_RESULT = 2;
-    static final int MSG_CANCEL_RESULT = 3;
+    static final int MSG_ABORT_VOICE_RESULT = 2;
+    static final int MSG_COMMAND_RESULT = 3;
+    static final int MSG_CANCEL_RESULT = 4;
 
     public static abstract class Request {
         IVoiceInteractorRequest mRequestInterface;
@@ -188,9 +224,42 @@
 
         IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
                 IVoiceInteractorCallback callback) throws RemoteException {
-            return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+            return interactor.startConfirmation(packageName, callback, mPrompt, mExtras);
         }
-   }
+    }
+
+    public static class AbortVoiceRequest extends Request {
+        final CharSequence mMessage;
+        final Bundle mExtras;
+
+        /**
+         * Reports that the current interaction can not be complete with voice, so the
+         * application will need to switch to a traditional input UI.  Applications should
+         * only use this when they need to completely bail out of the voice interaction
+         * and switch to a traditional UI.  When the resonsponse comes back, the voice
+         * system has handled the request and is ready to switch; at that point the application
+         * can start a new non-voice activity.  Be sure when starting the new activity
+         * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+         * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+         * interaction task.
+         *
+         * @param message Optional message to tell user about not being able to complete
+         * the interaction with voice.
+         * @param extras Additional optional information.
+         */
+        public AbortVoiceRequest(CharSequence message, Bundle extras) {
+            mMessage = message;
+            mExtras = extras;
+        }
+
+        public void onAbortResult(Bundle result) {
+        }
+
+        IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+                IVoiceInteractorCallback callback) throws RemoteException {
+            return interactor.startAbortVoice(packageName, callback, mMessage, mExtras);
+        }
+    }
 
     public static class CommandRequest extends Request {
         final String mCommand;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2ff85c6..d3a979c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
 import android.os.StatFs;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.MediaStore;
 import android.util.AttributeSet;
 import android.view.DisplayAdjustments;
 import android.view.Display;
@@ -929,6 +930,40 @@
     public abstract File[] getExternalCacheDirs();
 
     /**
+     * Returns absolute paths to application-specific directories on all
+     * external storage devices where the application can place media files.
+     * These files are scanned and made available to other apps through
+     * {@link MediaStore}.
+     * <p>
+     * This is like {@link #getExternalFilesDirs} in that these files will be
+     * deleted when the application is uninstalled, however there are some
+     * important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
+     * External storage devices returned here are considered a permanent part of
+     * the device, including both emulated external storage and physical media
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
+     * <p>
+     * An application may store data on any or all of the returned devices. For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
+     * <p>
+     * No permissions are required to read or write to the returned paths; they
+     * are always accessible to the calling app. Write access outside of these
+     * paths on secondary external storage devices is not available.
+     * <p>
+     * Returned paths may be {@code null} if a storage device is unavailable.
+     *
+     * @see Environment#getExternalStorageState(File)
+     */
+    public abstract File[] getExternalMediaDirs();
+
+    /**
      * Returns an array of strings naming the private files associated with
      * this Context's application package.
      *
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index c66355b..dbf9122 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -237,6 +237,11 @@
     }
 
     @Override
+    public File[] getExternalMediaDirs() {
+        return mBase.getExternalMediaDirs();
+    }
+
+    @Override
     public File getDir(String name, int mode) {
         return mBase.getDir(name, mode);
     }
diff --git a/core/java/android/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java
index cd86cd8..dcb3624 100644
--- a/core/java/android/hardware/hdmi/HdmiCecClient.java
+++ b/core/java/android/hardware/hdmi/HdmiCecClient.java
@@ -69,44 +69,28 @@
      * Send &lt;Active Source&gt; message.
      */
     public void sendActiveSource() {
-        try {
-            mService.sendActiveSource(mBinder);
-        } catch (RemoteException e) {
-            Log.e(TAG, "sendActiveSource threw exception ", e);
-        }
+        Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
     }
 
     /**
      * Send &lt;Inactive Source&gt; message.
      */
     public void sendInactiveSource() {
-        try {
-            mService.sendInactiveSource(mBinder);
-        } catch (RemoteException e) {
-            Log.e(TAG, "sendInactiveSource threw exception ", e);
-        }
+        Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
     }
 
     /**
      * Send &lt;Text View On&gt; message.
      */
     public void sendTextViewOn() {
-        try {
-            mService.sendTextViewOn(mBinder);
-        } catch (RemoteException e) {
-            Log.e(TAG, "sendTextViewOn threw exception ", e);
-        }
+        Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
     }
 
     /**
      * Send &lt;Image View On&gt; message.
      */
     public void sendImageViewOn() {
-        try {
-            mService.sendImageViewOn(mBinder);
-        } catch (RemoteException e) {
-            Log.e(TAG, "sendImageViewOn threw exception ", e);
-        }
+        Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
     }
 
     /**
@@ -116,11 +100,7 @@
      *        {@link HdmiCec#ADDR_TV}.
      */
     public void sendGiveDevicePowerStatus(int address) {
-        try {
-            mService.sendGiveDevicePowerStatus(mBinder, address);
-        } catch (RemoteException e) {
-            Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e);
-        }
+        Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
     }
 
     /**
@@ -133,11 +113,7 @@
      * @return true if TV is on; otherwise false.
      */
     public boolean isTvOn() {
-        try {
-            return mService.isTvOn(mBinder);
-        } catch (RemoteException e) {
-            Log.e(TAG, "isTvOn threw exception ", e);
-        }
-        return false;
+        Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
+        return true;
     }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java
index 10b058c..03c46d8 100644
--- a/core/java/android/hardware/hdmi/HdmiCecManager.java
+++ b/core/java/android/hardware/hdmi/HdmiCecManager.java
@@ -45,15 +45,7 @@
      * @return {@link HdmiCecClient} instance. {@code null} on failure.
      */
     public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) {
-        if (mService == null) {
-            return null;
-        }
-        try {
-            IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener));
-            return HdmiCecClient.create(mService, b);
-        } catch (RemoteException e) {
-            return null;
-        }
+        return HdmiCecClient.create(mService, null);
     }
 
     private IHdmiCecListener getListenerWrapper(final HdmiCecClient.Listener listener) {
diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java
index ddaf870..62fa279 100644
--- a/core/java/android/hardware/hdmi/HdmiCecMessage.java
+++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java
@@ -46,7 +46,7 @@
     public HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
         mSource = source;
         mDestination = destination;
-        mOpcode = opcode;
+        mOpcode = opcode & 0xFF;
         mParams = Arrays.copyOf(params, params.length);
     }
 
@@ -123,6 +123,7 @@
          * @param p HdmiCecMessage object to read the Rating from
          * @return a new HdmiCecMessage created from the data in the parcel
          */
+        @Override
         public HdmiCecMessage createFromParcel(Parcel p) {
             int source = p.readInt();
             int destination = p.readInt();
@@ -131,6 +132,7 @@
             p.readByteArray(params);
             return new HdmiCecMessage(source, destination, opcode, params);
         }
+        @Override
         public HdmiCecMessage[] newArray(int size) {
             return new HdmiCecMessage[size];
         }
@@ -139,11 +141,40 @@
     @Override
     public String toString() {
         StringBuffer s = new StringBuffer();
-        s.append(String.format("src: %d dst: %d op: %2X params: ", mSource, mDestination, mOpcode));
-        for (byte data : mParams) {
-            s.append(String.format("%02X ", data));
+        s.append(String.format("<%s> src: %d, dst: %d",
+                opcodeToString(mOpcode), mSource, mDestination));
+        if (mParams.length > 0) {
+            s.append(", params:");
+            for (byte data : mParams) {
+                s.append(String.format(" %02X", data));
+            }
         }
         return s.toString();
     }
+
+    private static String opcodeToString(int opcode) {
+        switch (opcode) {
+            case HdmiCec.MESSAGE_FEATURE_ABORT:
+                return "Feature Abort";
+            case HdmiCec.MESSAGE_CEC_VERSION:
+                return "CEC Version";
+            case HdmiCec.MESSAGE_REQUEST_ARC_INITIATION:
+                return "Request ARC Initiation";
+            case HdmiCec.MESSAGE_REQUEST_ARC_TERMINATION:
+                return "Request ARC Termination";
+            case HdmiCec.MESSAGE_REPORT_ARC_INITIATED:
+                return "Report ARC Initiated";
+            case HdmiCec.MESSAGE_REPORT_ARC_TERMINATED:
+                return "Report ARC Terminated";
+            case HdmiCec.MESSAGE_TEXT_VIEW_ON:
+                return "Text View On";
+            case HdmiCec.MESSAGE_ACTIVE_SOURCE:
+                return "Active Source";
+            case HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS:
+                return "Give Device Power Status";
+            default:
+                return String.format("Opcode: %02X", opcode);
+        }
+    }
 }
 
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
index 83da29a..f0bd237 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -90,7 +90,7 @@
     public void queryDisplayStatus(DisplayStatusCallback callback) {
         // TODO: PendingResult.
         try {
-            mService.oneTouchPlay(getCallbackWrapper(callback));
+            mService.queryDisplayStatus(getCallbackWrapper(callback));
         } catch (RemoteException e) {
             Log.e(TAG, "queryDisplayStatus threw exception ", e);
         }
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e98a26b..e84b695 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -191,6 +191,10 @@
             return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
         }
 
+        public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
+            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+        }
+
         public File[] buildExternalStorageAppObbDirs(String packageName) {
             return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
         }
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 5e005d0..d66fc0f 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,10 @@
 
 package android.preference;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.media.Ringtone;
@@ -45,11 +48,14 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final H mUiHandler = new H();
     private final Callback mCallback;
     private final Uri mDefaultUri;
     private final AudioManager mAudioManager;
     private final int mStreamType;
     private final int mMaxStreamVolume;
+    private final Receiver mReceiver = new Receiver();
+    private final Observer mVolumeObserver;
 
     private int mOriginalStreamVolume;
     private Ringtone mRingtone;
@@ -63,17 +69,6 @@
     private static final int MSG_INIT_SAMPLE = 3;
     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
 
-    private ContentObserver mVolumeObserver = new ContentObserver(new Handler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            if (mSeekBar != null && mAudioManager != null) {
-                int volume = mAudioManager.getStreamVolume(mStreamType);
-                mSeekBar.setProgress(volume);
-            }
-        }
-    };
-
     public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
             Callback callback) {
         mContext = context;
@@ -85,10 +80,11 @@
         mHandler = new Handler(thread.getLooper(), this);
         mCallback = callback;
         mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
+        mVolumeObserver = new Observer(mHandler);
         mContext.getContentResolver().registerContentObserver(
                 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
                 false, mVolumeObserver);
-
+        mReceiver.setListening(true);
         if (defaultUri == null) {
             if (mStreamType == AudioManager.STREAM_RING) {
                 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
@@ -103,6 +99,9 @@
     }
 
     public void setSeekBar(SeekBar seekBar) {
+        if (mSeekBar != null) {
+            mSeekBar.setOnSeekBarChangeListener(null);
+        }
         mSeekBar = seekBar;
         mSeekBar.setOnSeekBarChangeListener(null);
         mSeekBar.setMax(mMaxStreamVolume);
@@ -150,7 +149,11 @@
                 mCallback.onSampleStarting(this);
             }
             if (mRingtone != null) {
-                mRingtone.play();
+                try {
+                    mRingtone.play();
+                } catch (Throwable e) {
+                    Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
+                }
             }
         }
     }
@@ -172,6 +175,8 @@
         postStopSample();
         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
         mSeekBar.setOnSeekBarChangeListener(null);
+        mReceiver.setListening(false);
+        mHandler.getLooper().quitSafely();
     }
 
     public void revertVolume() {
@@ -252,4 +257,62 @@
             postSetVolume(mLastProgress);
         }
     }
-}
\ No newline at end of file
+
+    private final class H extends Handler {
+        private static final int UPDATE_SLIDER = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == UPDATE_SLIDER) {
+                if (mSeekBar != null) {
+                    mSeekBar.setProgress(msg.arg1);
+                    mLastProgress = mSeekBar.getProgress();
+                }
+            }
+        }
+
+        public void postUpdateSlider(int volume) {
+            obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
+        }
+    }
+
+    private final class Observer extends ContentObserver {
+        public Observer(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            if (mSeekBar != null && mAudioManager != null) {
+                final int volume = mAudioManager.getStreamVolume(mStreamType);
+                mUiHandler.postUpdateSlider(volume);
+            }
+        }
+    }
+
+    private final class Receiver extends BroadcastReceiver {
+        private boolean mListening;
+
+        public void setListening(boolean listening) {
+            if (mListening == listening) return;
+            mListening = listening;
+            if (listening) {
+                final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+                mContext.registerReceiver(this, filter);
+            } else {
+                mContext.unregisterReceiver(this);
+            }
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return;
+            final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+            final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+            if (mSeekBar != null && streamType == mStreamType && streamValue != -1) {
+                mUiHandler.postUpdateSlider(streamValue);
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 1e29f8e..2e9077a 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -47,9 +47,22 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
+import java.lang.ref.WeakReference;
+
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+/**
+ * An active voice interaction session, providing a facility for the implementation
+ * to interact with the user in the voice interaction layer.  This interface is no shown
+ * by default, but you can request that it be shown with {@link #showWindow()}, which
+ * will result in a later call to {@link #onCreateContentView()} in which the UI can be
+ * built
+ *
+ * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
+ * when done.  It can also initiate voice interactions with applications by calling
+ * {@link #startVoiceActivity}</p>.
+ */
 public abstract class VoiceInteractionSession implements KeyEvent.Callback {
     static final String TAG = "VoiceInteractionSession";
     static final boolean DEBUG = true;
@@ -80,11 +93,14 @@
     final Insets mTmpInsets = new Insets();
     final int[] mTmpLocation = new int[2];
 
+    final WeakReference<VoiceInteractionSession> mWeakRef
+            = new WeakReference<VoiceInteractionSession>(this);
+
     final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
         @Override
         public IVoiceInteractorRequest startConfirmation(String callingPackage,
-                IVoiceInteractorCallback callback, String prompt, Bundle extras) {
-            Request request = findRequest(callback, true);
+                IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) {
+            Request request = newRequest(callback);
             mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
                     new Caller(callingPackage, Binder.getCallingUid()), request,
                     prompt, extras));
@@ -92,9 +108,19 @@
         }
 
         @Override
+        public IVoiceInteractorRequest startAbortVoice(String callingPackage,
+                IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
+            Request request = newRequest(callback);
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE,
+                    new Caller(callingPackage, Binder.getCallingUid()), request,
+                    message, extras));
+            return request.mInterface;
+        }
+
+        @Override
         public IVoiceInteractorRequest startCommand(String callingPackage,
                 IVoiceInteractorCallback callback, String command, Bundle extras) {
-            Request request = findRequest(callback, true);
+            Request request = newRequest(callback);
             mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
                     new Caller(callingPackage, Binder.getCallingUid()), request,
                     command, extras));
@@ -143,29 +169,60 @@
         final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
             @Override
             public void cancel() throws RemoteException {
-                mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+                VoiceInteractionSession session = mSession.get();
+                if (session != null) {
+                    session.mHandlerCaller.sendMessage(
+                            session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+                }
             }
         };
         final IVoiceInteractorCallback mCallback;
-        final HandlerCaller mHandlerCaller;
-        Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+        final WeakReference<VoiceInteractionSession> mSession;
+
+        Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) {
             mCallback = callback;
-            mHandlerCaller = handlerCaller;
+            mSession = session.mWeakRef;
+        }
+
+        void finishRequest() {
+            VoiceInteractionSession session = mSession.get();
+            if (session == null) {
+                throw new IllegalStateException("VoiceInteractionSession has been destroyed");
+            }
+            Request req = session.removeRequest(mInterface.asBinder());
+            if (req == null) {
+                throw new IllegalStateException("Request not active: " + this);
+            } else if (req != this) {
+                throw new IllegalStateException("Current active request " + req
+                        + " not same as calling request " + this);
+            }
         }
 
         public void sendConfirmResult(boolean confirmed, Bundle result) {
             try {
                 if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
                         + " confirmed=" + confirmed + " result=" + result);
+                finishRequest();
                 mCallback.deliverConfirmationResult(mInterface, confirmed, result);
             } catch (RemoteException e) {
             }
         }
 
+        public void sendAbortVoiceResult(Bundle result) {
+            try {
+                if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+                        + " result=" + result);
+                finishRequest();
+                mCallback.deliverAbortVoiceResult(mInterface, result);
+            } catch (RemoteException e) {
+            }
+        }
+
         public void sendCommandResult(boolean complete, Bundle result) {
             try {
                 if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
                         + " result=" + result);
+                finishRequest();
                 mCallback.deliverCommandResult(mInterface, complete, result);
             } catch (RemoteException e) {
             }
@@ -174,6 +231,7 @@
         public void sendCancelResult() {
             try {
                 if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+                finishRequest();
                 mCallback.deliverCancel(mInterface);
             } catch (RemoteException e) {
             }
@@ -191,9 +249,10 @@
     }
 
     static final int MSG_START_CONFIRMATION = 1;
-    static final int MSG_START_COMMAND = 2;
-    static final int MSG_SUPPORTS_COMMANDS = 3;
-    static final int MSG_CANCEL = 4;
+    static final int MSG_START_ABORT_VOICE = 2;
+    static final int MSG_START_COMMAND = 3;
+    static final int MSG_SUPPORTS_COMMANDS = 4;
+    static final int MSG_CANCEL = 5;
 
     static final int MSG_TASK_STARTED = 100;
     static final int MSG_TASK_FINISHED = 101;
@@ -209,9 +268,16 @@
                     args = (SomeArgs)msg.obj;
                     if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
                             + " prompt=" + args.arg3 + " extras=" + args.arg4);
-                    onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3,
+                    onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
                             (Bundle)args.arg4);
                     break;
+                case MSG_START_ABORT_VOICE:
+                    args = (SomeArgs)msg.obj;
+                    if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
+                            + " message=" + args.arg3 + " extras=" + args.arg4);
+                    onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3,
+                            (Bundle) args.arg4);
+                    break;
                 case MSG_START_COMMAND:
                     args = (SomeArgs)msg.obj;
                     if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
@@ -329,18 +395,20 @@
                 mCallbacks, true);
     }
 
-    Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+    Request newRequest(IVoiceInteractorCallback callback) {
         synchronized (this) {
-            Request req = mActiveRequests.get(callback.asBinder());
+            Request req = new Request(callback, this);
+            mActiveRequests.put(req.mInterface.asBinder(), req);
+            return req;
+        }
+    }
+
+    Request removeRequest(IBinder reqInterface) {
+        synchronized (this) {
+            Request req = mActiveRequests.get(reqInterface);
             if (req != null) {
-                if (newRequest) {
-                    throw new IllegalArgumentException("Given request callback " + callback
-                            + " is already active");
-                }
-                return req;
+                mActiveRequests.remove(req);
             }
-            req = new Request(callback, mHandlerCaller);
-            mActiveRequests.put(callback.asBinder(), req);
             return req;
         }
     }
@@ -425,6 +493,27 @@
         mTheme = theme;
     }
 
+    /**
+     * Ask that a new activity be started for voice interaction.  This will create a
+     * new dedicated task in the activity manager for this voice interaction session;
+     * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
+     * will be set for you to make it a new task.
+     *
+     * <p>The newly started activity will be displayed to the user in a special way, as
+     * a layer under the voice interaction UI.</p>
+     *
+     * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
+     * through which it can perform voice interactions through your session.  These requests
+     * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
+     * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}.
+     *
+     * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
+     * and {@link #onTaskFinished} when the last activity has finished.
+     *
+     * @param intent The Intent to start this voice interaction.  The given Intent will
+     * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
+     * this is part of a voice interaction.
+     */
     public void startVoiceActivity(Intent intent) {
         if (mToken == null) {
             throw new IllegalStateException("Can't call before onCreate()");
@@ -439,14 +528,23 @@
         }
     }
 
+    /**
+     * Convenience for inflating views.
+     */
     public LayoutInflater getLayoutInflater() {
         return mInflater;
     }
 
+    /**
+     * Retrieve the window being used to show the session's UI.
+     */
     public Dialog getWindow() {
         return mWindow;
     }
 
+    /**
+     * Finish the session.
+     */
     public void finish() {
         if (mToken == null) {
             throw new IllegalStateException("Can't call before onCreate()");
@@ -458,6 +556,12 @@
         }
     }
 
+    /**
+     * Initiatize a new session.
+     *
+     * @param args The arguments that were supplied to
+     * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}.
+     */
     public void onCreate(Bundle args) {
         mTheme = mTheme != 0 ? mTheme
                 : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
@@ -472,9 +576,15 @@
         mWindow.setToken(mToken);
     }
 
+    /**
+     * Last callback to the session as it is being finished.
+     */
     public void onDestroy() {
     }
 
+    /**
+     * Hook in which to create the session's UI.
+     */
     public View onCreateContentView() {
         return null;
     }
@@ -507,6 +617,11 @@
         finish();
     }
 
+    /**
+     * Sessions automatically watch for requests that all system UI be closed (such as when
+     * the user presses HOME), which will appear here.  The default implementation always
+     * calls {@link #finish}.
+     */
     public void onCloseSystemDialogs() {
         finish();
     }
@@ -530,15 +645,98 @@
         outInsets.touchableRegion.setEmpty();
     }
 
+    /**
+     * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
+     * has actually started.
+     *
+     * @param intent The original {@link Intent} supplied to
+     * {@link #startVoiceActivity(android.content.Intent)}.
+     * @param taskId Unique ID of the now running task.
+     */
     public void onTaskStarted(Intent intent, int taskId) {
     }
 
+    /**
+     * Called when the last activity of a task initiated by
+     * {@link #startVoiceActivity(android.content.Intent)} has finished.  The default
+     * implementation calls {@link #finish()} on the assumption that this represents
+     * the completion of a voice action.  You can override the implementation if you would
+     * like a different behavior.
+     *
+     * @param intent The original {@link Intent} supplied to
+     * {@link #startVoiceActivity(android.content.Intent)}.
+     * @param taskId Unique ID of the finished task.
+     */
     public void onTaskFinished(Intent intent, int taskId) {
         finish();
     }
 
-    public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
-    public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+    /**
+     * Request to query for what extended commands the session supports.
+     *
+     * @param caller Who is making the request.
+     * @param commands An array of commands that are being queried.
+     * @return Return an array of booleans indicating which of each entry in the
+     * command array is supported.  A true entry in the array indicates the command
+     * is supported; false indicates it is not.  The default implementation returns
+     * an array of all false entries.
+     */
+    public boolean[] onGetSupportedCommands(Caller caller, String[] commands) {
+        return new boolean[commands.length];
+    }
+
+    /**
+     * Request to confirm with the user before proceeding with an unrecoverable operation,
+     * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
+     * VoiceInteractor.ConfirmationRequest}.
+     *
+     * @param caller Who is making the request.
+     * @param request The active request.
+     * @param prompt The prompt informing the user of what will happen, as per
+     * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+     * @param extras Any additional information, as per
+     * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+     */
+    public abstract void onConfirm(Caller caller, Request request, CharSequence prompt,
+            Bundle extras);
+
+    /**
+     * Request to abort the voice interaction session because the voice activity can not
+     * complete its interaction using voice.  Corresponds to
+     * {@link android.app.VoiceInteractor.AbortVoiceRequest
+     * VoiceInteractor.AbortVoiceRequest}.  The default implementation just sends an empty
+     * confirmation back to allow the activity to exit.
+     *
+     * @param caller Who is making the request.
+     * @param request The active request.
+     * @param message The message informing the user of the problem, as per
+     * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+     * @param extras Any additional information, as per
+     * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+     */
+    public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+        request.sendAbortVoiceResult(null);
+    }
+
+    /**
+     * Process an arbitrary extended command from the caller,
+     * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
+     * VoiceInteractor.CommandRequest}.
+     *
+     * @param caller Who is making the request.
+     * @param request The active request.
+     * @param command The command that is being executed, as per
+     * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+     * @param extras Any additional information, as per
+     * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+     */
     public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+
+    /**
+     * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
+     * that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
+     *
+     * @param request The request that is being canceled.
+     */
     public abstract void onCancel(Request request);
 }
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
index ac83356..ef89c68 100644
--- a/core/java/android/tv/ITvInputClient.aidl
+++ b/core/java/android/tv/ITvInputClient.aidl
@@ -17,6 +17,7 @@
 package android.tv;
 
 import android.content.ComponentName;
+import android.os.Bundle;
 import android.tv.ITvInputSession;
 import android.view.InputChannel;
 
@@ -29,4 +30,6 @@
     void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
     void onAvailabilityChanged(in String inputId, boolean isAvailable);
     void onSessionReleased(int seq);
+    void onSessionEvent(in String name, in Bundle args, int seq);
+    void onVideoSizeChanged(int width, int height, int seq);
 }
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl
index a2bd0d7..e27b8bf 100644
--- a/core/java/android/tv/ITvInputSessionCallback.aidl
+++ b/core/java/android/tv/ITvInputSessionCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.tv;
 
+import android.os.Bundle;
 import android.tv.ITvInputSession;
 
 /**
@@ -25,4 +26,6 @@
  */
 oneway interface ITvInputSessionCallback {
     void onSessionCreated(ITvInputSession session);
+    void onSessionEvent(in String name, in Bundle args);
+    void onVideoSizeChanged(int width, int height);
 }
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
index dfa84f8..d0c2ca6 100644
--- a/core/java/android/tv/TvInputManager.java
+++ b/core/java/android/tv/TvInputManager.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -85,6 +86,29 @@
          */
         public void onSessionReleased(Session session) {
         }
+
+        /**
+         * This is called at the beginning of the playback of a channel and later when the size of
+         * the video has been changed.
+         *
+         * @param session A {@link TvInputManager.Session} associated with this callback
+         * @param width the width of the video
+         * @param height the height of the video
+         * @hide
+         */
+        public void onVideoSizeChanged(Session session, int width, int height) {
+        }
+
+        /**
+         * This is called when a custom event has been sent from this session.
+         *
+         * @param session A {@link TvInputManager.Session} associated with this callback
+         * @param eventType The type of the event.
+         * @param eventArgs Optional arguments of the event.
+         * @hide
+         */
+        public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
+        }
     }
 
     private static final class SessionCallbackRecord {
@@ -116,6 +140,24 @@
                 }
             });
         }
+
+        public void postVideoSizeChanged(final int width, final int height) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onVideoSizeChanged(mSession, width, height);
+                }
+            });
+        }
+
+        public void postSessionEvent(final String eventType, final Bundle eventArgs) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
+                }
+            });
+        }
     }
 
     /**
@@ -196,6 +238,30 @@
             }
 
             @Override
+            public void onVideoSizeChanged(int width, int height, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postVideoSizeChanged(width, height);
+                }
+            }
+
+            @Override
+            public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postSessionEvent(eventType, eventArgs);
+                }
+            }
+
+            @Override
             public void onAvailabilityChanged(String inputId, boolean isAvailable) {
                 synchronized (mTvInputListenerRecordsMap) {
                     List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
index cb0142f..03d24db 100644
--- a/core/java/android/tv/TvInputService.java
+++ b/core/java/android/tv/TvInputService.java
@@ -23,6 +23,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -156,6 +157,7 @@
         private boolean mOverlayViewEnabled;
         private IBinder mWindowToken;
         private Rect mOverlayFrame;
+        private ITvInputSessionCallback mSessionCallback;
 
         public TvInputSessionImpl() {
             mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
@@ -188,6 +190,52 @@
         }
 
         /**
+         * Dispatches an event to the application using this session.
+         *
+         * @param eventType The type of the event.
+         * @param eventArgs Optional arguments of the event.
+         * @hide
+         */
+        public void dispatchSessionEvent(final String eventType, final Bundle eventArgs) {
+            if (eventType == null) {
+                throw new IllegalArgumentException("eventType should not be null.");
+            }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "dispatchSessionEvent(" + eventType + ")");
+                        mSessionCallback.onSessionEvent(eventType, eventArgs);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in sending event (event=" + eventType + ")");
+                    }
+                }
+            });
+        }
+
+        /**
+         * Sends the change on the size of the video. This is expected to be called at the
+         * beginning of the playback and later when the size has been changed.
+         *
+         * @param width The width of the video.
+         * @param height The height of the video.
+         * @hide
+         */
+        public void dispatchVideoSizeChanged(final int width, final int height) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged");
+                        mSessionCallback.onVideoSizeChanged(width, height);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in dispatchVideoSizeChanged");
+                    }
+                }
+            });
+        }
+
+        /**
          * Called when the session is released.
          */
         public abstract void onRelease();
@@ -394,9 +442,7 @@
                 mWindowManager.removeView(mOverlayView);
                 mOverlayView = null;
             }
-            if (DEBUG) {
-                Log.d(TAG, "create overlay view(" + frame + ")");
-            }
+            if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
             mWindowToken = windowToken;
             mOverlayFrame = frame;
             if (!mOverlayViewEnabled) {
@@ -431,9 +477,7 @@
          * @param frame A new position of the overlay view.
          */
         void relayoutOverlayView(Rect frame) {
-            if (DEBUG) {
-                Log.d(TAG, "relayout overlay view(" + frame + ")");
-            }
+            if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
             mOverlayFrame = frame;
             if (!mOverlayViewEnabled || mOverlayView == null) {
                 return;
@@ -449,9 +493,7 @@
          * Removes the current overlay view.
          */
         void removeOverlayView(boolean clearWindowToken) {
-            if (DEBUG) {
-                Log.d(TAG, "remove overlay view(" + mOverlayView + ")");
-            }
+            if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
             if (clearWindowToken) {
                 mWindowToken = null;
                 mOverlayFrame = null;
@@ -498,6 +540,10 @@
             mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
             return Session.DISPATCH_IN_PROGRESS;
         }
+
+        private void setSessionCallback(ITvInputSessionCallback callback) {
+            mSessionCallback = callback;
+        }
     }
 
     private final class ServiceHandler extends Handler {
@@ -517,6 +563,7 @@
                             // Failed to create a session.
                             cb.onSessionCreated(null);
                         } else {
+                            sessionImpl.setSessionCallback(cb);
                             ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
                                     sessionImpl, channel);
                             cb.onSessionCreated(stub);
diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java
index 59b6386..2d31701 100644
--- a/core/java/android/tv/TvView.java
+++ b/core/java/android/tv/TvView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.text.TextUtils;
 import android.tv.TvInputManager.Session;
@@ -379,5 +380,23 @@
                 mExternalCallback.onSessionReleased(session);
             }
         }
+
+        @Override
+        public void onVideoSizeChanged(Session session, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
+            }
+            if (mExternalCallback != null) {
+                mExternalCallback.onVideoSizeChanged(session, width, height);
+            }
+        }
+
+        @Override
+        public void onSessionEvent(TvInputManager.Session session, String eventType,
+                Bundle eventArgs) {
+            if (mExternalCallback != null) {
+                mExternalCallback.onSessionEvent(session, eventType, eventArgs);
+            }
+        }
     }
 }
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index e918119..4979059 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -219,6 +219,15 @@
         return mTarget;
     }
 
+    /**
+     * WARNING: May only be called once!!!
+     * TODO: Fix above -_-
+     */
+    public void setStartValue(float startValue) {
+        checkMutable();
+        nSetStartValue(mNativePtr.get(), startValue);
+    }
+
     @Override
     public void setStartDelay(long startDelay) {
         checkMutable();
@@ -282,11 +291,12 @@
     }
 
     private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
-            int property, float deltaValue);
+            int property, float finalValue);
     private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis,
-            long canvasProperty, float deltaValue);
+            long canvasProperty, float finalValue);
     private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
-            long canvasProperty, int paintField, float deltaValue);
+            long canvasProperty, int paintField, float finalValue);
+    private static native void nSetStartValue(long nativePtr, float startValue);
     private static native void nSetDuration(long nativePtr, long duration);
     private static native long nGetDuration(long nativePtr);
     private static native void nSetStartDelay(long nativePtr, long startDelay);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index d45d686..2b4677c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1199,6 +1199,9 @@
 
     /**
      * Notifies the keyguard to start fading out.
+     *
+     * @param startTime the start time of the animation in uptime milliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
-    public void startKeyguardExitAnimation(long fadeoutDuration);
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c9eb130..9a46052 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2495,17 +2495,25 @@
     }
 
     /**
-     * Positions the selector in a way that mimics keyboard focus. If the
-     * selector drawable supports hotspots, this manages the focus hotspot.
+     * Positions the selector in a way that mimics keyboard focus.
      */
     void positionSelectorLikeFocus(int position, View sel) {
+        // If we're changing position, update the visibility since the selector
+        // is technically being detached from the previous selection.
+        final Drawable selector = mSelector;
+        final boolean manageState = selector != null && mSelectorPosition != position
+                && position != INVALID_POSITION;
+        if (manageState) {
+            selector.setVisible(false, false);
+        }
+
         positionSelector(position, sel);
 
-        final Drawable selector = mSelector;
-        if (selector != null && position != INVALID_POSITION) {
+        if (manageState) {
             final Rect bounds = mSelectorRect;
             final float x = bounds.exactCenterX();
             final float y = bounds.exactCenterY();
+            selector.setVisible(getVisibility() == VISIBLE, false);
             selector.setHotspot(x, y);
         }
     }
@@ -2520,8 +2528,18 @@
         if (sel instanceof SelectionBoundsAdjuster) {
             ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
         }
-        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
-                selectorRect.bottom);
+
+        // Adjust for selection padding.
+        selectorRect.left -= mSelectionLeftPadding;
+        selectorRect.top -= mSelectionTopPadding;
+        selectorRect.right += mSelectionRightPadding;
+        selectorRect.bottom += mSelectionBottomPadding;
+
+        // Update the selector drawable.
+        final Drawable selector = mSelector;
+        if (selector != null) {
+            selector.setBounds(selectorRect);
+        }
 
         final boolean isChildViewEnabled = mIsChildViewEnabled;
         if (sel.isEnabled() != isChildViewEnabled) {
@@ -2532,11 +2550,6 @@
         }
     }
 
-    private void positionSelector(int l, int t, int r, int b) {
-        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
-                + mSelectionRightPadding, b + mSelectionBottomPadding);
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         int saveCount = 0;
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index a9a5eae..acee592 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -570,9 +570,9 @@
             mMenu = new MenuBuilder(context);
             mMenu.setCallback(new MenuBuilderCallback());
             mPresenter = new ActionMenuPresenter(context);
-            mPresenter.setMenuView(this);
             mPresenter.setCallback(new ActionMenuPresenterCallback());
             mMenu.addMenuPresenter(mPresenter);
+            mPresenter.setMenuView(this);
         }
 
         return mMenu;
@@ -652,6 +652,11 @@
         return false;
     }
 
+    /** @hide */
+    public void setExpandedActionViewsExclusive(boolean exclusive) {
+        mPresenter.setExpandedActionViewsExclusive(exclusive);
+    }
+
     /**
      * Interface responsible for receiving menu item click events if the items themselves
      * do not have individual item click listeners.
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index f35ca27..419c582 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -127,6 +127,8 @@
     // Clear me after use.
     private final ArrayList<View> mTempViews = new ArrayList<View>();
 
+    private final int[] mTempMargins = new int[2];
+
     private OnMenuItemClickListener mOnMenuItemClickListener;
 
     private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
@@ -220,7 +222,7 @@
 
         final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
         if (!TextUtils.isEmpty(subtitle)) {
-            setSubtitle(title);
+            setSubtitle(subtitle);
         }
         a.recycle();
     }
@@ -703,10 +705,23 @@
      * @return The toolbar's Menu
      */
     public Menu getMenu() {
-        ensureMenuView();
+        ensureMenu();
         return mMenuView.getMenu();
     }
 
+    private void ensureMenu() {
+        ensureMenuView();
+        if (mMenuView.peekMenu() == null) {
+            // Initialize a new menu for the first time.
+            final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
+            if (mExpandedMenuPresenter == null) {
+                mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+            }
+            mMenuView.setExpandedActionViewsExclusive(true);
+            menu.addMenuPresenter(mExpandedMenuPresenter);
+        }
+    }
+
     private void ensureMenuView() {
         if (mMenuView == null) {
             mMenuView = new ActionMenuView(getContext());
@@ -928,12 +943,49 @@
         child.measure(childWidthSpec, childHeightSpec);
     }
 
+    /**
+     * Returns the width + uncollapsed margins
+     */
+    private int measureChildCollapseMargins(View child,
+            int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int leftDiff = lp.leftMargin - collapsingMargins[0];
+        final int rightDiff = lp.rightMargin - collapsingMargins[1];
+        final int leftMargin = Math.max(0, leftDiff);
+        final int rightMargin = Math.max(0, rightDiff);
+        final int hMargins = leftMargin + rightMargin;
+        collapsingMargins[0] = Math.max(0, -leftDiff);
+        collapsingMargins[1] = Math.max(0, -rightDiff);
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+                        + heightUsed, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        return child.getMeasuredWidth() + hMargins;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = 0;
         int height = 0;
         int childState = 0;
 
+        final int[] collapsingMargins = mTempMargins;
+        final int marginStartIndex;
+        final int marginEndIndex;
+        if (isLayoutRtl()) {
+            marginStartIndex = 1;
+            marginEndIndex = 0;
+        } else {
+            marginStartIndex = 0;
+            marginEndIndex = 1;
+        }
+
         // System views measure first.
 
         int navWidth = 0;
@@ -956,7 +1008,9 @@
             childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
         }
 
-        width += Math.max(getContentInsetStart(), navWidth);
+        final int contentInsetStart = getContentInsetStart();
+        width += Math.max(contentInsetStart, navWidth);
+        collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
 
         int menuWidth = 0;
         if (shouldLayout(mMenuView)) {
@@ -968,21 +1022,21 @@
             childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
         }
 
-        width += Math.max(getContentInsetEnd(), menuWidth);
+        final int contentInsetEnd = getContentInsetEnd();
+        width += Math.max(contentInsetEnd, menuWidth);
+        collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
 
         if (shouldLayout(mExpandedActionView)) {
-            measureChildWithMargins(mExpandedActionView, widthMeasureSpec, width,
-                    heightMeasureSpec, 0);
-            width += mExpandedActionView.getMeasuredWidth() +
-                    getHorizontalMargins(mExpandedActionView);
+            width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, collapsingMargins);
             height = Math.max(height, mExpandedActionView.getMeasuredHeight() +
                     getVerticalMargins(mExpandedActionView));
             childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState());
         }
 
         if (shouldLayout(mLogoView)) {
-            measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0);
-            width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView);
+            width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, collapsingMargins);
             height = Math.max(height, mLogoView.getMeasuredHeight() +
                     getVerticalMargins(mLogoView));
             childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
@@ -993,17 +1047,18 @@
         final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
         final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
         if (shouldLayout(mTitleTextView)) {
-            measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins,
-                    heightMeasureSpec, titleVertMargins);
+            titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec,
+                    width + titleHorizMargins, heightMeasureSpec, titleVertMargins,
+                    collapsingMargins);
             titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
             titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
             childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState());
         }
         if (shouldLayout(mSubtitleTextView)) {
-            measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins,
-                    heightMeasureSpec, titleHeight + titleVertMargins);
-            titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() +
-                    getHorizontalMargins(mSubtitleTextView));
+            titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView,
+                    widthMeasureSpec, width + titleHorizMargins,
+                    heightMeasureSpec, titleHeight + titleVertMargins,
+                    collapsingMargins));
             titleHeight += mSubtitleTextView.getMeasuredHeight() +
                     getVerticalMargins(mSubtitleTextView);
             childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
@@ -1021,8 +1076,8 @@
                 continue;
             }
 
-            measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0);
-            width += child.getMeasuredWidth() + getHorizontalMargins(child);
+            width += measureChildCollapseMargins(child, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, collapsingMargins);
             height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
             childState = combineMeasuredStates(childState, child.getMeasuredState());
         }
@@ -1053,46 +1108,51 @@
         int left = paddingLeft;
         int right = width - paddingRight;
 
+        final int[] collapsingMargins = mTempMargins;
+        collapsingMargins[0] = collapsingMargins[1] = 0;
+
         if (shouldLayout(mNavButtonView)) {
             if (isRtl) {
-                right = layoutChildRight(mNavButtonView, right);
+                right = layoutChildRight(mNavButtonView, right, collapsingMargins);
             } else {
-                left = layoutChildLeft(mNavButtonView, left);
+                left = layoutChildLeft(mNavButtonView, left, collapsingMargins);
             }
         }
 
         if (shouldLayout(mCollapseButtonView)) {
             if (isRtl) {
-                right = layoutChildRight(mCollapseButtonView, right);
+                right = layoutChildRight(mCollapseButtonView, right, collapsingMargins);
             } else {
-                left = layoutChildLeft(mCollapseButtonView, left);
+                left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins);
             }
         }
 
         if (shouldLayout(mMenuView)) {
             if (isRtl) {
-                left = layoutChildLeft(mMenuView, left);
+                left = layoutChildLeft(mMenuView, left, collapsingMargins);
             } else {
-                right = layoutChildRight(mMenuView, right);
+                right = layoutChildRight(mMenuView, right, collapsingMargins);
             }
         }
 
+        collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
+        collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
         left = Math.max(left, getContentInsetLeft());
         right = Math.min(right, width - paddingRight - getContentInsetRight());
 
         if (shouldLayout(mExpandedActionView)) {
             if (isRtl) {
-                right = layoutChildRight(mExpandedActionView, right);
+                right = layoutChildRight(mExpandedActionView, right, collapsingMargins);
             } else {
-                left = layoutChildLeft(mExpandedActionView, left);
+                left = layoutChildLeft(mExpandedActionView, left, collapsingMargins);
             }
         }
 
         if (shouldLayout(mLogoView)) {
             if (isRtl) {
-                right = layoutChildRight(mLogoView, right);
+                right = layoutChildRight(mLogoView, right, collapsingMargins);
             } else {
-                left = layoutChildLeft(mLogoView, left);
+                left = layoutChildLeft(mLogoView, left, collapsingMargins);
             }
         }
 
@@ -1141,48 +1201,52 @@
                     break;
             }
             if (isRtl) {
+                final int rd = mTitleMarginStart - collapsingMargins[1];
+                right -= Math.max(0, rd);
+                collapsingMargins[1] = Math.max(0, -rd);
                 int titleRight = right;
                 int subtitleRight = right;
+
                 if (layoutTitle) {
                     final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
-                    titleRight -= lp.rightMargin + mTitleMarginStart;
                     final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
                     final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
                     mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
-                    titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd;
+                    titleRight = titleLeft - mTitleMarginEnd;
                     titleTop = titleBottom + lp.bottomMargin;
                 }
                 if (layoutSubtitle) {
                     final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
-                    subtitleRight -= lp.rightMargin + mTitleMarginStart;
                     titleTop += lp.topMargin;
                     final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
                     final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
                     mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
-                    subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd;
+                    subtitleRight = subtitleRight - mTitleMarginEnd;
                     titleTop = subtitleBottom + lp.bottomMargin;
                 }
                 right = Math.max(titleRight, subtitleRight);
             } else {
+                final int ld = mTitleMarginStart - collapsingMargins[0];
+                left += Math.max(0, ld);
+                collapsingMargins[0] = Math.max(0, -ld);
                 int titleLeft = left;
                 int subtitleLeft = left;
+
                 if (layoutTitle) {
                     final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
-                    titleLeft += lp.leftMargin + mTitleMarginStart;
                     final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
                     final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
                     mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
-                    titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd;
+                    titleLeft = titleRight + mTitleMarginEnd;
                     titleTop = titleBottom + lp.bottomMargin;
                 }
                 if (layoutSubtitle) {
                     final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
-                    subtitleLeft += lp.leftMargin + mTitleMarginStart;
                     titleTop += lp.topMargin;
                     final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
                     final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
                     mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
-                    subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd;
+                    subtitleLeft = subtitleRight + mTitleMarginEnd;
                     titleTop = subtitleBottom + lp.bottomMargin;
                 }
                 left = Math.max(titleLeft, subtitleLeft);
@@ -1195,19 +1259,19 @@
         addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
         final int leftViewsCount = mTempViews.size();
         for (int i = 0; i < leftViewsCount; i++) {
-            left = layoutChildLeft(mTempViews.get(i), left);
+            left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins);
         }
 
         addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
         final int rightViewsCount = mTempViews.size();
         for (int i = 0; i < rightViewsCount; i++) {
-            right = layoutChildRight(mTempViews.get(i), right);
+            right = layoutChildRight(mTempViews.get(i), right, collapsingMargins);
         }
 
         // Centered views try to center with respect to the whole bar, but views pinned
         // to the left or right can push the mass of centered views to one side or the other.
         addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);
-        final int centerViewsWidth = getViewListMeasuredWidth(mTempViews);
+        final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
         final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
         final int halfCenterViewsWidth = centerViewsWidth / 2;
         int centerLeft = parentCenter - halfCenterViewsWidth;
@@ -1220,25 +1284,35 @@
 
         final int centerViewsCount = mTempViews.size();
         for (int i = 0; i < centerViewsCount; i++) {
-            centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft);
+            centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins);
         }
         mTempViews.clear();
     }
 
-    private int getViewListMeasuredWidth(List<View> views) {
+    private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) {
+        int collapseLeft = collapsingMargins[0];
+        int collapseRight = collapsingMargins[1];
         int width = 0;
         final int count = views.size();
         for (int i = 0; i < count; i++) {
             final View v = views.get(i);
             final LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+            final int l = lp.leftMargin - collapseLeft;
+            final int r = lp.rightMargin - collapseRight;
+            final int leftMargin = Math.max(0, l);
+            final int rightMargin = Math.max(0, r);
+            collapseLeft = Math.max(0, -l);
+            collapseRight = Math.max(0, -r);
+            width += leftMargin + v.getMeasuredWidth() + rightMargin;
         }
         return width;
     }
 
-    private int layoutChildLeft(View child, int left) {
+    private int layoutChildLeft(View child, int left, int[] collapsingMargins) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        left += lp.leftMargin;
+        final int l = lp.leftMargin - collapsingMargins[0];
+        left += Math.max(0, l);
+        collapsingMargins[0] = Math.max(0, -l);
         final int top = getChildTop(child);
         final int childWidth = child.getMeasuredWidth();
         child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
@@ -1246,9 +1320,11 @@
         return left;
     }
 
-    private int layoutChildRight(View child, int right) {
+    private int layoutChildRight(View child, int right, int[] collapsingMargins) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        right -= lp.rightMargin;
+        final int r = lp.rightMargin - collapsingMargins[1];
+        right -= Math.max(0, r);
+        collapsingMargins[1] = Math.max(0, -r);
         final int top = getChildTop(child);
         final int childWidth = child.getMeasuredWidth();
         child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 737906a..2900595 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -26,7 +26,9 @@
  */
 interface IVoiceInteractor {
     IVoiceInteractorRequest startConfirmation(String callingPackage,
-            IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+            IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+    IVoiceInteractorRequest startAbortVoice(String callingPackage,
+            IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
     IVoiceInteractorRequest startCommand(String callingPackage,
             IVoiceInteractorCallback callback, String command, in Bundle extras);
     boolean[] supportsCommands(String callingPackage, in String[] commands);
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index c6f93e1..8dbf9d4 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,7 @@
 oneway interface IVoiceInteractorCallback {
     void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
             in Bundle result);
+    void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
     void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
     void deliverCancel(IVoiceInteractorRequest request);
 }
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index afb6f7c..6056bf2 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -23,37 +23,50 @@
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.view.ActionMode;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
+import android.view.Window;
 import android.widget.SpinnerAdapter;
 import android.widget.Toolbar;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.DecorToolbar;
+import com.android.internal.widget.ToolbarWidgetWrapper;
 
 import java.util.ArrayList;
-import java.util.Map;
 
 public class ToolbarActionBar extends ActionBar {
     private Toolbar mToolbar;
-    private View mCustomView;
-
-    private int mDisplayOptions;
-
-    private int mNavResId;
-    private int mIconResId;
-    private int mLogoResId;
-    private Drawable mNavDrawable;
-    private Drawable mIconDrawable;
-    private Drawable mLogoDrawable;
-    private int mTitleResId;
-    private int mSubtitleResId;
-    private CharSequence mTitle;
-    private CharSequence mSubtitle;
+    private DecorToolbar mDecorToolbar;
+    private Window.Callback mWindowCallback;
 
     private boolean mLastMenuVisibility;
     private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
             new ArrayList<OnMenuVisibilityListener>();
 
-    public ToolbarActionBar(Toolbar toolbar) {
+    private final Runnable mMenuInvalidator = new Runnable() {
+        @Override
+        public void run() {
+            populateOptionsMenu();
+        }
+    };
+
+    private final Toolbar.OnMenuItemClickListener mMenuClicker =
+            new Toolbar.OnMenuItemClickListener() {
+        @Override
+        public boolean onMenuItemClick(MenuItem item) {
+            return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
+        }
+    };
+
+    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
         mToolbar = toolbar;
+        mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
+        mWindowCallback = windowCallback;
+        toolbar.setOnMenuItemClickListener(mMenuClicker);
+        mDecorToolbar.setWindowTitle(title);
     }
 
     @Override
@@ -63,19 +76,8 @@
 
     @Override
     public void setCustomView(View view, LayoutParams layoutParams) {
-        if (mCustomView != null) {
-            mToolbar.removeView(mCustomView);
-        }
-        mCustomView = view;
-        if (view != null) {
-            mToolbar.addView(view, generateLayoutParams(layoutParams));
-        }
-    }
-
-    private Toolbar.LayoutParams generateLayoutParams(LayoutParams lp) {
-        final Toolbar.LayoutParams result = new Toolbar.LayoutParams(lp);
-        result.gravity = lp.gravity;
-        return result;
+        view.setLayoutParams(layoutParams);
+        mDecorToolbar.setCustomView(view);
     }
 
     @Override
@@ -86,48 +88,22 @@
 
     @Override
     public void setIcon(int resId) {
-        mIconResId = resId;
-        mIconDrawable = null;
-        updateToolbarLogo();
+        mDecorToolbar.setIcon(resId);
     }
 
     @Override
     public void setIcon(Drawable icon) {
-        mIconResId = 0;
-        mIconDrawable = icon;
-        updateToolbarLogo();
+        mDecorToolbar.setIcon(icon);
     }
 
     @Override
     public void setLogo(int resId) {
-        mLogoResId = resId;
-        mLogoDrawable = null;
-        updateToolbarLogo();
+        mDecorToolbar.setLogo(resId);
     }
 
     @Override
     public void setLogo(Drawable logo) {
-        mLogoResId = 0;
-        mLogoDrawable = logo;
-        updateToolbarLogo();
-    }
-
-    private void updateToolbarLogo() {
-        Drawable drawable = null;
-        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
-            final int resId;
-            if ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
-                resId = mLogoResId;
-                drawable = mLogoDrawable;
-            } else {
-                resId = mIconResId;
-                drawable = mIconDrawable;
-            }
-            if (resId != 0) {
-                drawable = mToolbar.getContext().getDrawable(resId);
-            }
-        }
-        mToolbar.setLogo(drawable);
+        mDecorToolbar.setLogo(logo);
     }
 
     @Override
@@ -219,42 +195,22 @@
 
     @Override
     public void setTitle(CharSequence title) {
-        mTitle = title;
-        mTitleResId = 0;
-        updateToolbarTitle();
+        mDecorToolbar.setTitle(title);
     }
 
     @Override
     public void setTitle(int resId) {
-        mTitleResId = resId;
-        mTitle = null;
-        updateToolbarTitle();
+        mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
     }
 
     @Override
     public void setSubtitle(CharSequence subtitle) {
-        mSubtitle = subtitle;
-        mSubtitleResId = 0;
-        updateToolbarTitle();
+        mDecorToolbar.setSubtitle(subtitle);
     }
 
     @Override
     public void setSubtitle(int resId) {
-        mSubtitleResId = resId;
-        mSubtitle = null;
-        updateToolbarTitle();
-    }
-
-    private void updateToolbarTitle() {
-        final Context context = mToolbar.getContext();
-        CharSequence title = null;
-        CharSequence subtitle = null;
-        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
-            title = mTitleResId != 0 ? context.getText(mTitleResId) : mTitle;
-            subtitle = mSubtitleResId != 0 ? context.getText(mSubtitleResId) : mSubtitle;
-        }
-        mToolbar.setTitle(title);
-        mToolbar.setSubtitle(subtitle);
+        mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
     }
 
     @Override
@@ -264,9 +220,8 @@
 
     @Override
     public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
-        final int oldOptions = mDisplayOptions;
-        mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask);
-        final int optionsChanged = oldOptions ^ mDisplayOptions;
+        mDecorToolbar.setDisplayOptions((options & mask) |
+                mDecorToolbar.getDisplayOptions() & ~mask);
     }
 
     @Override
@@ -301,7 +256,7 @@
 
     @Override
     public View getCustomView() {
-        return mCustomView;
+        return mDecorToolbar.getCustomView();
     }
 
     @Override
@@ -327,7 +282,7 @@
 
     @Override
     public int getDisplayOptions() {
-        return mDisplayOptions;
+        return mDecorToolbar.getDisplayOptions();
     }
 
     @Override
@@ -425,6 +380,54 @@
         return mToolbar.getVisibility() == View.VISIBLE;
     }
 
+    @Override
+    public boolean openOptionsMenu() {
+        return mToolbar.showOverflowMenu();
+    }
+
+    @Override
+    public boolean invalidateOptionsMenu() {
+        mToolbar.removeCallbacks(mMenuInvalidator);
+        mToolbar.postOnAnimation(mMenuInvalidator);
+        return true;
+    }
+
+    @Override
+    public boolean collapseActionView() {
+        if (mToolbar.hasExpandedActionView()) {
+            mToolbar.collapseActionView();
+            return true;
+        }
+        return false;
+    }
+
+    void populateOptionsMenu() {
+        final Menu menu = mToolbar.getMenu();
+        final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
+        if (mb != null) {
+            mb.stopDispatchingItemsChanged();
+        }
+        try {
+            menu.clear();
+            if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
+                    !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+                menu.clear();
+            }
+        } finally {
+            if (mb != null) {
+                mb.startDispatchingItemsChanged();
+            }
+        }
+    }
+
+    @Override
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            openOptionsMenu();
+        }
+        return true;
+    }
+
     public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
         mMenuVisibilityListeners.add(listener);
     }
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index f22800c..a5421f5 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -60,6 +60,9 @@
     /**
      * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper
      * and keyguard flag.
+     *
+     * @param startTime the start time of the animation in uptime milliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
-    oneway void startKeyguardExitAnimation(long fadeoutDuration);
+    oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
 }
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index b8dc307..3e15c32 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -24,6 +24,7 @@
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -80,16 +81,20 @@
     public ToolbarWidgetWrapper(Toolbar toolbar) {
         mToolbar = toolbar;
 
+        mTitle = toolbar.getTitle();
+        mSubtitle = toolbar.getSubtitle();
+        mTitleSet = !TextUtils.isEmpty(mTitle);
+
         final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
                 R.styleable.ActionBar, R.attr.actionBarStyle, 0);
 
         final CharSequence title = a.getText(R.styleable.ActionBar_title);
-        if (title != null) {
+        if (!TextUtils.isEmpty(title)) {
             setTitle(title);
         }
 
         final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
-        if (subtitle != null) {
+        if (!TextUtils.isEmpty(subtitle)) {
             setSubtitle(subtitle);
         }
 
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index fedb1b2..a2b1ed9 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -23,6 +23,11 @@
 #define ENCODING_PCM_16BIT 2
 #define ENCODING_PCM_8BIT  3
 #define ENCODING_PCM_FLOAT 4
+#define ENCODING_INVALID 0
+#define ENCODING_DEFAULT 1
+
+#define CHANNEL_INVALID 0
+#define CHANNEL_OUT_DEFAULT 1
 
 static inline audio_format_t audioFormatToNative(int audioFormat)
 {
@@ -33,9 +38,58 @@
         return AUDIO_FORMAT_PCM_8_BIT;
     case ENCODING_PCM_FLOAT:
         return AUDIO_FORMAT_PCM_FLOAT;
+    case ENCODING_DEFAULT:
+        return AUDIO_FORMAT_DEFAULT;
     default:
         return AUDIO_FORMAT_INVALID;
     }
 }
 
+static inline int audioFormatFromNative(audio_format_t nativeFormat)
+{
+    switch (nativeFormat) {
+    case AUDIO_FORMAT_PCM_16_BIT:
+        return ENCODING_PCM_16BIT;
+    case AUDIO_FORMAT_PCM_8_BIT:
+        return ENCODING_PCM_8BIT;
+    case AUDIO_FORMAT_PCM_FLOAT:
+        return ENCODING_PCM_FLOAT;
+    case AUDIO_FORMAT_DEFAULT:
+        return ENCODING_DEFAULT;
+    default:
+        return ENCODING_INVALID;
+    }
+}
+
+static inline audio_channel_mask_t outChannelMaskToNative(int channelMask)
+{
+    switch (channelMask) {
+    case CHANNEL_OUT_DEFAULT:
+    case CHANNEL_INVALID:
+        return AUDIO_CHANNEL_NONE;
+    default:
+        return (audio_channel_mask_t)(channelMask>>2);
+    }
+}
+
+static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask)
+{
+    switch (nativeMask) {
+    case AUDIO_CHANNEL_NONE:
+        return CHANNEL_OUT_DEFAULT;
+    default:
+        return (int)nativeMask<<2;
+    }
+}
+
+static inline audio_channel_mask_t inChannelMaskToNative(int channelMask)
+{
+    return (audio_channel_mask_t)channelMask;
+}
+
+static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask)
+{
+    return (int)nativeMask;
+}
+
 #endif // ANDROID_MEDIA_AUDIOFORMAT_H
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index a9a62f8..0f7e140 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -15,7 +15,9 @@
 ** limitations under the License.
 */
 
-#define LOG_TAG "AudioSystem"
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioSystem-JNI"
 #include <utils/Log.h>
 
 #include <jni.h>
@@ -26,6 +28,8 @@
 
 #include <system/audio.h>
 #include <system/audio_policy.h>
+#include "android_media_AudioFormat.h"
+#include "android_media_AudioErrors.h"
 
 // ----------------------------------------------------------------------------
 
@@ -33,12 +37,160 @@
 
 static const char* const kClassPathName = "android/media/AudioSystem";
 
+static jclass gArrayListClass;
+static struct {
+    jmethodID    add;
+} gArrayListMethods;
+
+static jclass gAudioHandleClass;
+static jmethodID gAudioHandleCstor;
+static struct {
+    jfieldID    mId;
+} gAudioHandleFields;
+
+static jclass gAudioPortClass;
+static jmethodID gAudioPortCstor;
+static struct {
+    jfieldID    mHandle;
+    jfieldID    mRole;
+    jfieldID    mGains;
+    jfieldID    mActiveConfig;
+    // other fields unused by JNI
+} gAudioPortFields;
+
+static jclass gAudioPortConfigClass;
+static jmethodID gAudioPortConfigCstor;
+static struct {
+    jfieldID    mPort;
+    jfieldID    mSamplingRate;
+    jfieldID    mChannelMask;
+    jfieldID    mFormat;
+    jfieldID    mGain;
+    jfieldID    mConfigMask;
+} gAudioPortConfigFields;
+
+static jclass gAudioDevicePortClass;
+static jmethodID gAudioDevicePortCstor;
+
+static jclass gAudioDevicePortConfigClass;
+static jmethodID gAudioDevicePortConfigCstor;
+
+static jclass gAudioMixPortClass;
+static jmethodID gAudioMixPortCstor;
+
+static jclass gAudioMixPortConfigClass;
+static jmethodID gAudioMixPortConfigCstor;
+
+static jclass gAudioGainClass;
+static jmethodID gAudioGainCstor;
+
+static jclass gAudioGainConfigClass;
+static jmethodID gAudioGainConfigCstor;
+static struct {
+    jfieldID mIndex;
+    jfieldID mMode;
+    jfieldID mChannelMask;
+    jfieldID mValues;
+    jfieldID mRampDurationMs;
+    // other fields unused by JNI
+} gAudioGainConfigFields;
+
+static jclass gAudioPatchClass;
+static jmethodID gAudioPatchCstor;
+static struct {
+    jfieldID    mHandle;
+    // other fields unused by JNI
+} gAudioPatchFields;
+
+static const char* const kEventHandlerClassPathName =
+        "android/media/AudioPortEventHandler";
+static jmethodID gPostEventFromNative;
+
 enum AudioError {
     kAudioStatusOk = 0,
     kAudioStatusError = 1,
     kAudioStatusMediaServerDied = 100
 };
 
+enum  {
+    AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1,
+    AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2,
+    AUDIOPORT_EVENT_SERVICE_DIED = 3,
+};
+
+#define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5
+
+// ----------------------------------------------------------------------------
+// ref-counted object for callbacks
+class JNIAudioPortCallback: public AudioSystem::AudioPortCallback
+{
+public:
+    JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
+    ~JNIAudioPortCallback();
+
+    virtual void onAudioPortListUpdate();
+    virtual void onAudioPatchListUpdate();
+    virtual void onServiceDied();
+
+private:
+    void sendEvent(int event);
+
+    jclass      mClass;     // Reference to AudioPortEventHandlerDelegate class
+    jobject     mObject;    // Weak ref to AudioPortEventHandlerDelegate Java object to call on
+};
+
+JNIAudioPortCallback::JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+
+    // Hold onto the SoundTriggerModule class for use in calling the static method
+    // that posts events to the application thread.
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        ALOGE("Can't find class %s", kEventHandlerClassPathName);
+        return;
+    }
+    mClass = (jclass)env->NewGlobalRef(clazz);
+
+    // We use a weak reference so the SoundTriggerModule object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    mObject  = env->NewGlobalRef(weak_thiz);
+}
+
+JNIAudioPortCallback::~JNIAudioPortCallback()
+{
+    // remove global references
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(mObject);
+    env->DeleteGlobalRef(mClass);
+}
+
+void JNIAudioPortCallback::sendEvent(int event)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+                              event, 0, 0, NULL);
+    if (env->ExceptionCheck()) {
+        ALOGW("An exception occurred while notifying an event.");
+        env->ExceptionClear();
+    }
+}
+
+void JNIAudioPortCallback::onAudioPortListUpdate()
+{
+    sendEvent(AUDIOPORT_EVENT_PORT_LIST_UPDATED);
+}
+
+void JNIAudioPortCallback::onAudioPatchListUpdate()
+{
+    sendEvent(AUDIOPORT_EVENT_PATCH_LIST_UPDATED);
+}
+
+void JNIAudioPortCallback::onServiceDied()
+{
+    sendEvent(AUDIOPORT_EVENT_SERVICE_DIED);
+}
+
 static int check_AudioSystem_Command(status_t status)
 {
     switch (status) {
@@ -281,6 +433,854 @@
     return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
 }
 
+
+static bool useInChannelMask(audio_port_type_t type, audio_port_role_t role)
+{
+    return ((type == AUDIO_PORT_TYPE_DEVICE) && (role == AUDIO_PORT_ROLE_SOURCE)) ||
+                ((type == AUDIO_PORT_TYPE_MIX) && (role == AUDIO_PORT_ROLE_SINK));
+}
+
+static void convertAudioGainConfigToNative(JNIEnv *env,
+                                               struct audio_gain_config *nAudioGainConfig,
+                                               const jobject jAudioGainConfig,
+                                               bool useInMask)
+{
+    nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
+    nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+    ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
+    jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
+    audio_channel_mask_t nMask;
+    if (useInMask) {
+        nMask = inChannelMaskToNative(jMask);
+        ALOGV("convertAudioGainConfigToNative IN mask java %x native %x", jMask, nMask);
+    } else {
+        nMask = outChannelMaskToNative(jMask);
+        ALOGV("convertAudioGainConfigToNative OUT mask java %x native %x", jMask, nMask);
+    }
+    nAudioGainConfig->channel_mask = nMask;
+    nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig,
+                                                       gAudioGainConfigFields.mRampDurationMs);
+    jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig,
+                                                       gAudioGainConfigFields.mValues);
+    int *nValues = env->GetIntArrayElements(jValues, NULL);
+    size_t size = env->GetArrayLength(jValues);
+    memcpy(nAudioGainConfig->values, nValues, size * sizeof(int));
+    env->DeleteLocalRef(jValues);
+}
+
+
+static jint convertAudioPortConfigToNative(JNIEnv *env,
+                                               struct audio_port_config *nAudioPortConfig,
+                                               const jobject jAudioPortConfig)
+{
+    jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort);
+    jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle);
+    nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId);
+    nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort,
+                                                                 gAudioPortFields.mRole);
+    if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+        nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE;
+    } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+        nAudioPortConfig->type = AUDIO_PORT_TYPE_MIX;
+    } else {
+        env->DeleteLocalRef(jAudioPort);
+        env->DeleteLocalRef(jHandle);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    ALOGV("convertAudioPortConfigToNative handle %d role %d type %d",
+          nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type);
+
+    nAudioPortConfig->sample_rate = env->GetIntField(jAudioPortConfig,
+                                                     gAudioPortConfigFields.mSamplingRate);
+
+    bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role);
+    audio_channel_mask_t nMask;
+    jint jMask = env->GetIntField(jAudioPortConfig,
+                                   gAudioPortConfigFields.mChannelMask);
+    if (useInMask) {
+        nMask = inChannelMaskToNative(jMask);
+        ALOGV("convertAudioPortConfigToNative IN mask java %x native %x", jMask, nMask);
+    } else {
+        nMask = outChannelMaskToNative(jMask);
+        ALOGV("convertAudioPortConfigToNative OUT mask java %x native %x", jMask, nMask);
+    }
+    nAudioPortConfig->channel_mask = nMask;
+
+    jint jFormat = env->GetIntField(jAudioPortConfig, gAudioPortConfigFields.mFormat);
+    audio_format_t nFormat = audioFormatToNative(jFormat);
+    ALOGV("convertAudioPortConfigToNative format %d native %d", jFormat, nFormat);
+    nAudioPortConfig->format = nFormat;
+    jobject jGain = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mGain);
+    if (jGain != NULL) {
+        convertAudioGainConfigToNative(env, &nAudioPortConfig->gain, jGain, useInMask);
+        env->DeleteLocalRef(jGain);
+    } else {
+        ALOGV("convertAudioPortConfigToNative no gain");
+        nAudioPortConfig->gain.index = -1;
+    }
+    nAudioPortConfig->config_mask = env->GetIntField(jAudioPortConfig,
+                                                     gAudioPortConfigFields.mConfigMask);
+
+    env->DeleteLocalRef(jAudioPort);
+    env->DeleteLocalRef(jHandle);
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioPortConfigFromNative(JNIEnv *env,
+                                                 jobject jAudioPort,
+                                                 jobject *jAudioPortConfig,
+                                                 const struct audio_port_config *nAudioPortConfig)
+{
+    jint jStatus = AUDIO_JAVA_SUCCESS;
+    jobject jAudioGainConfig = NULL;
+    jobject jAudioGain = NULL;
+    jintArray jGainValues;
+    bool audioportCreated = false;
+
+    ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort);
+
+    if (jAudioPort == NULL) {
+        jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+                                                 nAudioPortConfig->id);
+
+        ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id,
+              nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix");
+
+        if (jHandle == NULL) {
+            return (jint)AUDIO_JAVA_ERROR;
+        }
+        // create dummy port and port config objects with just the correct handle
+        // and configuration data. The actual AudioPortConfig objects will be
+        // constructed by java code with correct class type (device, mix etc...)
+        // and reference to AudioPort instance in this client
+        jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor,
+                                           jHandle,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           NULL);
+        env->DeleteLocalRef(jHandle);
+        if (jAudioPort == NULL) {
+            return (jint)AUDIO_JAVA_ERROR;
+        }
+        ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d",
+              nAudioPortConfig->id);
+
+        audioportCreated = true;
+    }
+
+    bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role);
+
+    audio_channel_mask_t nMask;
+    jint jMask;
+
+    int gainIndex = nAudioPortConfig->gain.index;
+    if (gainIndex >= 0) {
+        ALOGV("convertAudioPortConfigFromNative gain found with index %d mode %x",
+              gainIndex, nAudioPortConfig->gain.mode);
+        if (audioportCreated) {
+            ALOGV("convertAudioPortConfigFromNative creating gain");
+            jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
+                                               gainIndex,
+                                               0,
+                                               0,
+                                               0,
+                                               0,
+                                               0,
+                                               0,
+                                               0,
+                                               0);
+            if (jAudioGain == NULL) {
+                ALOGV("convertAudioPortConfigFromNative creating gain FAILED");
+                jStatus = (jint)AUDIO_JAVA_ERROR;
+                goto exit;
+            }
+        } else {
+            ALOGV("convertAudioPortConfigFromNative reading gain from port");
+            jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort,
+                                                                      gAudioPortFields.mGains);
+            if (jGains == NULL) {
+                ALOGV("convertAudioPortConfigFromNative could not get gains from port");
+                jStatus = (jint)AUDIO_JAVA_ERROR;
+                goto exit;
+            }
+            jAudioGain = env->GetObjectArrayElement(jGains, gainIndex);
+            env->DeleteLocalRef(jGains);
+            if (jAudioGain == NULL) {
+                ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex);
+                jStatus = (jint)AUDIO_JAVA_ERROR;
+                goto exit;
+            }
+        }
+        //TODO: replace popcount by audio utils function mask to count
+        int numValues = popcount(nAudioPortConfig->gain.channel_mask);
+        jGainValues = env->NewIntArray(numValues);
+        if (jGainValues == NULL) {
+            ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues);
+            jStatus = (jint)AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+        env->SetIntArrayRegion(jGainValues, 0, numValues,
+                               nAudioPortConfig->gain.values);
+
+        nMask = nAudioPortConfig->gain.channel_mask;
+        if (useInMask) {
+            jMask = inChannelMaskFromNative(nMask);
+            ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+        } else {
+            jMask = outChannelMaskFromNative(nMask);
+            ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+        }
+
+        jAudioGainConfig = env->NewObject(gAudioGainConfigClass,
+                                        gAudioGainConfigCstor,
+                                        gainIndex,
+                                        jAudioGain,
+                                        nAudioPortConfig->gain.mode,
+                                        jMask,
+                                        jGainValues,
+                                        nAudioPortConfig->gain.ramp_duration_ms);
+        env->DeleteLocalRef(jGainValues);
+        if (jAudioGainConfig == NULL) {
+            ALOGV("convertAudioPortConfigFromNative could not create gain config");
+            jStatus = (jint)AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+    }
+    jclass clazz;
+    jmethodID methodID;
+    if (audioportCreated) {
+        clazz = gAudioPortConfigClass;
+        methodID = gAudioPortConfigCstor;
+        ALOGV("convertAudioPortConfigFromNative building a generic port config");
+    } else {
+        if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+            clazz = gAudioDevicePortConfigClass;
+            methodID = gAudioDevicePortConfigCstor;
+            ALOGV("convertAudioPortConfigFromNative building a device config");
+        } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+            clazz = gAudioMixPortConfigClass;
+            methodID = gAudioMixPortConfigCstor;
+            ALOGV("convertAudioPortConfigFromNative building a mix config");
+        } else {
+            jStatus = (jint)AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+    }
+    nMask = nAudioPortConfig->channel_mask;
+    if (useInMask) {
+        jMask = inChannelMaskFromNative(nMask);
+        ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+    } else {
+        jMask = outChannelMaskFromNative(nMask);
+        ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+    }
+
+    *jAudioPortConfig = env->NewObject(clazz, methodID,
+                                       jAudioPort,
+                                       nAudioPortConfig->sample_rate,
+                                       jMask,
+                                       audioFormatFromNative(nAudioPortConfig->format),
+                                       jAudioGainConfig);
+    if (*jAudioPortConfig == NULL) {
+        ALOGV("convertAudioPortConfigFromNative could not create new port config");
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+    } else {
+        ALOGV("convertAudioPortConfigFromNative OK");
+    }
+
+exit:
+    if (audioportCreated) {
+        env->DeleteLocalRef(jAudioPort);
+        if (jAudioGain != NULL) {
+            env->DeleteLocalRef(jAudioGain);
+        }
+    }
+    if (jAudioGainConfig != NULL) {
+        env->DeleteLocalRef(jAudioGainConfig);
+    }
+    return jStatus;
+}
+
+static jint convertAudioPortFromNative(JNIEnv *env,
+                                           jobject *jAudioPort, const struct audio_port *nAudioPort)
+{
+    jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
+    jintArray jSamplingRates = NULL;
+    jintArray jChannelMasks = NULL;
+    jintArray jFormats = NULL;
+    jobjectArray jGains = NULL;
+    jobject jHandle = NULL;
+    bool useInMask;
+
+    ALOGV("convertAudioPortFromNative id %d role %d type %d",
+                                  nAudioPort->id, nAudioPort->role, nAudioPort->type);
+
+    jSamplingRates = env->NewIntArray(nAudioPort->num_sample_rates);
+    if (jSamplingRates == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    if (nAudioPort->num_sample_rates) {
+        env->SetIntArrayRegion(jSamplingRates, 0, nAudioPort->num_sample_rates,
+                               (jint *)nAudioPort->sample_rates);
+    }
+
+    jChannelMasks = env->NewIntArray(nAudioPort->num_channel_masks);
+    if (jChannelMasks == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    useInMask = useInChannelMask(nAudioPort->type, nAudioPort->role);
+
+    jint jMask;
+    for (size_t j = 0; j < nAudioPort->num_channel_masks; j++) {
+        if (useInMask) {
+            jMask = inChannelMaskFromNative(nAudioPort->channel_masks[j]);
+        } else {
+            jMask = outChannelMaskFromNative(nAudioPort->channel_masks[j]);
+        }
+        env->SetIntArrayRegion(jChannelMasks, j, 1, &jMask);
+    }
+
+    jFormats = env->NewIntArray(nAudioPort->num_formats);
+    if (jFormats == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    for (size_t j = 0; j < nAudioPort->num_formats; j++) {
+        jint jFormat = audioFormatFromNative(nAudioPort->formats[j]);
+        env->SetIntArrayRegion(jFormats, j, 1, &jFormat);
+    }
+
+    jGains = env->NewObjectArray(nAudioPort->num_gains,
+                                          gAudioGainClass, NULL);
+    if (jGains == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    for (size_t j = 0; j < nAudioPort->num_gains; j++) {
+        audio_channel_mask_t nMask = nAudioPort->gains[j].channel_mask;
+        if (useInMask) {
+            jMask = inChannelMaskFromNative(nMask);
+            ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+        } else {
+            jMask = outChannelMaskFromNative(nMask);
+            ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+        }
+
+        jobject jGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
+                                                 j,
+                                                 nAudioPort->gains[j].mode,
+                                                 jMask,
+                                                 nAudioPort->gains[j].min_value,
+                                                 nAudioPort->gains[j].max_value,
+                                                 nAudioPort->gains[j].default_value,
+                                                 nAudioPort->gains[j].step_value,
+                                                 nAudioPort->gains[j].min_ramp_ms,
+                                                 nAudioPort->gains[j].max_ramp_ms);
+        if (jGain == NULL) {
+            jStatus = (jint)AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+        env->SetObjectArrayElement(jGains, j, jGain);
+        env->DeleteLocalRef(jGain);
+    }
+
+    jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+                                             nAudioPort->id);
+    if (jHandle == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+
+    if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) {
+        ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
+        jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address);
+        *jAudioPort = env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor,
+                                     jHandle, jSamplingRates, jChannelMasks, jFormats, jGains,
+                                     nAudioPort->ext.device.type, jAddress);
+        env->DeleteLocalRef(jAddress);
+    } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) {
+        ALOGV("convertAudioPortFromNative is a mix");
+        *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor,
+                                     jHandle, nAudioPort->role, jSamplingRates, jChannelMasks,
+                                     jFormats, jGains);
+    } else {
+        ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type);
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    if (*jAudioPort == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+
+    jobject jAudioPortConfig;
+    jStatus = convertAudioPortConfigFromNative(env,
+                                                       *jAudioPort,
+                                                       &jAudioPortConfig,
+                                                       &nAudioPort->active_config);
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig);
+
+exit:
+    if (jSamplingRates != NULL) {
+        env->DeleteLocalRef(jSamplingRates);
+    }
+    if (jChannelMasks != NULL) {
+        env->DeleteLocalRef(jChannelMasks);
+    }
+    if (jFormats != NULL) {
+        env->DeleteLocalRef(jFormats);
+    }
+    if (jGains != NULL) {
+        env->DeleteLocalRef(jGains);
+    }
+    if (jHandle != NULL) {
+        env->DeleteLocalRef(jHandle);
+    }
+
+    return jStatus;
+}
+
+
+static jint
+android_media_AudioSystem_listAudioPorts(JNIEnv *env, jobject clazz,
+                                         jobject jPorts, jintArray jGeneration)
+{
+    ALOGV("listAudioPorts");
+
+    if (jPorts == NULL) {
+        ALOGE("listAudioPorts NULL AudioPort ArrayList");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jPorts, gArrayListClass)) {
+        ALOGE("listAudioPorts not an arraylist");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    status_t status;
+    unsigned int generation1;
+    unsigned int generation;
+    unsigned int numPorts;
+    jint *nGeneration;
+    struct audio_port *nPorts = NULL;
+    int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
+
+    // get the port count and all the ports until they both return the same generation
+    do {
+        if (attempts-- < 0) {
+            status = TIMED_OUT;
+            break;
+        }
+
+        numPorts = 0;
+        status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE,
+                                             AUDIO_PORT_TYPE_NONE,
+                                                      &numPorts,
+                                                      NULL,
+                                                      &generation1);
+        if (status != NO_ERROR || numPorts == 0) {
+            ALOGE_IF(status != NO_ERROR, "AudioSystem::listAudioPorts error %d", status);
+            break;
+        }
+        nPorts = (struct audio_port *)realloc(nPorts, numPorts * sizeof(struct audio_port));
+
+        status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE,
+                                             AUDIO_PORT_TYPE_NONE,
+                                                      &numPorts,
+                                                      nPorts,
+                                                      &generation);
+        ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d",
+              numPorts, generation, generation1);
+    } while (generation1 != generation && status == NO_ERROR);
+
+    jint jStatus = nativeToJavaStatus(status);
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
+        goto exit;
+    }
+
+    nGeneration = env->GetIntArrayElements(jGeneration, NULL);
+    if (nGeneration == NULL) {
+        jStatus = (jint)AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    nGeneration[0] = generation1;
+    env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+
+    for (size_t i = 0; i < numPorts; i++) {
+        jobject jAudioPort;
+        jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            goto exit;
+        }
+        env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort);
+    }
+
+exit:
+    free(nPorts);
+    return jStatus;
+}
+
+static int
+android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz,
+                                 jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks)
+{
+    status_t status;
+    jint jStatus;
+
+    ALOGV("createAudioPatch");
+    if (jPatches == NULL || jSources == NULL || jSinks == NULL) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    if (env->GetArrayLength(jPatches) != 1) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    jint numSources = env->GetArrayLength(jSources);
+    if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    jint numSinks = env->GetArrayLength(jSinks);
+    if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    audio_patch_handle_t handle = (audio_patch_handle_t)0;
+    jobject jPatch = env->GetObjectArrayElement(jPatches, 0);
+    jobject jPatchHandle = NULL;
+    if (jPatch != NULL) {
+        if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
+            return (jint)AUDIO_JAVA_BAD_VALUE;
+        }
+        jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
+        handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+    }
+
+    struct audio_patch nPatch;
+
+    nPatch.id = handle;
+    nPatch.num_sources = 0;
+    nPatch.num_sinks = 0;
+    jobject jSource = NULL;
+    jobject jSink = NULL;
+
+    for (jint i = 0; i < numSources; i++) {
+        jSource = env->GetObjectArrayElement(jSources, i);
+        if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) {
+            jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
+            goto exit;
+        }
+        jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource);
+        env->DeleteLocalRef(jSource);
+        jSource = NULL;
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            goto exit;
+        }
+        nPatch.num_sources++;
+    }
+
+    for (jint i = 0; i < numSinks; i++) {
+        jSink = env->GetObjectArrayElement(jSinks, i);
+        if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) {
+            jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
+            goto exit;
+        }
+        jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink);
+        env->DeleteLocalRef(jSink);
+        jSink = NULL;
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            goto exit;
+        }
+        nPatch.num_sinks++;
+    }
+
+    ALOGV("AudioSystem::createAudioPatch");
+    status = AudioSystem::createAudioPatch(&nPatch, &handle);
+    ALOGV("AudioSystem::createAudioPatch() returned %d hande %d", status, handle);
+
+    jStatus = nativeToJavaStatus(status);
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
+        goto exit;
+    }
+
+    if (jPatchHandle == NULL) {
+        jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+                                           handle);
+        if (jPatchHandle == NULL) {
+            jStatus = (jint)AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+        jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks);
+        if (jPatch == NULL) {
+            jStatus = (jint)AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+        env->SetObjectArrayElement(jPatches, 0, jPatch);
+    } else {
+        env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle);
+    }
+
+exit:
+    if (jPatchHandle != NULL) {
+        env->DeleteLocalRef(jPatchHandle);
+    }
+    if (jPatch != NULL) {
+        env->DeleteLocalRef(jPatch);
+    }
+    if (jSource != NULL) {
+        env->DeleteLocalRef(jSource);
+    }
+    if (jSink != NULL) {
+        env->DeleteLocalRef(jSink);
+    }
+    return jStatus;
+}
+
+static int
+android_media_AudioSystem_releaseAudioPatch(JNIEnv *env, jobject clazz,
+                                               jobject jPatch)
+{
+    ALOGV("releaseAudioPatch");
+    if (jPatch == NULL) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    audio_patch_handle_t handle = (audio_patch_handle_t)0;
+    jobject jPatchHandle = NULL;
+    if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
+    handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+    env->DeleteLocalRef(jPatchHandle);
+
+    ALOGV("AudioSystem::releaseAudioPatch");
+    status_t status = AudioSystem::releaseAudioPatch(handle);
+    ALOGV("AudioSystem::releaseAudioPatch() returned %d", status);
+    jint jStatus = nativeToJavaStatus(status);
+    return status;
+}
+
+static jint
+android_media_AudioSystem_listAudioPatches(JNIEnv *env, jobject clazz,
+                                           jobject jPatches, jintArray jGeneration)
+{
+    ALOGV("listAudioPatches");
+    if (jPatches == NULL) {
+        ALOGE("listAudioPatches NULL AudioPatch ArrayList");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jPatches, gArrayListClass)) {
+        ALOGE("listAudioPatches not an arraylist");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    status_t status;
+    unsigned int generation1;
+    unsigned int generation;
+    unsigned int numPatches;
+    jint *nGeneration;
+    struct audio_patch *nPatches = NULL;
+    jobjectArray jSources = NULL;
+    jobject jSource = NULL;
+    jobjectArray jSinks = NULL;
+    jobject jSink = NULL;
+    jobject jPatch = NULL;
+    int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
+
+    // get the patch count and all the patches until they both return the same generation
+    do {
+        if (attempts-- < 0) {
+            status = TIMED_OUT;
+            break;
+        }
+
+        numPatches = 0;
+        status = AudioSystem::listAudioPatches(&numPatches,
+                                               NULL,
+                                               &generation1);
+        if (status != NO_ERROR || numPatches == 0) {
+            ALOGE_IF(status != NO_ERROR, "listAudioPatches AudioSystem::listAudioPatches error %d",
+                                      status);
+            break;
+        }
+        nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch));
+
+        status = AudioSystem::listAudioPatches(&numPatches,
+                                               nPatches,
+                                               &generation);
+        ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d",
+              numPatches, generation, generation1);
+
+    } while (generation1 != generation && status == NO_ERROR);
+
+    jint jStatus = nativeToJavaStatus(status);
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
+        goto exit;
+    }
+
+    nGeneration = env->GetIntArrayElements(jGeneration, NULL);
+    if (nGeneration == NULL) {
+        jStatus = AUDIO_JAVA_ERROR;
+        goto exit;
+    }
+    nGeneration[0] = generation1;
+    env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+
+    for (size_t i = 0; i < numPatches; i++) {
+        jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+                                                 nPatches[i].id);
+        if (patchHandle == NULL) {
+            jStatus = AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+        ALOGV("listAudioPatches patch %d num_sources %d num_sinks %d",
+              i, nPatches[i].num_sources, nPatches[i].num_sinks);
+
+        env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id);
+
+        // load sources
+        jSources = env->NewObjectArray(nPatches[i].num_sources,
+                                       gAudioPortConfigClass, NULL);
+        if (jSources == NULL) {
+            jStatus = AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+
+        for (size_t j = 0; j < nPatches[i].num_sources; j++) {
+            jStatus = convertAudioPortConfigFromNative(env,
+                                                      NULL,
+                                                      &jSource,
+                                                      &nPatches[i].sources[j]);
+            if (jStatus != AUDIO_JAVA_SUCCESS) {
+                goto exit;
+            }
+            env->SetObjectArrayElement(jSources, j, jSource);
+            env->DeleteLocalRef(jSource);
+            jSource = NULL;
+            ALOGV("listAudioPatches patch %d source %d is a %s handle %d",
+                  i, j,
+                  nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
+                  nPatches[i].sources[j].id);
+        }
+        // load sinks
+        jSinks = env->NewObjectArray(nPatches[i].num_sinks,
+                                     gAudioPortConfigClass, NULL);
+        if (jSinks == NULL) {
+            jStatus = AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+
+        for (size_t j = 0; j < nPatches[i].num_sinks; j++) {
+            jStatus = convertAudioPortConfigFromNative(env,
+                                                      NULL,
+                                                      &jSink,
+                                                      &nPatches[i].sinks[j]);
+
+            if (jStatus != AUDIO_JAVA_SUCCESS) {
+                goto exit;
+            }
+            env->SetObjectArrayElement(jSinks, j, jSink);
+            env->DeleteLocalRef(jSink);
+            jSink = NULL;
+            ALOGV("listAudioPatches patch %d sink %d is a %s handle %d",
+                  i, j,
+                  nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
+                  nPatches[i].sinks[j].id);
+        }
+
+        jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor,
+                                       patchHandle, jSources, jSinks);
+        env->DeleteLocalRef(jSources);
+        jSources = NULL;
+        env->DeleteLocalRef(jSinks);
+        jSinks = NULL;
+        if (jPatch == NULL) {
+            jStatus = AUDIO_JAVA_ERROR;
+            goto exit;
+        }
+        env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch);
+        env->DeleteLocalRef(jPatch);
+        jPatch = NULL;
+    }
+
+exit:
+    if (jSources != NULL) {
+        env->DeleteLocalRef(jSources);
+    }
+    if (jSource != NULL) {
+        env->DeleteLocalRef(jSource);
+    }
+    if (jSinks != NULL) {
+        env->DeleteLocalRef(jSinks);
+    }
+    if (jSink != NULL) {
+        env->DeleteLocalRef(jSink);
+    }
+    if (jPatch != NULL) {
+        env->DeleteLocalRef(jPatch);
+    }
+    free(nPatches);
+    return jStatus;
+}
+
+static jint
+android_media_AudioSystem_setAudioPortConfig(JNIEnv *env, jobject clazz,
+                                 jobject jAudioPortConfig)
+{
+    ALOGV("setAudioPortConfig");
+    if (jAudioPortConfig == NULL) {
+        return AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) {
+        return AUDIO_JAVA_BAD_VALUE;
+    }
+    struct audio_port_config nAudioPortConfig;
+    jint jStatus = convertAudioPortConfigToNative(env, &nAudioPortConfig, jAudioPortConfig);
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+    status_t status = AudioSystem::setAudioPortConfig(&nAudioPortConfig);
+    ALOGV("AudioSystem::setAudioPortConfig() returned %d", status);
+    jStatus = nativeToJavaStatus(status);
+    return jStatus;
+}
+
+static void
+android_media_AudioSystem_eventHandlerSetup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+    ALOGV("eventHandlerSetup");
+
+    sp<JNIAudioPortCallback> callback = new JNIAudioPortCallback(env, thiz, weak_this);
+
+    AudioSystem::setAudioPortCallback(callback);
+}
+
+static void
+android_media_AudioSystem_eventHandlerFinalize(JNIEnv *env, jobject thiz)
+{
+    ALOGV("eventHandlerFinalize");
+
+    sp<JNIAudioPortCallback> callback;
+
+    AudioSystem::setAudioPortCallback(callback);
+}
+
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
@@ -309,12 +1309,123 @@
     {"getOutputLatency",    "(I)I",     (void *)android_media_AudioSystem_getOutputLatency},
     {"setLowRamDevice",     "(Z)I",     (void *)android_media_AudioSystem_setLowRamDevice},
     {"checkAudioFlinger",    "()I",     (void *)android_media_AudioSystem_checkAudioFlinger},
+    {"listAudioPorts",      "(Ljava/util/ArrayList;[I)I",
+                                                (void *)android_media_AudioSystem_listAudioPorts},
+    {"createAudioPatch",    "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
+                                            (void *)android_media_AudioSystem_createAudioPatch},
+    {"releaseAudioPatch",   "(Landroid/media/AudioPatch;)I",
+                                            (void *)android_media_AudioSystem_releaseAudioPatch},
+    {"listAudioPatches",    "(Ljava/util/ArrayList;[I)I",
+                                                (void *)android_media_AudioSystem_listAudioPatches},
+    {"setAudioPortConfig",   "(Landroid/media/AudioPortConfig;)I",
+                                            (void *)android_media_AudioSystem_setAudioPortConfig},
+};
+
+
+static JNINativeMethod gEventHandlerMethods[] = {
+    {"native_setup",
+        "(Ljava/lang/Object;)V",
+        (void *)android_media_AudioSystem_eventHandlerSetup},
+    {"native_finalize",
+        "()V",
+        (void *)android_media_AudioSystem_eventHandlerFinalize},
 };
 
 int register_android_media_AudioSystem(JNIEnv *env)
 {
+
+    jclass arrayListClass = env->FindClass("java/util/ArrayList");
+    gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass);
+    gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+    jclass audioHandleClass = env->FindClass("android/media/AudioHandle");
+    gAudioHandleClass = (jclass) env->NewGlobalRef(audioHandleClass);
+    gAudioHandleCstor = env->GetMethodID(audioHandleClass, "<init>", "(I)V");
+    gAudioHandleFields.mId = env->GetFieldID(audioHandleClass, "mId", "I");
+
+    jclass audioPortClass = env->FindClass("android/media/AudioPort");
+    gAudioPortClass = (jclass) env->NewGlobalRef(audioPortClass);
+    gAudioPortCstor = env->GetMethodID(audioPortClass, "<init>",
+                               "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V");
+    gAudioPortFields.mHandle = env->GetFieldID(audioPortClass, "mHandle",
+                                               "Landroid/media/AudioHandle;");
+    gAudioPortFields.mRole = env->GetFieldID(audioPortClass, "mRole", "I");
+    gAudioPortFields.mGains = env->GetFieldID(audioPortClass, "mGains",
+                                              "[Landroid/media/AudioGain;");
+    gAudioPortFields.mActiveConfig = env->GetFieldID(audioPortClass, "mActiveConfig",
+                                              "Landroid/media/AudioPortConfig;");
+
+    jclass audioPortConfigClass = env->FindClass("android/media/AudioPortConfig");
+    gAudioPortConfigClass = (jclass) env->NewGlobalRef(audioPortConfigClass);
+    gAudioPortConfigCstor = env->GetMethodID(audioPortConfigClass, "<init>",
+                                 "(Landroid/media/AudioPort;IIILandroid/media/AudioGainConfig;)V");
+    gAudioPortConfigFields.mPort = env->GetFieldID(audioPortConfigClass, "mPort",
+                                                   "Landroid/media/AudioPort;");
+    gAudioPortConfigFields.mSamplingRate = env->GetFieldID(audioPortConfigClass,
+                                                           "mSamplingRate", "I");
+    gAudioPortConfigFields.mChannelMask = env->GetFieldID(audioPortConfigClass,
+                                                          "mChannelMask", "I");
+    gAudioPortConfigFields.mFormat = env->GetFieldID(audioPortConfigClass, "mFormat", "I");
+    gAudioPortConfigFields.mGain = env->GetFieldID(audioPortConfigClass, "mGain",
+                                                   "Landroid/media/AudioGainConfig;");
+    gAudioPortConfigFields.mConfigMask = env->GetFieldID(audioPortConfigClass, "mConfigMask", "I");
+
+    jclass audioDevicePortConfigClass = env->FindClass("android/media/AudioDevicePortConfig");
+    gAudioDevicePortConfigClass = (jclass) env->NewGlobalRef(audioDevicePortConfigClass);
+    gAudioDevicePortConfigCstor = env->GetMethodID(audioDevicePortConfigClass, "<init>",
+                         "(Landroid/media/AudioDevicePort;IIILandroid/media/AudioGainConfig;)V");
+
+    jclass audioMixPortConfigClass = env->FindClass("android/media/AudioMixPortConfig");
+    gAudioMixPortConfigClass = (jclass) env->NewGlobalRef(audioMixPortConfigClass);
+    gAudioMixPortConfigCstor = env->GetMethodID(audioMixPortConfigClass, "<init>",
+                         "(Landroid/media/AudioMixPort;IIILandroid/media/AudioGainConfig;)V");
+
+    jclass audioDevicePortClass = env->FindClass("android/media/AudioDevicePort");
+    gAudioDevicePortClass = (jclass) env->NewGlobalRef(audioDevicePortClass);
+    gAudioDevicePortCstor = env->GetMethodID(audioDevicePortClass, "<init>",
+             "(Landroid/media/AudioHandle;[I[I[I[Landroid/media/AudioGain;ILjava/lang/String;)V");
+
+    jclass audioMixPortClass = env->FindClass("android/media/AudioMixPort");
+    gAudioMixPortClass = (jclass) env->NewGlobalRef(audioMixPortClass);
+    gAudioMixPortCstor = env->GetMethodID(audioMixPortClass, "<init>",
+                              "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V");
+
+    jclass audioGainClass = env->FindClass("android/media/AudioGain");
+    gAudioGainClass = (jclass) env->NewGlobalRef(audioGainClass);
+    gAudioGainCstor = env->GetMethodID(audioGainClass, "<init>", "(IIIIIIIII)V");
+
+    jclass audioGainConfigClass = env->FindClass("android/media/AudioGainConfig");
+    gAudioGainConfigClass = (jclass) env->NewGlobalRef(audioGainConfigClass);
+    gAudioGainConfigCstor = env->GetMethodID(audioGainConfigClass, "<init>",
+                                             "(ILandroid/media/AudioGain;II[II)V");
+    gAudioGainConfigFields.mIndex = env->GetFieldID(gAudioGainConfigClass, "mIndex", "I");
+    gAudioGainConfigFields.mMode = env->GetFieldID(audioGainConfigClass, "mMode", "I");
+    gAudioGainConfigFields.mChannelMask = env->GetFieldID(audioGainConfigClass, "mChannelMask",
+                                                          "I");
+    gAudioGainConfigFields.mValues = env->GetFieldID(audioGainConfigClass, "mValues", "[I");
+    gAudioGainConfigFields.mRampDurationMs = env->GetFieldID(audioGainConfigClass,
+                                                             "mRampDurationMs", "I");
+
+    jclass audioPatchClass = env->FindClass("android/media/AudioPatch");
+    gAudioPatchClass = (jclass) env->NewGlobalRef(audioPatchClass);
+    gAudioPatchCstor = env->GetMethodID(audioPatchClass, "<init>",
+"(Landroid/media/AudioHandle;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)V");
+    gAudioPatchFields.mHandle = env->GetFieldID(audioPatchClass, "mHandle",
+                                                "Landroid/media/AudioHandle;");
+
+    jclass eventHandlerClass = env->FindClass(kEventHandlerClassPathName);
+    gPostEventFromNative = env->GetStaticMethodID(eventHandlerClass, "postEventFromNative",
+                                            "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+
+
     AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
 
-    return AndroidRuntime::registerNativeMethods(env,
+    int status = AndroidRuntime::registerNativeMethods(env,
                 kClassPathName, gMethods, NELEM(gMethods));
+
+    if (status == 0) {
+        status = AndroidRuntime::registerNativeMethods(env,
+                kEventHandlerClassPathName, gEventHandlerMethods, NELEM(gEventHandlerMethods));
+    }
+    return status;
 }
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index e19ce36..d689864 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -116,6 +116,11 @@
     return reinterpret_cast<jlong>( animator );
 }
 
+static void setStartValue(JNIEnv* env, jobject clazz, jlong animatorPtr, jfloat startValue) {
+    BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
+    animator->setStartValue(startValue);
+}
+
 static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong duration) {
     LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative");
     BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
@@ -157,6 +162,7 @@
     { "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IF)J", (void*) createAnimator },
     { "nCreateCanvasPropertyFloatAnimator", "(Ljava/lang/ref/WeakReference;JF)J", (void*) createCanvasPropertyFloatAnimator },
     { "nCreateCanvasPropertyPaintAnimator", "(Ljava/lang/ref/WeakReference;JIF)J", (void*) createCanvasPropertyPaintAnimator },
+    { "nSetStartValue", "(JF)V", (void*) setStartValue },
     { "nSetDuration", "(JJ)V", (void*) setDuration },
     { "nGetDuration", "(J)J", (void*) getDuration },
     { "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml
index 4c4f6a4..117774a 100644
--- a/core/res/res/anim/input_method_exit.xml
+++ b/core/res/res/anim/input_method_exit.xml
@@ -17,7 +17,7 @@
 -->
 <set xmlns:android="http://schemas.android.com/apk/res/android"
 	android:shareInterpolator="false">
-    <translate android:fromYDelta="0" android:toYDelta="-20%"
+    <translate android:fromYDelta="0" android:toYDelta="10%"
 			android:interpolator="@interpolator/accelerate_quint"
             android:duration="@android:integer/config_shortAnimTime"/>
     <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 4a956d7..7e212be 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -18,10 +18,17 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:background="#ff000000" android:shareInterpolator="false">
+    android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60">
     <alpha
-        android:fromAlpha="1.0" android:toAlpha="1.0"
+        android:fromAlpha="0.0" android:toAlpha="1.0"
         android:fillEnabled="true" android:fillBefore="true"
-        android:interpolator="@interpolator/decelerate_quint"
-        android:duration="0"/>
+        android:interpolator="@interpolator/linear_out_slow_in"
+        android:duration="@integer/config_shortAnimTime"/>
+    <scale
+        android:fromXScale="0.95" android:toXScale="1.0"
+        android:fromYScale="0.95" android:toYScale="1.0"
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true" android:fillBefore="true"
+        android:interpolator="@interpolator/linear_out_slow_in"
+        android:duration="@integer/config_shortAnimTime" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
deleted file mode 100644
index f7a6a65..0000000
--- a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:detachWallpaper="true" android:shareInterpolator="false">
-    <alpha
-        android:fromAlpha="0.0" android:toAlpha="1.0"
-        android:fillEnabled="true" android:fillBefore="true"
-        android:interpolator="@interpolator/decelerate_quad"
-        android:startOffset="@android:integer/config_mediumAnimTime"
-        android:duration="@android:integer/config_shortAnimTime"/>
-</set>
diff --git a/core/res/res/color/btn_default_quantum_dark.xml b/core/res/res/color/btn_default_quantum_dark.xml
index f2e772d..ec0f140 100644
--- a/core/res/res/color/btn_default_quantum_dark.xml
+++ b/core/res/res/color/btn_default_quantum_dark.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_700"/>
-    <item android:color="@color/quantum_grey_700"/>
+    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_dark"/>
+    <item android:color="@color/button_quantum_dark"/>
 </selector>
diff --git a/core/res/res/color/btn_default_quantum_light.xml b/core/res/res/color/btn_default_quantum_light.xml
index de1bd2c..9536d24 100644
--- a/core/res/res/color/btn_default_quantum_light.xml
+++ b/core/res/res/color/btn_default_quantum_light.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_300"/>
-    <item android:color="@color/quantum_grey_300"/>
+    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_light"/>
+    <item android:color="@color/button_quantum_light"/>
 </selector>
diff --git a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index ba5bd01..0000000
--- a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index 4e2612d..0000000
--- a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index e6ca1ea..0000000
--- a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index d6018dd..0000000
--- a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_bugreport.xml b/core/res/res/drawable/ic_lock_bugreport.xml
index a3f82ce..b93a09a 100644
--- a/core/res/res/drawable/ic_lock_bugreport.xml
+++ b/core/res/res/drawable/ic_lock_bugreport.xml
@@ -1,19 +1,28 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+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
+   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
+         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.
+    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.
 -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="32dp"
+        android:height="32dp"/>
 
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_lock_bugreport_alpha"
-    android:tint="?attr/colorControlNormal" />
+    <viewport
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"/>
+
+    <path
+        android:fill="?attr/colorControlNormal"
+        android:pathData="M20.0,8.0l-2.8,0.0c-0.5,-0.8 -1.1,-1.5 -1.8,-2.0L17.0,4.4L15.6,3.0l-2.2,2.2C13.0,5.1 12.5,5.0 12.0,5.0s-1.0,0.1 -1.4,0.2L8.4,3.0L7.0,4.4L8.6,6.0C7.9,6.5 7.3,7.2 6.8,8.0L4.0,8.0l0.0,2.0l2.1,0.0C6.0,10.3 6.0,10.7 6.0,11.0l0.0,1.0L4.0,12.0l0.0,2.0l2.0,0.0l0.0,1.0c0.0,0.3 0.0,0.7 0.1,1.0L4.0,16.0l0.0,2.0l2.8,0.0c1.0,1.8 3.0,3.0 5.2,3.0s4.2,-1.2 5.2,-3.0L20.0,18.0l0.0,-2.0l-2.1,0.0c0.1,-0.3 0.1,-0.7 0.1,-1.0l0.0,-1.0l2.0,0.0l0.0,-2.0l-2.0,0.0l0.0,-1.0c0.0,-0.3 0.0,-0.7 -0.1,-1.0L20.0,10.0L20.0,8.0zM14.0,16.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,16.0zM14.0,12.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,12.0z"/>
+</vector>
diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/item_background_borderless_quantum.xml
similarity index 82%
rename from core/res/res/drawable/list_selector_quantum.xml
rename to core/res/res/drawable/item_background_borderless_quantum.xml
index 6cd59e5..c2a1c127 100644
--- a/core/res/res/drawable/list_selector_quantum.xml
+++ b/core/res/res/drawable/item_background_borderless_quantum.xml
@@ -15,8 +15,5 @@
 -->
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:tint="?attr/colorControlHighlight">
-    <item android:id="@id/mask">
-        <color android:color="@color/white" />
-    </item>
-</ripple>
+    android:tint="?attr/colorControlHighlight"
+    android:pinned="true" />
diff --git a/core/res/res/drawable/item_background_quantum.xml b/core/res/res/drawable/item_background_quantum.xml
index c2a1c127..039ca51 100644
--- a/core/res/res/drawable/item_background_quantum.xml
+++ b/core/res/res/drawable/item_background_quantum.xml
@@ -15,5 +15,8 @@
 -->
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:tint="?attr/colorControlHighlight"
-    android:pinned="true" />
+    android:tint="?attr/colorControlHighlight">
+    <item android:id="@id/mask">
+        <color android:color="@color/white" />
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml
index e109425f4..7fd22ad 100644
--- a/core/res/res/layout/alert_dialog_quantum.xml
+++ b/core/res/res/layout/alert_dialog_quantum.xml
@@ -23,7 +23,10 @@
     android:orientation="vertical"
     android:background="@drawable/dialog_background_quantum"
     android:translationZ="@dimen/floating_window_z"
-    android:layout_margin="@dimen/floating_window_margin">
+    android:layout_marginLeft="@dimen/floating_window_margin_left"
+    android:layout_marginTop="@dimen/floating_window_margin_top"
+    android:layout_marginRight="@dimen/floating_window_margin_right"
+    android:layout_marginBottom="@dimen/floating_window_margin_bottom">
 
     <LinearLayout android:id="@+id/topPanel"
         android:layout_width="match_parent"
@@ -44,7 +47,7 @@
                 android:scaleType="fitCenter"
                 android:src="@null" />
             <TextView android:id="@+id/alertTitle"
-                style="?android:attr/windowTitleStyle"
+                style="?attr/windowTitleStyle"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:layout_width="match_parent"
@@ -65,7 +68,7 @@
             android:layout_height="wrap_content"
             android:clipToPadding="false">
             <TextView android:id="@+id/message"
-                style="?android:attr/textAppearanceMedium"
+                style="?attr/textAppearanceMedium"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:paddingStart="16dip"
@@ -92,26 +95,24 @@
         android:gravity="end"
         android:padding="16dip">
         <LinearLayout
-            style="?android:attr/buttonBarStyle"
+            style="?attr/buttonBarStyle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layoutDirection="locale">
             <Button android:id="@+id/button3"
-                style="?android:attr/buttonBarButtonStyle"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginRight="8dip"
                 android:maxLines="2"
                 android:minHeight="@dimen/alert_dialog_button_bar_height" />
             <Button android:id="@+id/button2"
-                style="?android:attr/buttonBarButtonStyle"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:maxLines="2"
                 android:minHeight="@dimen/alert_dialog_button_bar_height" />
             <Button android:id="@+id/button1"
-                style="?android:attr/buttonBarButtonStyle"
-                android:layout_marginLeft="8dip"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:maxLines="2"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5fec907..c0286f1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -890,9 +890,12 @@
              with the appearance of a singel button broken into segments. -->
         <attr name="segmentedButtonStyle" format="reference" />
 
-        <!-- Background drawable for standalone items that need focus/pressed states. -->
+        <!-- Background drawable for bordered standalone items that need focus/pressed states. -->
         <attr name="selectableItemBackground" format="reference" />
 
+        <!-- Background drawable for borderless standalone items that need focus/pressed states. -->
+        <attr name="selectableItemBackgroundBorderless" format="reference" />
+
         <!-- Style for buttons without an explicit border, often used in groups. -->
         <attr name="borderlessButtonStyle" format="reference" />
 
@@ -4658,11 +4661,11 @@
 
     <!-- Drawable used to show animated touch feedback. -->
     <declare-styleable name="RippleDrawable">
-        <!-- The tint to use for feedback ripples. This attribute is required. -->
+        <!-- The tint to use for ripple effects. This attribute is required. -->
         <attr name="tint" />
-        <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default vlaue is src_atop, which draws over the opaque parts of the drawable. -->
+        <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default value is src_atop, which draws over the opaque parts of the drawable. -->
         <attr name="tintMode" />
-        <!-- Whether to pin feedback ripples to the center of the drawable. Default value is false. -->
+        <!-- Whether to pin ripple effects to the center of the drawable. Default value is false. -->
         <attr name="pinned" format="boolean" />
     </declare-styleable>
 
diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml
index 556463e..976930c0 100644
--- a/core/res/res/values/colors_quantum.xml
+++ b/core/res/res/values/colors_quantum.xml
@@ -16,8 +16,14 @@
 
 <!-- Colors specific to Quantum themes. -->
 <resources>
-    <color name="background_quantum_dark">#ff303030</color>
-    <color name="background_quantum_light">@color/white</color>
+    <color name="background_quantum_dark">#ff414042</color>
+    <color name="background_quantum_light">#fff1f2f2</color>
+
+    <color name="ripple_quantum_dark">#30ffffff</color>
+    <color name="ripple_quantum_light">#30000000</color>
+
+    <color name="button_quantum_dark">#ff5a595b</color>
+    <color name="button_quantum_light">#ffd6d7d7</color>
 
     <color name="bright_foreground_quantum_dark">@color/white</color>
     <color name="bright_foreground_quantum_light">@color/black</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e9d8ccc..f6732d3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1538,6 +1538,7 @@
          -->
     <string-array translatable="false" name="config_globalActionsList">
         <item>power</item>
+        <item>bugreport</item>
         <item>users</item>
     </string-array>
 
diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml
index 2defee2..b5ba1ca 100644
--- a/core/res/res/values/dimens_quantum.xml
+++ b/core/res/res/values/dimens_quantum.xml
@@ -52,7 +52,10 @@
     <dimen name="text_size_small_quantum">14sp</dimen>
 
     <dimen name="floating_window_z">16dp</dimen>
-    <dimen name="floating_window_margin">32dp</dimen>
+    <dimen name="floating_window_margin_left">16dp</dimen>
+    <dimen name="floating_window_margin_top">8dp</dimen>
+    <dimen name="floating_window_margin_right">16dp</dimen>
+    <dimen name="floating_window_margin_bottom">32dp</dimen>
 
     <!-- the amount of elevation for pressed button state-->
     <dimen name="button_pressed_z">2dp</dimen>
diff --git a/core/res/res/values/donottranslate_quantum.xml b/core/res/res/values/donottranslate_quantum.xml
index 83cc4e5..e53c40e 100644
--- a/core/res/res/values/donottranslate_quantum.xml
+++ b/core/res/res/values/donottranslate_quantum.xml
@@ -27,6 +27,6 @@
     <string name="font_family_body_1_quantum">sans-serif</string>
     <string name="font_family_caption_quantum">sans-serif</string>
     <string name="font_family_menu_quantum">sans-serif-medium</string>
-    <string name="font_family_button_quantum">sans-serif</string>
+    <string name="font_family_button_quantum">sans-serif-medium</string>
 
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2d5477c..e9a2f9f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2178,6 +2178,7 @@
   <public type="attr" name="contentInsetLeft" />
   <public type="attr" name="contentInsetRight" />
   <public type="attr" name="paddingMode" />
+  <public type="attr" name="selectableItemBackgroundBorderless" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8286ef9..7234911 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -419,6 +419,8 @@
         current device state, to send as an e-mail message.  It will take a little
         time from starting the bug report until it is ready to be sent; please be
         patient.</string>
+    <!-- Format for build summary info [CHAR LIMIT=NONE] -->
+    <string name="bugreport_status" translatable="false">%s (%s)</string>
 
     <!-- label for item that enables silent mode in phone options dialog -->
     <string name="global_action_toggle_silent_mode">Silent mode</string>
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 6943533..108334fa 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -404,8 +404,10 @@
         <item name="textAppearance">?attr/textAppearanceButton</item>
         <item name="textColor">?attr/textColorPrimary</item>
         <item name="minHeight">48dip</item>
-        <item name="minWidth">96dip</item>
-        <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item>
+        <item name="minWidth">88dip</item>
+
+        <!-- TODO: Turn this back on when we support inset drawable outlines. -->
+        <!-- <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> -->
     </style>
 
     <!-- Small bordered ink button -->
@@ -434,7 +436,6 @@
         <item name="background">@drawable/btn_toggle_quantum</item>
         <item name="textOn">@string/capital_on</item>
         <item name="textOff">@string/capital_off</item>
-        <item name="textAppearance">?attr/textAppearanceSmall</item>
         <item name="minHeight">48dip</item>
     </style>
 
@@ -468,34 +469,29 @@
         <item name="paddingEnd">8dp</item>
     </style>
 
-    <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView">
-        <item name="drawablePadding">4dip</item>
-    </style>
-
+    <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView" />
     <style name="Widget.Quantum.TextSelectHandle" parent="Widget.TextSelectHandle"/>
     <style name="Widget.Quantum.TextSuggestionsPopupWindow" parent="Widget.TextSuggestionsPopupWindow"/>
     <style name="Widget.Quantum.AbsListView" parent="Widget.AbsListView"/>
 
     <style name="Widget.Quantum.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
-        <item name="dropDownSelector">@drawable/list_selector_quantum</item>
+        <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_quantum</item>
     </style>
 
     <style name="Widget.Quantum.CompoundButton" parent="Widget.CompoundButton"/>
 
     <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
-        <item name="background">?attr/selectableItemBackground</item>
-        <item name="drawablePadding">4dip</item>
+        <item name="background">?attr/selectableItemBackgroundBorderless</item>
     </style>
 
     <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
-        <item name="background">?attr/selectableItemBackground</item>
-        <item name="drawablePadding">4dip</item>
+        <item name="background">?attr/selectableItemBackgroundBorderless</item>
     </style>
 
     <style name="Widget.Quantum.CompoundButton.Star" parent="Widget.CompoundButton.Star">
         <item name="button">@drawable/btn_star_quantum</item>
-        <item name="background">?attr/selectableItemBackground</item>
+        <item name="background">?attr/selectableItemBackgroundBorderless</item>
     </style>
 
     <style name="Widget.Quantum.CompoundButton.Switch">
@@ -507,7 +503,7 @@
         <item name="textOff"></item>
         <item name="switchMinWidth">4dip</item>
         <item name="switchPadding">4dip</item>
-        <item name="background">?attr/selectableItemBackground</item>
+        <item name="background">?attr/selectableItemBackgroundBorderless</item>
     </style>
 
     <style name="Widget.Quantum.EditText" parent="Widget.EditText"/>
@@ -522,7 +518,10 @@
     <style name="Widget.Quantum.ExpandableListView.White"/>
     <style name="Widget.Quantum.Gallery" parent="Widget.Gallery"/>
     <style name="Widget.Quantum.GestureOverlayView" parent="Widget.GestureOverlayView"/>
-    <style name="Widget.Quantum.GridView" parent="Widget.GridView"/>
+
+    <style name="Widget.Quantum.GridView" parent="Widget.GridView">
+        <item name="android:listSelector">?attr/selectableItemBackground</item>
+    </style>
 
     <style name="Widget.Quantum.CalendarView" parent="Widget.CalendarView">
         <item name="selectedWeekBackgroundColor">#330099FF</item>
@@ -586,7 +585,7 @@
     <style name="Widget.Quantum.PopupWindow" parent="Widget.PopupWindow"/>
 
     <style name="Widget.Quantum.PopupWindow.ActionMode">
-        <item name="popupBackground">@color/black</item>
+        <item name="popupBackground">@drawable/popup_background_quantum</item>
         <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item>
     </style>
 
@@ -626,7 +625,7 @@
         <item name="paddingStart">16dip</item>
         <item name="paddingEnd">16dip</item>
         <item name="mirrorForRtl">true</item>
-        <item name="background">?attr/selectableItemBackground</item>
+        <item name="background">?attr/selectableItemBackgroundBorderless</item>
     </style>
 
     <style name="Widget.Quantum.RatingBar" parent="Widget.RatingBar">
@@ -653,7 +652,7 @@
 
     <style name="Widget.Quantum.Spinner" parent="Widget.Spinner.DropDown">
         <item name="background">@drawable/spinner_background_quantum</item>
-        <item name="dropDownSelector">@drawable/list_selector_quantum</item>
+        <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_quantum</item>
         <item name="dropDownVerticalOffset">0dip</item>
         <item name="dropDownHorizontalOffset">0dip</item>
@@ -712,7 +711,7 @@
     <style name="Widget.Quantum.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"/>
 
     <style name="Widget.Quantum.ListPopupWindow" parent="Widget.ListPopupWindow">
-        <item name="dropDownSelector">@drawable/list_selector_quantum</item>
+        <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_quantum</item>
         <item name="popupAnimationStyle">@style/Animation.Quantum.Popup</item>
         <item name="dropDownVerticalOffset">0dip</item>
@@ -809,7 +808,7 @@
     </style>
 
     <style name="Widget.Quantum.MediaRouteButton">
-        <item name="background">?attr/selectableItemBackground</item>
+        <item name="background">?attr/selectableItemBackgroundBorderless</item>
         <item name="externalRouteEnabledDrawable">@drawable/ic_media_route_quantum</item>
         <item name="minWidth">56dp</item>
         <item name="minHeight">48dp</item>
@@ -879,12 +878,7 @@
     <style name="Widget.Quantum.Light.ListView" parent="Widget.Quantum.ListView"/>
     <style name="Widget.Quantum.Light.ListView.White" parent="Widget.Quantum.ListView.White"/>
     <style name="Widget.Quantum.Light.PopupWindow" parent="Widget.Quantum.PopupWindow"/>
-
-    <style name="Widget.Quantum.Light.PopupWindow.ActionMode">
-        <item name="popupBackground">@color/white</item>
-        <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item>
-    </style>
-
+    <style name="Widget.Quantum.Light.PopupWindow.ActionMode" parent="Widget.Quantum.PopupWindow.ActionMode"/>
     <style name="Widget.Quantum.Light.ProgressBar" parent="Widget.Quantum.ProgressBar"/>
     <style name="Widget.Quantum.Light.ProgressBar.Horizontal" parent="Widget.Quantum.ProgressBar.Horizontal"/>
     <style name="Widget.Quantum.Light.ProgressBar.Small" parent="Widget.Quantum.ProgressBar.Small"/>
@@ -894,7 +888,6 @@
     <style name="Widget.Quantum.Light.ProgressBar.Small.Inverse" parent="Widget.Quantum.ProgressBar.Small.Inverse"/>
     <style name="Widget.Quantum.Light.ProgressBar.Large.Inverse" parent="Widget.Quantum.ProgressBar.Large.Inverse"/>
     <style name="Widget.Quantum.Light.SeekBar" parent="Widget.Quantum.SeekBar"/>
-
     <style name="Widget.Quantum.Light.RatingBar" parent="Widget.Quantum.RatingBar" />
 
     <style name="Widget.Quantum.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e1629b..8b1ca31 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1388,6 +1388,7 @@
   <java-symbol type="string" name="android_upgrading_title" />
   <java-symbol type="string" name="bugreport_title" />
   <java-symbol type="string" name="bugreport_message" />
+  <java-symbol type="string" name="bugreport_status" />
   <java-symbol type="string" name="faceunlock_multiple_failures" />
   <java-symbol type="string" name="global_action_power_off" />
   <java-symbol type="string" name="global_actions_airplane_mode_off_status" />
@@ -1679,7 +1680,6 @@
   <java-symbol type="anim" name="push_down_out" />
   <java-symbol type="anim" name="push_up_in" />
   <java-symbol type="anim" name="push_up_out" />
-  <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" />
   <java-symbol type="anim" name="lock_screen_behind_enter" />
 
   <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 41f4ff8..648660b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -124,6 +124,7 @@
         <item name="buttonStyleToggle">@android:style/Widget.Button.Toggle</item>
 
         <item name="selectableItemBackground">@android:drawable/item_background</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item>
         <item name="borderlessButtonStyle">?android:attr/buttonStyle</item>
         <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item>
 
@@ -1032,6 +1033,7 @@
         <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.MediaRouteButton</item>
 
         <item name="selectableItemBackground">@android:drawable/item_background_holo_dark</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item>
         <item name="borderlessButtonStyle">@android:style/Widget.Holo.Button.Borderless</item>
         <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item>
 
@@ -1372,6 +1374,7 @@
         <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.Light.MediaRouteButton</item>
 
         <item name="selectableItemBackground">@android:drawable/item_background_holo_light</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item>
         <item name="borderlessButtonStyle">@android:style/Widget.Holo.Light.Button.Borderless</item>
         <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item>
 
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index 47ba764..cdbd771 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -101,6 +101,7 @@
         <item name="mediaRouteButtonStyle">@style/Widget.Quantum.MediaRouteButton</item>
 
         <item name="selectableItemBackground">@drawable/item_background_quantum</item>
+        <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item>
         <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item>
         <item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item>
 
@@ -125,8 +126,7 @@
         <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item>
         <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
 
-        <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
-
+        <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item>
         <item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
 
         <item name="listDividerAlertDialog">@drawable/list_divider_quantum</item>
@@ -308,7 +308,7 @@
         <item name="actionModePopupWindowStyle">@style/Widget.Quantum.PopupWindow.ActionMode</item>
         <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
         <item name="actionBarTheme">@null</item>
-        <item name="actionBarItemBackground">@drawable/item_background_quantum</item>
+        <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
 
         <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
         <item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item>
@@ -378,8 +378,8 @@
 
         <item name="colorControlNormal">?attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorPrimary</item>
-        <item name="colorControlHighlight">#30ffffff</item>
 
+        <item name="colorControlHighlight">@color/ripple_quantum_dark</item>
         <item name="colorButtonNormal">@color/btn_default_quantum_dark</item>
     </style>
 
@@ -446,6 +446,7 @@
         <item name="mediaRouteButtonStyle">@style/Widget.Quantum.Light.MediaRouteButton</item>
 
         <item name="selectableItemBackground">@drawable/item_background_quantum</item>
+        <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item>
         <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
         <item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item>
 
@@ -470,8 +471,7 @@
         <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item>
         <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
 
-        <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
-
+        <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item>
         <item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
 
         <item name="expandableListPreferredItemPaddingLeft">40dip</item>
@@ -655,7 +655,7 @@
         <item name="actionModePopupWindowStyle">@style/Widget.Quantum.Light.PopupWindow.ActionMode</item>
         <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
         <item name="actionBarTheme">@null</item>
-        <item name="actionBarItemBackground">@drawable/item_background_quantum</item>
+        <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
 
         <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
         <item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item>
@@ -668,7 +668,7 @@
         <item name="dividerVertical">?attr/listDivider</item>
         <item name="dividerHorizontal">?attr/listDivider</item>
         <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar</item>
-        <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
         <item name="segmentedButtonStyle">@style/Widget.Quantum.Light.SegmentedButton</item>
 
         <!-- SearchView attributes -->
@@ -721,8 +721,8 @@
 
         <item name="colorControlNormal">?attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorPrimary</item>
-        <item name="colorControlHighlight">#30000000</item>
 
+        <item name="colorControlHighlight">@color/ripple_quantum_light</item>
         <item name="colorButtonNormal">@color/btn_default_quantum_light</item>
     </style>
 
@@ -770,7 +770,8 @@
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
 
-        <item name="colorButtonNormal">@color/quantum_grey_100</item>
+        <item name="colorControlHighlight">@color/ripple_quantum_light</item>
+        <item name="colorButtonNormal">@color/btn_default_quantum_light</item>
     </style>
 
     <!-- Theme overlay that replaces colors with their dark versions but preserves
@@ -805,7 +806,8 @@
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item>
 
-        <item name="colorButtonNormal">@color/quantum_grey_700</item>
+        <item name="colorControlHighlight">@color/ripple_quantum_dark</item>
+        <item name="colorButtonNormal">@color/btn_default_quantum_dark</item>
     </style>
 
     <!-- Theme overlay that replaces the activated control color (which by default
@@ -1002,7 +1004,7 @@
         <item name="colorBackgroundCacheHint">@null</item>
 
         <item name="buttonBarStyle">@style/Widget.Quantum.ButtonBar.AlertDialog</item>
-        <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless.Small</item>
+        <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item>
 
         <item name="textAppearance">@style/TextAppearance.Quantum</item>
         <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item>
@@ -1121,7 +1123,7 @@
         <item name="colorBackgroundCacheHint">@null</item>
 
         <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar.AlertDialog</item>
-        <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
+        <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
 
         <item name="textAppearance">@style/TextAppearance.Quantum</item>
         <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item>
diff --git a/data/fonts/Roboto-Black.ttf b/data/fonts/Roboto-Black.ttf
index 2cdbe43..cb905bc 100644
--- a/data/fonts/Roboto-Black.ttf
+++ b/data/fonts/Roboto-Black.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Bold.ttf b/data/fonts/Roboto-Bold.ttf
index 15c9b4e..68822ca 100644
--- a/data/fonts/Roboto-Bold.ttf
+++ b/data/fonts/Roboto-Bold.ttf
Binary files differ
diff --git a/data/fonts/Roboto-BoldItalic.ttf b/data/fonts/Roboto-BoldItalic.ttf
index a0abf30..aebf8eb 100644
--- a/data/fonts/Roboto-BoldItalic.ttf
+++ b/data/fonts/Roboto-BoldItalic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Italic.ttf b/data/fonts/Roboto-Italic.ttf
index 67b5394..2041cbc 100644
--- a/data/fonts/Roboto-Italic.ttf
+++ b/data/fonts/Roboto-Italic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Light.ttf b/data/fonts/Roboto-Light.ttf
index d9fb64a..aa45340 100644
--- a/data/fonts/Roboto-Light.ttf
+++ b/data/fonts/Roboto-Light.ttf
Binary files differ
diff --git a/data/fonts/Roboto-LightItalic.ttf b/data/fonts/Roboto-LightItalic.ttf
index 1fd1d31..a85444f 100644
--- a/data/fonts/Roboto-LightItalic.ttf
+++ b/data/fonts/Roboto-LightItalic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Medium.ttf b/data/fonts/Roboto-Medium.ttf
index c63c115..a3c1a1f 100644
--- a/data/fonts/Roboto-Medium.ttf
+++ b/data/fonts/Roboto-Medium.ttf
Binary files differ
diff --git a/data/fonts/Roboto-MediumItalic.ttf b/data/fonts/Roboto-MediumItalic.ttf
index cd7c835..a30aa0c 100644
--- a/data/fonts/Roboto-MediumItalic.ttf
+++ b/data/fonts/Roboto-MediumItalic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Regular.ttf b/data/fonts/Roboto-Regular.ttf
index 9cb4a5a..0e58508 100644
--- a/data/fonts/Roboto-Regular.ttf
+++ b/data/fonts/Roboto-Regular.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Thin.ttf b/data/fonts/Roboto-Thin.ttf
index f02f100..8779333 100644
--- a/data/fonts/Roboto-Thin.ttf
+++ b/data/fonts/Roboto-Thin.ttf
Binary files differ
diff --git a/data/fonts/Roboto-ThinItalic.ttf b/data/fonts/Roboto-ThinItalic.ttf
index 12a2ce0..b79cb26 100644
--- a/data/fonts/Roboto-ThinItalic.ttf
+++ b/data/fonts/Roboto-ThinItalic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Bold.ttf b/data/fonts/RobotoCondensed-Bold.ttf
index 1079af6..3e06c7c 100644
--- a/data/fonts/RobotoCondensed-Bold.ttf
+++ b/data/fonts/RobotoCondensed-Bold.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-BoldItalic.ttf b/data/fonts/RobotoCondensed-BoldItalic.ttf
index e7f13c2..aaf9fe0 100644
--- a/data/fonts/RobotoCondensed-BoldItalic.ttf
+++ b/data/fonts/RobotoCondensed-BoldItalic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Italic.ttf b/data/fonts/RobotoCondensed-Italic.ttf
index 7fa04481..d2b611f 100644
--- a/data/fonts/RobotoCondensed-Italic.ttf
+++ b/data/fonts/RobotoCondensed-Italic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Light.ttf b/data/fonts/RobotoCondensed-Light.ttf
index 96b75dd..d4eb198 100644
--- a/data/fonts/RobotoCondensed-Light.ttf
+++ b/data/fonts/RobotoCondensed-Light.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-LightItalic.ttf b/data/fonts/RobotoCondensed-LightItalic.ttf
index 7a2c164..a08f3f4 100644
--- a/data/fonts/RobotoCondensed-LightItalic.ttf
+++ b/data/fonts/RobotoCondensed-LightItalic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Regular.ttf b/data/fonts/RobotoCondensed-Regular.ttf
index 734cc40..b9fc49c 100644
--- a/data/fonts/RobotoCondensed-Regular.ttf
+++ b/data/fonts/RobotoCondensed-Regular.ttf
Binary files differ
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index f82acc3..1512da5 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -612,7 +612,9 @@
             case LINE: {
                 RectF r = mRect;
                 float y = r.centerY();
-                canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
+                if (haveStroke) {
+                    canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
+                }
                 break;
             }
             case RING:
@@ -1153,6 +1155,10 @@
         // Extract the theme attributes, if any.
         st.mAttrPadding = a.extractThemeAttrs();
 
+        if (st.mPadding == null) {
+            st.mPadding = new Rect();
+        }
+
         final Rect pad = st.mPadding;
         pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left),
                 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top),
@@ -1424,12 +1430,10 @@
     }
 
     final static class GradientState extends ConstantState {
-        public final Rect mPadding = new Rect();
-
         public int mChangingConfigurations;
         public int mShape = RECTANGLE;
         public int mGradient = LINEAR_GRADIENT;
-        public int mAngle;
+        public int mAngle = 0;
         public Orientation mOrientation;
         public ColorStateList mColorStateList;
         public ColorStateList mStrokeColorStateList;
@@ -1437,11 +1441,12 @@
         public int[] mTempColors; // no need to copy
         public float[] mTempPositions; // no need to copy
         public float[] mPositions;
-        public int mStrokeWidth = -1;   // if >= 0 use stroking.
-        public float mStrokeDashWidth;
-        public float mStrokeDashGap;
-        public float mRadius;    // use this if mRadiusArray is null
-        public float[] mRadiusArray;
+        public int mStrokeWidth = -1; // if >= 0 use stroking.
+        public float mStrokeDashWidth = 0.0f;
+        public float mStrokeDashGap = 0.0f;
+        public float mRadius = 0.0f; // use this if mRadiusArray is null
+        public float[] mRadiusArray = null;
+        public Rect mPadding = null;
         public int mWidth = -1;
         public int mHeight = -1;
         public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
@@ -1491,7 +1496,7 @@
                 mRadiusArray = state.mRadiusArray.clone();
             }
             if (state.mPadding != null) {
-                mPadding.set(state.mPadding);
+                mPadding = new Rect(state.mPadding);
             }
             mWidth = state.mWidth;
             mHeight = state.mHeight;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 2e47d3a..75cb0a00 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -304,7 +304,7 @@
      * @param right The right padding of the new layer.
      * @param bottom The bottom padding of the new layer.
      */
-    private void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right,
+    void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right,
             int bottom) {
         final LayerState st = mLayerState;
         final int N = st.mChildren != null ? st.mChildren.length : 0;
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 24e8de6..ada741b 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -25,9 +25,10 @@
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
+import android.util.MathUtils;
 import android.view.HardwareCanvas;
 import android.view.RenderNodeAnimator;
-import android.view.animation.AccelerateInterpolator;
+import android.view.animation.LinearInterpolator;
 
 import java.util.ArrayList;
 
@@ -35,7 +36,7 @@
  * Draws a Quantum Paper ripple.
  */
 class Ripple {
-    private static final TimeInterpolator INTERPOLATOR = new AccelerateInterpolator();
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
     private static final float GLOBAL_SPEED = 1.0f;
     private static final float WAVE_TOUCH_DOWN_ACCELERATION = 512.0f * GLOBAL_SPEED;
@@ -47,17 +48,23 @@
     private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
     private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>();
 
-    private final Drawable mOwner;
+    private final RippleDrawable mOwner;
 
     /** Bounds used for computing max radius. */
     private final Rect mBounds;
 
     /** Full-opacity color for drawing this ripple. */
-    private final int mColor;
+    private int mColor;
 
     /** Maximum ripple radius. */
     private float mOuterRadius;
 
+    /** Screen density used to adjust pixel-based velocities. */
+    private float mDensity;
+
+    private float mStartingX;
+    private float mStartingY;
+
     // Hardware rendering properties.
     private CanvasProperty<Paint> mPropPaint;
     private CanvasProperty<Float> mPropRadius;
@@ -78,13 +85,13 @@
     // Software rendering properties.
     private float mOuterOpacity = 0;
     private float mOpacity = 1;
-    private float mRadius = 0;
     private float mOuterX;
     private float mOuterY;
-    private float mX;
-    private float mY;
 
-    private boolean mFinished;
+    // Values used to tween between the start and end positions.
+    private float mTweenRadius = 0;
+    private float mTweenX = 0;
+    private float mTweenY = 0;
 
     /** Whether we should be drawing hardware animations. */
     private boolean mHardwareAnimating;
@@ -92,28 +99,42 @@
     /** Whether we can use hardware acceleration for the exit animation. */
     private boolean mCanUseHardware;
 
+    /** Whether we have an explicit maximum radius. */
+    private boolean mHasMaxRadius;
+
     /**
      * Creates a new ripple.
      */
-    public Ripple(Drawable owner, Rect bounds, int color) {
+    public Ripple(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
         mOwner = owner;
         mBounds = bounds;
+        mStartingX = startingX;
+        mStartingY = startingY;
+    }
+
+    public void setup(int maxRadius, int color, float density) {
         mColor = color | 0xFF000000;
 
-        final float halfWidth = bounds.width() / 2.0f;
-        final float halfHeight = bounds.height() / 2.0f;
-        mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
+            mHasMaxRadius = true;
+            mOuterRadius = maxRadius;
+        } else {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+        }
+
         mOuterX = 0;
         mOuterY = 0;
+        mDensity = density;
     }
 
-    public void setRadius(float r) {
-        mRadius = r;
-        invalidateSelf();
-    }
-
-    public float getRadius() {
-        return mRadius;
+    public void onHotspotBoundsChanged() {
+        if (!mHasMaxRadius) {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+        }
     }
 
     public void setOpacity(float a) {
@@ -134,29 +155,31 @@
         return mOuterOpacity;
     }
 
-    public void setX(float x) {
-        mX = x;
+    public void setRadiusGravity(float r) {
+        mTweenRadius = r;
         invalidateSelf();
     }
 
-    public float getX() {
-        return mX;
+    public float getRadiusGravity() {
+        return mTweenRadius;
     }
 
-    public void setY(float y) {
-        mY = y;
+    public void setXGravity(float x) {
+        mTweenX = x;
         invalidateSelf();
     }
 
-    public float getY() {
-        return mY;
+    public float getXGravity() {
+        return mTweenX;
     }
 
-    /**
-     * Returns whether this ripple has finished exiting.
-     */
-    public boolean isFinished() {
-        return mFinished;
+    public void setYGravity(float y) {
+        mTweenY = y;
+        invalidateSelf();
+    }
+
+    public float getYGravity() {
+        return mTweenY;
     }
 
     /**
@@ -204,28 +227,27 @@
     }
 
     private boolean drawSoftware(Canvas c, Paint p) {
-        final float radius = mRadius;
-        final float opacity = mOpacity;
-        final float outerOpacity = mOuterOpacity;
+        boolean hasContent = false;
 
         // Cache the paint alpha so we can restore it later.
         final int paintAlpha = p.getAlpha();
-        final int alpha = (int) (255 * opacity + 0.5f);
-        final int outerAlpha = (int) (255 * outerOpacity + 0.5f);
 
-        boolean hasContent = false;
-
-        if (outerAlpha > 0 && alpha > 0) {
-            p.setAlpha(Math.min(alpha, outerAlpha));
+        final int outerAlpha = (int) (255 * mOuterOpacity + 0.5f);
+        if (outerAlpha > 0 && mOuterRadius > 0) {
+            p.setAlpha(outerAlpha);
             p.setStyle(Style.FILL);
             c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
             hasContent = true;
         }
 
-        if (opacity > 0 && radius > 0) {
+        final int alpha = (int) (255 * mOpacity + 0.5f);
+        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
+        if (alpha > 0 && radius > 0) {
+            final float x = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+            final float y = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
             p.setAlpha(alpha);
             p.setStyle(Style.FILL);
-            c.drawCircle(mX, mY, radius, p);
+            c.drawCircle(x, y, radius, p);
             hasContent = true;
         }
 
@@ -235,45 +257,49 @@
     }
 
     /**
-     * Returns the maximum bounds for this ripple.
+     * Returns the maximum bounds of the ripple relative to the ripple center.
      */
     public void getBounds(Rect bounds) {
         final int outerX = (int) mOuterX;
         final int outerY = (int) mOuterY;
         final int r = (int) mOuterRadius;
         bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
-
-        final int x = (int) mX;
-        final int y = (int) mY;
-        bounds.union(x - r, y - r, x + r, y + r);
     }
 
     /**
-     * Starts the enter animation at the specified absolute coordinates.
+     * Specifies the starting position relative to the drawable bounds. No-op if
+     * the ripple has already entered.
      */
-    public void enter(float x, float y) {
-        mX = x - mBounds.exactCenterX();
-        mY = y - mBounds.exactCenterY();
+    public void move(float x, float y) {
+        mStartingX = x;
+        mStartingY = y;
+    }
 
+    /**
+     * Starts the enter animation.
+     */
+    public void enter() {
         final int radiusDuration = (int)
-                (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION) + 0.5);
+                (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
         final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY);
 
-        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", 0, mOuterRadius);
+        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
         radius.setAutoCancel(true);
         radius.setDuration(radiusDuration);
+        radius.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "x", mOuterX);
+        final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
         cX.setAutoCancel(true);
         cX.setDuration(radiusDuration);
 
-        final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "y", mOuterY);
+        final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
         cY.setAutoCancel(true);
         cY.setDuration(radiusDuration);
 
         final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
         outer.setAutoCancel(true);
         outer.setDuration(outerDuration);
+        outer.setInterpolator(LINEAR_INTERPOLATOR);
 
         mAnimRadius = radius;
         mAnimOuterOpacity = outer;
@@ -295,15 +321,16 @@
     public void exit() {
         cancelSoftwareAnimations();
 
+        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final float remaining;
         if (mAnimRadius != null && mAnimRadius.isRunning()) {
-            remaining = mOuterRadius - mRadius;
+            remaining = mOuterRadius - radius;
         } else {
             remaining = mOuterRadius;
         }
 
         final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
-                + WAVE_TOUCH_DOWN_ACCELERATION)) + 0.5);
+                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
         final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
 
         // Determine at what time the inner and outer opacity intersect.
@@ -325,6 +352,8 @@
             int inflectionOpacity) {
         mPendingAnimations.clear();
 
+        final float startX = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+        final float startY = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
         final Paint outerPaint = new Paint();
         outerPaint.setAntiAlias(true);
         outerPaint.setColor(mColor);
@@ -335,58 +364,69 @@
         mPropOuterX = CanvasProperty.createFloat(mOuterX);
         mPropOuterY = CanvasProperty.createFloat(mOuterY);
 
+        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
         paint.setColor(mColor);
         paint.setAlpha((int) (255 * mOpacity + 0.5f));
         paint.setStyle(Style.FILL);
         mPropPaint = CanvasProperty.createPaint(paint);
-        mPropRadius = CanvasProperty.createFloat(mRadius);
-        mPropX = CanvasProperty.createFloat(mX);
-        mPropY = CanvasProperty.createFloat(mY);
+        mPropRadius = CanvasProperty.createFloat(startRadius);
+        mPropX = CanvasProperty.createFloat(startX);
+        mPropY = CanvasProperty.createFloat(startY);
 
-        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mOuterRadius);
-        radius.setDuration(radiusDuration);
+        final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
+        radiusAnim.setDuration(radiusDuration);
+        radiusAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
-        x.setDuration(radiusDuration);
+        final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
+        xAnim.setDuration(radiusDuration);
+        xAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
-        y.setDuration(radiusDuration);
+        final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
+        yAnim.setDuration(radiusDuration);
+        yAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
                 RenderNodeAnimator.PAINT_ALPHA, 0);
-        opacity.setDuration(opacityDuration);
-        opacity.addListener(mAnimationListener);
+        opacityAnim.setDuration(opacityDuration);
+        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator outerOpacity;
+        final RenderNodeAnimator outerOpacityAnim;
         if (outerInflection > 0) {
             // Outer opacity continues to increase for a bit.
-            outerOpacity = new RenderNodeAnimator(
+            outerOpacityAnim = new RenderNodeAnimator(
                     mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
-            outerOpacity.setDuration(outerInflection);
+            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
             // Chain the outer opacity exit animation.
             final int outerDuration = opacityDuration - outerInflection;
             if (outerDuration > 0) {
-                final RenderNodeAnimator outerFadeOut = new RenderNodeAnimator(
+                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
                         mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-                outerFadeOut.setDuration(outerDuration);
-                outerFadeOut.setStartDelay(outerInflection);
+                outerFadeOutAnim.setDuration(outerDuration);
+                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+                outerFadeOutAnim.setStartDelay(outerInflection);
+                outerFadeOutAnim.addListener(mAnimationListener);
 
-                mPendingAnimations.add(outerFadeOut);
+                mPendingAnimations.add(outerFadeOutAnim);
+            } else {
+                outerOpacityAnim.addListener(mAnimationListener);
             }
         } else {
-            outerOpacity = new RenderNodeAnimator(
+            outerOpacityAnim = new RenderNodeAnimator(
                     mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-            outerOpacity.setDuration(opacityDuration);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+            outerOpacityAnim.setDuration(opacityDuration);
+            outerOpacityAnim.addListener(mAnimationListener);
         }
 
-        mPendingAnimations.add(radius);
-        mPendingAnimations.add(opacity);
-        mPendingAnimations.add(outerOpacity);
-        mPendingAnimations.add(x);
-        mPendingAnimations.add(y);
+        mPendingAnimations.add(radiusAnim);
+        mPendingAnimations.add(opacityAnim);
+        mPendingAnimations.add(outerOpacityAnim);
+        mPendingAnimations.add(xAnim);
+        mPendingAnimations.add(yAnim);
 
         mHardwareAnimating = true;
 
@@ -394,65 +434,80 @@
     }
 
     private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
-            float inflectionOpacity) {
-        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", mOuterRadius);
-        radius.setAutoCancel(true);
-        radius.setDuration(radiusDuration);
+            int inflectionOpacity) {
+        final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
+        radiusAnim.setAutoCancel(true);
+        radiusAnim.setDuration(radiusDuration);
+        radiusAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator x = ObjectAnimator.ofFloat(this, "x", mOuterX);
-        x.setAutoCancel(true);
-        x.setDuration(radiusDuration);
+        final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
+        xAnim.setAutoCancel(true);
+        xAnim.setDuration(radiusDuration);
+        xAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator y = ObjectAnimator.ofFloat(this, "y", mOuterY);
-        y.setAutoCancel(true);
-        y.setDuration(radiusDuration);
+        final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
+        yAnim.setAutoCancel(true);
+        yAnim.setDuration(radiusDuration);
+        yAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "opacity", 0);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(opacityDuration);
-        opacity.addListener(mAnimationListener);
+        final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
+        opacityAnim.setAutoCancel(true);
+        opacityAnim.setDuration(opacityDuration);
+        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator outerOpacity;
+        final ObjectAnimator outerOpacityAnim;
         if (outerInflection > 0) {
             // Outer opacity continues to increase for a bit.
-            outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", inflectionOpacity);
-            outerOpacity.setDuration(outerInflection);
+            outerOpacityAnim = ObjectAnimator.ofFloat(this,
+                    "outerOpacity", inflectionOpacity / 255.0f);
+            outerOpacityAnim.setAutoCancel(true);
+            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
             // Chain the outer opacity exit animation.
             final int outerDuration = opacityDuration - outerInflection;
-            outerOpacity.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    final ObjectAnimator outerFadeOut = ObjectAnimator.ofFloat(Ripple.this,
-                            "outerOpacity", 0);
-                    outerFadeOut.setDuration(outerDuration);
+            if (outerDuration > 0) {
+                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this,
+                                "outerOpacity", 0);
+                        outerFadeOutAnim.setAutoCancel(true);
+                        outerFadeOutAnim.setDuration(outerDuration);
+                        outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+                        outerFadeOutAnim.addListener(mAnimationListener);
 
-                    mAnimOuterOpacity = outerFadeOut;
+                        mAnimOuterOpacity = outerFadeOutAnim;
 
-                    outerFadeOut.start();
-                }
+                        outerFadeOutAnim.start();
+                    }
 
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    animation.removeListener(this);
-                }
-            });
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        animation.removeListener(this);
+                    }
+                });
+            } else {
+                outerOpacityAnim.addListener(mAnimationListener);
+            }
         } else {
-            outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
-            outerOpacity.setDuration(opacityDuration);
+            outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
+            outerOpacityAnim.setAutoCancel(true);
+            outerOpacityAnim.setDuration(opacityDuration);
+            outerOpacityAnim.addListener(mAnimationListener);
         }
 
-        mAnimRadius = radius;
-        mAnimOpacity = opacity;
-        mAnimOuterOpacity = outerOpacity;
-        mAnimX = opacity;
-        mAnimY = opacity;
+        mAnimRadius = radiusAnim;
+        mAnimOpacity = opacityAnim;
+        mAnimOuterOpacity = outerOpacityAnim;
+        mAnimX = opacityAnim;
+        mAnimY = opacityAnim;
 
-        radius.start();
-        opacity.start();
-        outerOpacity.start();
-        x.start();
-        y.start();
+        radiusAnim.start();
+        opacityAnim.start();
+        outerOpacityAnim.start();
+        xAnim.start();
+        yAnim.start();
     }
 
     /**
@@ -498,6 +553,11 @@
         runningAnimations.clear();
     }
 
+    private void removeSelf() {
+        // The owner will invalidate itself.
+        mOwner.removeRipple(this);
+    }
+
     private void invalidateSelf() {
         mOwner.invalidateSelf();
     }
@@ -505,12 +565,7 @@
     private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
-            mFinished = true;
-        }
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-            mFinished = true;
+            removeSelf();
         }
     };
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 1bd7cac..9d7a8b6 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -25,17 +25,14 @@
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PointF;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.SparseArray;
 
 import com.android.internal.R;
-import com.android.org.bouncycastle.util.Arrays;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -71,10 +68,17 @@
 public class RippleDrawable extends LayerDrawable {
     private static final String LOG_TAG = RippleDrawable.class.getSimpleName();
     private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
-    private static final PorterDuffXfermode DST_ATOP = new PorterDuffXfermode(Mode.DST_ATOP);
     private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
     private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
 
+    /**
+     * Constant for automatically determining the maximum ripple radius.
+     *
+     * @see #setMaxRadius(int)
+     * @hide
+     */
+    public static final int RADIUS_AUTO = -1;
+
     /** The maximum number of ripples supported. */
     private static final int MAX_RIPPLES = 10;
 
@@ -91,17 +95,8 @@
 
     private final RippleState mState;
 
-    /**
-     * Lazily-created map of pending hotspot locations. These may be modified by
-     * calls to {@link #setHotspot(float, float)}.
-     */
-    private SparseArray<PointF> mPendingHotspots;
-
-    /**
-     * Lazily-created map of active hotspot locations. These may be modified by
-     * calls to {@link #setHotspot(float, float)}.
-     */
-    private SparseArray<Ripple> mActiveHotspots;
+    /** The current hotspot. May be actively animating or pending entry. */
+    private Ripple mHotspot;
 
     /**
      * Lazily-created array of actively animating ripples. Inactive ripples are
@@ -122,18 +117,46 @@
     /** Whether bounds are being overridden. */
     private boolean mOverrideBounds;
 
+    /** Whether the hotspot is currently active (e.g. focused or pressed). */
+    private boolean mActive;
+
     RippleDrawable() {
+        this(null, null);
+    }
+
+    /**
+     * Creates a new ripple drawable with the specified content and mask
+     * drawables.
+     *
+     * @param content The content drawable, may be {@code null}
+     * @param mask The mask drawable, may be {@code null}
+     */
+    public RippleDrawable(Drawable content, Drawable mask) {
         this(new RippleState(null, null, null), null, null);
+
+        if (content != null) {
+            addLayer(content, null, 0, 0, 0, 0, 0);
+        }
+
+        if (mask != null) {
+            addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
+        }
+
+        ensurePadding();
     }
 
     @Override
     public void setAlpha(int alpha) {
-        
+        super.setAlpha(alpha);
+
+        // TODO: Should we support this?
     }
 
     @Override
     public void setColorFilter(ColorFilter cf) {
-        
+        super.setColorFilter(cf);
+
+        // TODO: Should we support this?
     }
 
     @Override
@@ -146,20 +169,18 @@
     protected boolean onStateChange(int[] stateSet) {
         super.onStateChange(stateSet);
 
-        final boolean pressed = Arrays.contains(stateSet, R.attr.state_pressed);
-        if (!pressed) {
-            removeHotspot(R.attr.state_pressed);
-        } else {
-            activateHotspot(R.attr.state_pressed);
+        boolean active = false;
+        final int N = stateSet.length;
+        for (int i = 0; i < N; i++) {
+            if (stateSet[i] == R.attr.state_focused
+                    || stateSet[i] == R.attr.state_pressed) {
+                active = true;
+                break;
+            }
         }
+        setActive(active);
 
-        final boolean focused = Arrays.contains(stateSet, R.attr.state_focused);
-        if (!focused) {
-            removeHotspot(R.attr.state_focused);
-        } else {
-            activateHotspot(R.attr.state_focused);
-        }
-
+        // Update the paint color. Only applicable when animated in software.
         if (mRipplePaint != null && mState.mTint != null) {
             final ColorStateList stateList = mState.mTint;
             final int newColor = stateList.getColorForState(stateSet, 0);
@@ -174,12 +195,25 @@
         return false;
     }
 
+    private void setActive(boolean active) {
+        if (mActive != active) {
+            mActive = active;
+
+            if (active) {
+                activateHotspot();
+            } else {
+                removeHotspot();
+            }
+        }
+    }
+
     @Override
     protected void onBoundsChange(Rect bounds) {
         super.onBoundsChange(bounds);
 
         if (!mOverrideBounds) {
             mHotspotBounds.set(bounds);
+            onHotspotBoundsChanged();
         }
 
         invalidateSelf();
@@ -272,7 +306,7 @@
     /**
      * Initializes the constant state from the values in the typed array.
      */
-    private void updateStateFromTypedArray(TypedArray a) {
+    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
         final RippleState state = mState;
 
         // Extract the theme attributes, if any.
@@ -289,6 +323,12 @@
         }
 
         mState.mPinned = a.getBoolean(R.styleable.RippleDrawable_pinned, mState.mPinned);
+
+        // If we're not waiting on a theme, verify required attributes.
+        if (state.mTouchThemeAttrs == null && mState.mTint == null) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    ": <ripple> requires a valid tint attribute");
+        }
     }
 
     /**
@@ -314,8 +354,13 @@
 
         final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
                 R.styleable.RippleDrawable);
-        updateStateFromTypedArray(a);
-        a.recycle();
+        try {
+            updateStateFromTypedArray(a);
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } finally {
+            a.recycle();
+        }
     }
 
     @Override
@@ -330,36 +375,15 @@
             y = mHotspotBounds.exactCenterY();
         }
 
-        // TODO: We should only have a single pending/active hotspot.
-        final int id = R.attr.state_pressed;
-        final int[] stateSet = getState();
-        if (!Arrays.contains(stateSet, id)) {
-            // The hotspot is not active, so just modify the pending location.
-            getOrCreatePendingHotspot(id).set(x, y);
-            return;
-        }
+        if (mHotspot == null) {
+            mHotspot = new Ripple(this, mHotspotBounds, x, y);
 
-        if (mAnimatingRipplesCount >= MAX_RIPPLES) {
-            // This should never happen unless the user is tapping like a maniac
-            // or there is a bug that's preventing ripples from being removed.
-            Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
-            return;
+            if (mActive) {
+                activateHotspot();
+            }
+        } else {
+            mHotspot.move(x, y);
         }
-
-        if (mActiveHotspots == null) {
-            mActiveHotspots = new SparseArray<Ripple>();
-            mAnimatingRipples = new Ripple[MAX_RIPPLES];
-        }
-
-        final Ripple ripple = mActiveHotspots.get(id);
-        if (ripple != null) {
-            // The hotspot is active, but we can't move it because it's probably
-            // busy animating the center position.
-            return;
-        }
-
-        // The hotspot needs to be made active.
-        createActiveHotspot(id, x, y);
     }
 
     private boolean circleContains(Rect bounds, float x, float y) {
@@ -374,74 +398,44 @@
         return pointRadius < boundsRadius;
     }
 
-    private PointF getOrCreatePendingHotspot(int id) {
-        final PointF p;
-        if (mPendingHotspots == null) {
-            mPendingHotspots = new SparseArray<>(2);
-            p = null;
-        } else {
-            p = mPendingHotspots.get(id);
-        }
-
-        if (p == null) {
-            final PointF newPoint = new PointF();
-            mPendingHotspots.put(id, newPoint);
-            return newPoint;
-        } else {
-            return p;
-        }
-    }
-
-    /**
-     * Moves a hotspot from pending to active.
-     */
-    private void activateHotspot(int id) {
-        final SparseArray<PointF> pendingHotspots = mPendingHotspots;
-        if (pendingHotspots != null) {
-            final int index = pendingHotspots.indexOfKey(id);
-            if (index >= 0) {
-                final PointF hotspot = pendingHotspots.valueAt(index);
-                pendingHotspots.removeAt(index);
-                createActiveHotspot(id, hotspot.x, hotspot.y);
-            }
-        }
-    }
-
     /**
      * Creates an active hotspot at the specified location.
      */
-    private void createActiveHotspot(int id, float x, float y) {
+    private void activateHotspot() {
+        if (mAnimatingRipplesCount >= MAX_RIPPLES) {
+            // This should never happen unless the user is tapping like a maniac
+            // or there is a bug that's preventing ripples from being removed.
+            Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
+            return;
+        }
+
+        if (mHotspot == null) {
+            final float x = mHotspotBounds.exactCenterX();
+            final float y = mHotspotBounds.exactCenterY();
+            mHotspot = new Ripple(this, mHotspotBounds, x, y);
+        }
+
         final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
-        final Ripple newRipple = new Ripple(this, mHotspotBounds, color);
-        newRipple.enter(x, y);
+        mHotspot.setup(mState.mMaxRadius, color, mDensity);
+        mHotspot.enter();
 
         if (mAnimatingRipples == null) {
             mAnimatingRipples = new Ripple[MAX_RIPPLES];
         }
-        mAnimatingRipples[mAnimatingRipplesCount++] = newRipple;
-
-        if (mActiveHotspots == null) {
-            mActiveHotspots = new SparseArray<Ripple>();
-        }
-        mActiveHotspots.put(id, newRipple);
+        mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot;
     }
 
-    private void removeHotspot(int id) {
-        if (mActiveHotspots == null) {
-            return;
-        }
-
-        final Ripple ripple = mActiveHotspots.get(id);
-        if (ripple != null) {
-            ripple.exit();
-
-            mActiveHotspots.remove(id);
+    private void removeHotspot() {
+        if (mHotspot != null) {
+            mHotspot.exit();
+            mHotspot = null;
         }
     }
 
     private void clearHotspots() {
-        if (mActiveHotspots != null) {
-            mActiveHotspots.clear();
+        if (mHotspot != null) {
+            mHotspot.cancel();
+            mHotspot = null;
         }
 
         final int count = mAnimatingRipplesCount;
@@ -459,73 +453,113 @@
     public void setHotspotBounds(int left, int top, int right, int bottom) {
         mOverrideBounds = true;
         mHotspotBounds.set(left, top, right, bottom);
+
+        onHotspotBoundsChanged();
+    }
+
+    /**
+     * Notifies all the animating ripples that the hotspot bounds have changed.
+     */
+    private void onHotspotBoundsChanged() {
+        final int count = mAnimatingRipplesCount;
+        final Ripple[] ripples = mAnimatingRipples;
+        for (int i = 0; i < count; i++) {
+            ripples[i].onHotspotBoundsChanged();
+        }
     }
 
     @Override
     public void draw(Canvas canvas) {
-        final int N = mLayerState.mNum;
-        final Rect bounds = getBounds();
-        final ChildDrawable[] array = mLayerState.mChildren;
-        final boolean maskOnly = mState.mMask != null && N == 1;
+        final Rect bounds = isProjected() ? getDirtyBounds() : getBounds();
 
-        int restoreToCount = drawRippleLayer(canvas, maskOnly);
+        // Draw the content into a layer first.
+        final int contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
 
-        if (restoreToCount >= 0) {
-            // We have a ripple layer that contains ripples. If we also have an
-            // explicit mask drawable, apply it now using DST_IN blending.
-            if (mState.mMask != null) {
-                canvas.saveLayer(bounds.left, bounds.top, bounds.right,
-                        bounds.bottom, getMaskingPaint(DST_IN));
-                mState.mMask.draw(canvas);
-                canvas.restoreToCount(restoreToCount);
-                restoreToCount = -1;
-            }
+        // Next, draw the ripples into a layer.
+        final int rippleLayer = drawRippleLayer(canvas, bounds, mState.mTintXfermode);
 
-            // If there's more content, we need an extra masking layer to merge
-            // the ripples over the content.
-            if (!maskOnly) {
-                final PorterDuffXfermode xfermode = mState.getTintXfermodeInverse();
-                final int count = canvas.saveLayer(bounds.left, bounds.top,
-                        bounds.right, bounds.bottom, getMaskingPaint(xfermode));
-                if (restoreToCount < 0) {
-                    restoreToCount = count;
-                }
-            }
+        // If we have ripples, draw the masking layer.
+        if (rippleLayer >= 0) {
+            drawMaskingLayer(canvas, bounds, DST_IN);
         }
 
+        // Composite the layers if needed.
+        if (contentLayer >= 0) {
+            canvas.restoreToCount(contentLayer);
+        } else if (rippleLayer >= 0) {
+            canvas.restoreToCount(rippleLayer);
+        }
+    }
+
+    /**
+     * Removes a ripple from the animating ripple list.
+     *
+     * @param ripple the ripple to remove
+     */
+    void removeRipple(Ripple ripple) {
+        // Ripple ripple ripple ripple. Ripple ripple.
+        final Ripple[] ripples = mAnimatingRipples;
+        final int count = mAnimatingRipplesCount;
+        final int index = getRippleIndex(ripple);
+        if (index >= 0) {
+            for (int i = index + 1; i < count; i++) {
+                ripples[i - 1] = ripples[i];
+            }
+            ripples[count - 1] = null;
+            mAnimatingRipplesCount--;
+            invalidateSelf();
+        }
+    }
+
+    private int getRippleIndex(Ripple ripple) {
+        final Ripple[] ripples = mAnimatingRipples;
+        final int count = mAnimatingRipplesCount;
+        for (int i = 0; i < count; i++) {
+            if (ripples[i] == ripple) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+        final int count = mLayerState.mNum;
+        if (count == 0 || (mState.mMask != null && count == 1)) {
+            return -1;
+        }
+
+        final Paint maskingPaint = getMaskingPaint(mode);
+        final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+                bounds.right, bounds.bottom, maskingPaint);
+
         // Draw everything except the mask.
-        for (int i = 0; i < N; i++) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < count; i++) {
             if (array[i].mId != R.id.mask) {
                 array[i].mDrawable.draw(canvas);
             }
         }
 
-        // Composite the layers if needed.
-        if (restoreToCount >= 0) {
-            canvas.restoreToCount(restoreToCount);
-        }
+        return restoreToCount;
     }
 
-    private int drawRippleLayer(Canvas canvas, boolean maskOnly) {
+    private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
         final int count = mAnimatingRipplesCount;
         if (count == 0) {
             return -1;
         }
 
-        final Ripple[] ripples = mAnimatingRipples;
-        final boolean projected = isProjected();
-        final Rect layerBounds = projected ? getDirtyBounds() : getBounds();
-
         // Separate the ripple color and alpha channel. The alpha will be
         // applied when we merge the ripples down to the canvas.
-        final int rippleColor;
+        final int rippleARGB;
         if (mState.mTint != null) {
-            rippleColor = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
+            rippleARGB = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
         } else {
-            rippleColor = Color.TRANSPARENT;
+            rippleARGB = Color.TRANSPARENT;
         }
-        final int rippleAlpha = Color.alpha(rippleColor);
 
+        final int rippleAlpha = Color.alpha(rippleARGB);
+        final int rippleColor = rippleARGB | (0xFF << 24);
         if (mRipplePaint == null) {
             mRipplePaint = new Paint();
             mRipplePaint.setAntiAlias(true);
@@ -536,36 +570,20 @@
         boolean drewRipples = false;
         int restoreToCount = -1;
         int restoreTranslate = -1;
-        int animatingCount = 0;
 
         // Draw ripples and update the animating ripples array.
+        final Ripple[] ripples = mAnimatingRipples;
         for (int i = 0; i < count; i++) {
             final Ripple ripple = ripples[i];
 
-            // Mark and skip finished ripples.
-            if (ripple.isFinished()) {
-                ripples[i] = null;
-                continue;
-            }
-
             // If we're masking the ripple layer, make sure we have a layer
             // first. This will merge SRC_OVER (directly) onto the canvas.
             if (restoreToCount < 0) {
-                // If we're projecting or we only have a mask, we want to treat the
-                // underlying canvas as our content and merge the ripple layer down
-                // using the tint xfermode.
-                final PorterDuffXfermode xfermode;
-                if (projected || maskOnly) {
-                    xfermode = mState.getTintXfermode();
-                } else {
-                    xfermode = SRC_OVER;
-                }
-
-                final Paint layerPaint = getMaskingPaint(xfermode);
-                layerPaint.setAlpha(rippleAlpha);
-                restoreToCount = canvas.saveLayer(layerBounds.left, layerBounds.top,
-                        layerBounds.right, layerBounds.bottom, layerPaint);
-                layerPaint.setAlpha(255);
+                final Paint maskingPaint = getMaskingPaint(mode);
+                maskingPaint.setAlpha(rippleAlpha);
+                restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+                        bounds.right, bounds.bottom, maskingPaint);
+                maskingPaint.setAlpha(255);
 
                 restoreTranslate = canvas.save();
                 // Translate the canvas to the current hotspot bounds.
@@ -573,13 +591,8 @@
             }
 
             drewRipples |= ripple.draw(canvas, ripplePaint);
-
-            ripples[animatingCount] = ripples[i];
-            animatingCount++;
         }
 
-        mAnimatingRipplesCount = animatingCount;
-
         // Always restore the translation.
         if (restoreTranslate >= 0) {
             canvas.restoreToCount(restoreTranslate);
@@ -594,6 +607,20 @@
         return restoreToCount;
     }
 
+    private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+        final Drawable mask = mState.mMask;
+        if (mask == null) {
+            return -1;
+        }
+
+        final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+                bounds.right, bounds.bottom, getMaskingPaint(mode));
+
+        mask.draw(canvas);
+
+        return restoreToCount;
+    }
+
     private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
         if (mMaskingPaint == null) {
             mMaskingPaint = new Paint();
@@ -634,8 +661,8 @@
         int[] mTouchThemeAttrs;
         ColorStateList mTint = null;
         PorterDuffXfermode mTintXfermode = SRC_ATOP;
-        PorterDuffXfermode mTintXfermodeInverse = DST_ATOP;
         Drawable mMask;
+        int mMaxRadius = RADIUS_AUTO;
         boolean mPinned = false;
 
         public RippleState(RippleState orig, RippleDrawable owner, Resources res) {
@@ -645,14 +672,12 @@
                 mTouchThemeAttrs = orig.mTouchThemeAttrs;
                 mTint = orig.mTint;
                 mTintXfermode = orig.mTintXfermode;
-                mTintXfermodeInverse = orig.mTintXfermodeInverse;
+                mMaxRadius = orig.mMaxRadius;
                 mPinned = orig.mPinned;
             }
         }
 
         public void setTintMode(Mode mode) {
-            final Mode invertedMode = RippleState.invertPorterDuffMode(mode);
-            mTintXfermodeInverse = new PorterDuffXfermode(invertedMode);
             mTintXfermode = new PorterDuffXfermode(mode);
         }
 
@@ -660,10 +685,6 @@
             return mTintXfermode;
         }
 
-        public PorterDuffXfermode getTintXfermodeInverse() {
-            return mTintXfermodeInverse;
-        }
-
         @Override
         public boolean canApplyTheme() {
             return mTouchThemeAttrs != null || super.canApplyTheme();
@@ -683,33 +704,36 @@
         public Drawable newDrawable(Resources res, Theme theme) {
             return new RippleDrawable(this, res, theme);
         }
+    }
 
-        /**
-         * Inverts SRC and DST in PorterDuff blending modes.
-         */
-        private static Mode invertPorterDuffMode(Mode src) {
-            switch (src) {
-                case SRC_ATOP:
-                    return Mode.DST_ATOP;
-                case SRC_IN:
-                    return Mode.DST_IN;
-                case SRC_OUT:
-                    return Mode.DST_OUT;
-                case SRC_OVER:
-                    return Mode.DST_OVER;
-                case DST_ATOP:
-                    return Mode.SRC_ATOP;
-                case DST_IN:
-                    return Mode.SRC_IN;
-                case DST_OUT:
-                    return Mode.SRC_OUT;
-                case DST_OVER:
-                    return Mode.SRC_OVER;
-                default:
-                    // Everything else is agnostic to SRC versus DST.
-                    return src;
-            }
+    /**
+     * Sets the maximum ripple radius in pixels. The default value of
+     * {@link #RADIUS_AUTO} defines the radius as the distance from the center
+     * of the drawable bounds (or hotspot bounds, if specified) to a corner.
+     *
+     * @param maxRadius the maximum ripple radius in pixels or
+     *            {@link #RADIUS_AUTO} to automatically determine the maximum
+     *            radius based on the bounds
+     * @see #getMaxRadius()
+     * @see #setHotspotBounds(int, int, int, int)
+     * @hide
+     */
+    public void setMaxRadius(int maxRadius) {
+        if (maxRadius != RADIUS_AUTO && maxRadius < 0) {
+            throw new IllegalArgumentException("maxRadius must be RADIUS_AUTO or >= 0");
         }
+
+        mState.mMaxRadius = maxRadius;
+    }
+
+    /**
+     * @return the maximum ripple radius in pixels, or {@link #RADIUS_AUTO} if
+     *         the radius is determined automatically
+     * @see #setMaxRadius(int)
+     * @hide
+     */
+    public int getMaxRadius() {
+        return mState.mMaxRadius;
     }
 
     private RippleDrawable(RippleState state, Resources res, Theme theme) {
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index b80f7e9..eff3011 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -63,7 +63,6 @@
 void BaseRenderNodeAnimator::setupStartValueIfNecessary(RenderNode* target, TreeInfo& info) {
     if (mPlayState == NEEDS_START) {
         setStartValue(getValue(target));
-        mPlayState = PENDING;
     }
 }
 
@@ -154,7 +153,8 @@
 }
 
 void RenderPropertyAnimator::onAttached(RenderNode* target) {
-    if (target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+    if (mPlayState == NEEDS_START
+            && target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
         setStartValue((target->stagingProperties().*mPropertyAccess->getter)());
     }
     (target->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 7741617..a0c7c55 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -41,6 +41,7 @@
 class BaseRenderNodeAnimator : public VirtualLightRefBase {
     PREVENT_COPY_AND_ASSIGN(BaseRenderNodeAnimator);
 public:
+    ANDROID_API void setStartValue(float value);
     ANDROID_API void setInterpolator(Interpolator* interpolator);
     ANDROID_API void setDuration(nsecs_t durationInMs);
     ANDROID_API nsecs_t duration() { return mDuration; }
@@ -64,11 +65,9 @@
     BaseRenderNodeAnimator(float finalValue);
     virtual ~BaseRenderNodeAnimator();
 
-    void setStartValue(float value);
     virtual float getValue(RenderNode* target) const = 0;
     virtual void setValue(RenderNode* target, float value) = 0;
 
-private:
     void callOnFinishedListener(TreeInfo& info);
 
     enum PlayState {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f4affa0..8ca303b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -41,6 +41,7 @@
 import java.util.HashMap;
 import java.util.ArrayList;
 
+
 /**
  * AudioManager provides access to volume and ringer mode control.
  * <p>
@@ -61,6 +62,7 @@
     private final boolean mUseVolumeKeySounds;
     private final Binder mToken = new Binder();
     private static String TAG = "AudioManager";
+    AudioPortEventHandler mAudioPortEventHandler;
 
     /**
      * Broadcast intent, a hint for applications that audio is about to become
@@ -337,6 +339,12 @@
     public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6;
 
     /**
+     * Adjusting the volume was prevented due to silent mode, display a hint in the UI.
+     * @hide
+     */
+    public static final int FLAG_SHOW_SILENT_HINT = 1 << 7;
+
+    /**
      * Ringer mode that will be silent and will not vibrate. (This overrides the
      * vibrate setting.)
      *
@@ -438,6 +446,7 @@
                 com.android.internal.R.bool.config_useMasterVolume);
         mUseVolumeKeySounds = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useVolumeKeySounds);
+        mAudioPortEventHandler = new AudioPortEventHandler(this);
     }
 
     private static IAudioService getService()
@@ -635,7 +644,12 @@
             if (mUseMasterVolume) {
                 service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
             } else {
-                service.adjustVolume(direction, flags, mContext.getOpPackageName());
+                if (USE_SESSIONS) {
+                    MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+                    helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
+                } else {
+                    service.adjustVolume(direction, flags, mContext.getOpPackageName());
+                }
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustVolume", e);
@@ -665,8 +679,13 @@
             if (mUseMasterVolume) {
                 service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
             } else {
-                service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
-                        mContext.getOpPackageName());
+                if (USE_SESSIONS) {
+                    MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+                    helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
+                } else {
+                    service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
+                            mContext.getOpPackageName());
+                }
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
@@ -3003,7 +3022,7 @@
      * @hide
      */
     public int listAudioPorts(ArrayList<AudioPort> ports) {
-        return ERROR_INVALID_OPERATION;
+        return updateAudioPortCache(ports, null);
     }
 
     /**
@@ -3012,7 +3031,17 @@
      * @hide
      */
     public int listAudioDevicePorts(ArrayList<AudioPort> devices) {
-        return ERROR_INVALID_OPERATION;
+        ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+        int status = updateAudioPortCache(ports, null);
+        if (status == SUCCESS) {
+            devices.clear();
+            for (int i = 0; i < ports.size(); i++) {
+                if (ports.get(i) instanceof AudioDevicePort) {
+                    devices.add(ports.get(i));
+                }
+            }
+        }
+        return status;
     }
 
     /**
@@ -3041,7 +3070,7 @@
     public int createAudioPatch(AudioPatch[] patch,
                                  AudioPortConfig[] sources,
                                  AudioPortConfig[] sinks) {
-        return ERROR_INVALID_OPERATION;
+        return AudioSystem.createAudioPatch(patch, sources, sinks);
     }
 
     /**
@@ -3056,7 +3085,7 @@
      * @hide
      */
     public int releaseAudioPatch(AudioPatch patch) {
-        return  ERROR_INVALID_OPERATION;
+        return AudioSystem.releaseAudioPatch(patch);
     }
 
     /**
@@ -3065,7 +3094,7 @@
      * @hide
      */
     public int listAudioPatches(ArrayList<AudioPatch> patches) {
-        return ERROR_INVALID_OPERATION;
+        return updateAudioPortCache(null, patches);
     }
 
     /**
@@ -3074,7 +3103,14 @@
      * @hide
      */
     public int setAudioPortGain(AudioPort port, AudioGainConfig gain) {
-        return ERROR_INVALID_OPERATION;
+        if (port == null || gain == null) {
+            return ERROR_BAD_VALUE;
+        }
+        AudioPortConfig activeConfig = port.activeConfig();
+        AudioPortConfig config = new AudioPortConfig(port, activeConfig.samplingRate(),
+                                        activeConfig.channelMask(), activeConfig.format(), gain);
+        config.mConfigMask = AudioPortConfig.GAIN;
+        return AudioSystem.setAudioPortConfig(config);
     }
 
     /**
@@ -3102,16 +3138,128 @@
     }
 
     /**
-     * Register an audio port update listener.
+     * Register an audio port list update listener.
      * @hide
      */
     public void registerAudioPortUpdateListener(OnAudioPortUpdateListener l) {
+        mAudioPortEventHandler.registerListener(l);
     }
 
     /**
-     * Unregister an audio port update listener.
+     * Unregister an audio port list update listener.
      * @hide
      */
     public void unregisterAudioPortUpdateListener(OnAudioPortUpdateListener l) {
+        mAudioPortEventHandler.unregisterListener(l);
+    }
+
+    //
+    // AudioPort implementation
+    //
+
+    static final int AUDIOPORT_GENERATION_INIT = 0;
+    Integer mAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT);
+    ArrayList<AudioPort> mAudioPortsCached = new ArrayList<AudioPort>();
+    ArrayList<AudioPatch> mAudioPatchesCached = new ArrayList<AudioPatch>();
+
+    int resetAudioPortGeneration() {
+        int generation;
+        synchronized (mAudioPortGeneration) {
+            generation = mAudioPortGeneration;
+            mAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
+        }
+        return generation;
+    }
+
+    int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches) {
+        synchronized (mAudioPortGeneration) {
+
+            if (mAudioPortGeneration == AUDIOPORT_GENERATION_INIT) {
+                int[] patchGeneration = new int[1];
+                int[] portGeneration = new int[1];
+                int status;
+                ArrayList<AudioPort> newPorts = new ArrayList<AudioPort>();
+                ArrayList<AudioPatch> newPatches = new ArrayList<AudioPatch>();
+
+                do {
+                    newPorts.clear();
+                    status = AudioSystem.listAudioPorts(newPorts, portGeneration);
+                    Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPorts() status: "+
+                                    status+" num ports: "+ newPorts.size() +" portGeneration: "+portGeneration[0]);
+                    if (status != SUCCESS) {
+                        return status;
+                    }
+                    newPatches.clear();
+                    status = AudioSystem.listAudioPatches(newPatches, patchGeneration);
+                    Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPatches() status: "+
+                            status+" num patches: "+ newPatches.size() +" patchGeneration: "+patchGeneration[0]);
+                    if (status != SUCCESS) {
+                        return status;
+                    }
+                } while (patchGeneration[0] != portGeneration[0]);
+
+                for (int i = 0; i < newPatches.size(); i++) {
+                    for (int j = 0; j < newPatches.get(i).sources().length; j++) {
+                        AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j], newPorts);
+                        if (portCfg == null) {
+                            return ERROR;
+                        }
+                        newPatches.get(i).sources()[j] = portCfg;
+                    }
+                    for (int j = 0; j < newPatches.get(i).sinks().length; j++) {
+                        AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j], newPorts);
+                        if (portCfg == null) {
+                            return ERROR;
+                        }
+                        newPatches.get(i).sinks()[j] = portCfg;
+                    }
+                }
+
+                mAudioPortsCached = newPorts;
+                mAudioPatchesCached = newPatches;
+                mAudioPortGeneration = portGeneration[0];
+            }
+            if (ports != null) {
+                ports.clear();
+                ports.addAll(mAudioPortsCached);
+            }
+            if (patches != null) {
+                patches.clear();
+                patches.addAll(mAudioPatchesCached);
+            }
+        }
+        return SUCCESS;
+    }
+
+    AudioPortConfig updatePortConfig(AudioPortConfig portCfg, ArrayList<AudioPort> ports) {
+        AudioPort port = portCfg.port();
+        int k;
+        for (k = 0; k < ports.size(); k++) {
+            // compare handles because the port returned by JNI is not of the correct
+            // subclass
+            if (ports.get(k).handle().equals(port.handle())) {
+                Log.i(TAG, "updatePortConfig match found for port handle: "+
+                            port.handle().id()+" port: "+ k);
+                port = ports.get(k);
+                break;
+            }
+        }
+        if (k == ports.size()) {
+            // this hould never happen
+            Log.e(TAG, "updatePortConfig port not found for handle: "+port.handle().id());
+            return null;
+        }
+        AudioGainConfig gainCfg = portCfg.gain();
+        if (gainCfg != null) {
+            AudioGain gain = port.gain(gainCfg.index());
+            gainCfg = gain.buildConfig(gainCfg.mode(),
+                                       gainCfg.channelMask(),
+                                       gainCfg.values(),
+                                       gainCfg.rampDurationMs());
+        }
+        return port.buildConfig(portCfg.samplingRate(),
+                                                 portCfg.channelMask(),
+                                                 portCfg.format(),
+                                                 gainCfg);
     }
 }
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 9aeddef..fbd5022 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -133,6 +133,9 @@
      * Get the gain descriptor at a given index
      */
     AudioGain gain(int index) {
+        if (index < mGains.length) {
+            return null;
+        }
         return mGains[index];
     }
 
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
new file mode 100644
index 0000000..782ecd8
--- /dev/null
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -0,0 +1,172 @@
+/*
+ * 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 android.media;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.lang.ref.WeakReference;
+
+/**
+ * The AudioPortEventHandler handles AudioManager.OnAudioPortUpdateListener callbacks
+ * posted from JNI
+ * @hide
+ */
+
+class AudioPortEventHandler {
+    private final Handler mHandler;
+    private ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners;
+    private AudioManager mAudioManager;
+
+    private static String TAG = "AudioPortEventHandler";
+
+    private static final int AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1;
+    private static final int AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2;
+    private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
+    private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
+
+    AudioPortEventHandler(AudioManager audioManager) {
+        mAudioManager = audioManager;
+        mListeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>();
+
+        // find the looper for our new event handler
+        Looper looper = Looper.myLooper();
+        if (looper == null) {
+            looper = Looper.getMainLooper();
+        }
+
+        if (looper != null) {
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    Log.i(TAG, "handleMessage: "+msg.what);
+                    ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
+                    synchronized (this) {
+                        if (msg.what == AUDIOPORT_EVENT_NEW_LISTENER) {
+                            listeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>();
+                            if (mListeners.contains(msg.obj)) {
+                                listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj);
+                            }
+                        } else {
+                            listeners = mListeners;
+                        }
+                    }
+                    if (listeners.isEmpty()) {
+                        return;
+                    }
+                    // reset audio port cache if the event corresponds to a change coming
+                    // from audio policy service or if mediaserver process died.
+                    if (msg.what == AUDIOPORT_EVENT_PORT_LIST_UPDATED ||
+                            msg.what == AUDIOPORT_EVENT_PATCH_LIST_UPDATED ||
+                            msg.what == AUDIOPORT_EVENT_SERVICE_DIED) {
+                        mAudioManager.resetAudioPortGeneration();
+                    }
+                    ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+                    ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
+                    if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
+                        int status = mAudioManager.updateAudioPortCache(ports, patches);
+                        if (status != AudioManager.SUCCESS) {
+                            return;
+                        }
+                    }
+
+                    switch (msg.what) {
+                    case AUDIOPORT_EVENT_NEW_LISTENER:
+                    case AUDIOPORT_EVENT_PORT_LIST_UPDATED:
+                        AudioPort[] portList = ports.toArray(new AudioPort[0]);
+                        for (int i = 0; i < listeners.size(); i++) {
+                            listeners.get(i).OnAudioPortListUpdate(portList);
+                        }
+                        if (msg.what == AUDIOPORT_EVENT_PORT_LIST_UPDATED) {
+                            break;
+                        }
+                        // FALL THROUGH
+
+                    case AUDIOPORT_EVENT_PATCH_LIST_UPDATED:
+                        AudioPatch[] patchList = patches.toArray(new AudioPatch[0]);
+                        for (int i = 0; i < listeners.size(); i++) {
+                            listeners.get(i).OnAudioPatchListUpdate(patchList);
+                        }
+                        break;
+
+                    case AUDIOPORT_EVENT_SERVICE_DIED:
+                        for (int i = 0; i < listeners.size(); i++) {
+                            listeners.get(i).OnServiceDied();
+                        }
+                        break;
+
+                    default:
+                        break;
+                    }
+                }
+            };
+        } else {
+            mHandler = null;
+        }
+
+        native_setup(new WeakReference<AudioPortEventHandler>(this));
+    }
+    private native void native_setup(Object module_this);
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+    private native void native_finalize();
+
+    void registerListener(AudioManager.OnAudioPortUpdateListener l) {
+        synchronized (this) {
+            mListeners.add(l);
+        }
+        if (mHandler != null) {
+            Message m = mHandler.obtainMessage(AUDIOPORT_EVENT_NEW_LISTENER, 0, 0, l);
+            mHandler.sendMessage(m);
+        }
+    }
+
+    void unregisterListener(AudioManager.OnAudioPortUpdateListener l) {
+        synchronized (this) {
+            mListeners.remove(l);
+        }
+    }
+
+    Handler handler() {
+        return mHandler;
+    }
+
+    @SuppressWarnings("unused")
+    private static void postEventFromNative(Object module_ref,
+                                            int what, int arg1, int arg2, Object obj) {
+        AudioPortEventHandler eventHandler =
+                (AudioPortEventHandler)((WeakReference)module_ref).get();
+        if (eventHandler == null) {
+            return;
+        }
+
+        if (eventHandler != null) {
+            Handler handler = eventHandler.handler();
+            if (handler != null) {
+                Message m = handler.obtainMessage(what, arg1, arg2, obj);
+                handler.sendMessage(m);
+            }
+        }
+    }
+
+}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 5b620fd..fd346d5 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -115,6 +115,9 @@
     /** Allow volume changes to set ringer mode to silent? */
     private static final boolean VOLUME_SETS_RINGER_MODE_SILENT = false;
 
+    /** In silent mode, are volume adjustments (raises) prevented? */
+    private static final boolean PREVENT_VOLUME_ADJUSTMENT_IF_SILENT = true;
+
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -126,6 +129,11 @@
      */
     public static final int PLAY_SOUND_DELAY = 300;
 
+    /**
+     * Only used in the result from {@link #checkForRingerModeChange(int, int, int)}
+     */
+    private static final int FLAG_ADJUST_VOLUME = 1;
+
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private final AppOpsManager mAppOps;
@@ -941,7 +949,12 @@
             }
             // Check if the ringer mode changes with this volume adjustment. If
             // it does, it will handle adjusting the volume, so we won't below
-            adjustVolume = checkForRingerModeChange(aliasIndex, direction, step);
+            final int result = checkForRingerModeChange(aliasIndex, direction, step);
+            adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
+            // If suppressing a volume adjustment in silent mode, display the UI hint
+            if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
+                flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
+            }
         }
 
         int oldIndex = mStreamStates[streamType].getIndex(device);
@@ -2538,8 +2551,8 @@
      * adjusting volume. If so, this will set the proper ringer mode and volume
      * indices on the stream states.
      */
-    private boolean checkForRingerModeChange(int oldIndex, int direction,  int step) {
-        boolean adjustVolumeIndex = true;
+    private int checkForRingerModeChange(int oldIndex, int direction,  int step) {
+        int result = FLAG_ADJUST_VOLUME;
         int ringerMode = getRingerMode();
 
         switch (ringerMode) {
@@ -2578,17 +2591,21 @@
             } else if (direction == AudioManager.ADJUST_RAISE) {
                 ringerMode = RINGER_MODE_NORMAL;
             }
-            adjustVolumeIndex = false;
+            result &= ~FLAG_ADJUST_VOLUME;
             break;
         case RINGER_MODE_SILENT:
             if (direction == AudioManager.ADJUST_RAISE) {
-                if (mHasVibrator) {
-                    ringerMode = RINGER_MODE_VIBRATE;
+                if (PREVENT_VOLUME_ADJUSTMENT_IF_SILENT) {
+                    result |= AudioManager.FLAG_SHOW_SILENT_HINT;
                 } else {
-                    ringerMode = RINGER_MODE_NORMAL;
+                  if (mHasVibrator) {
+                      ringerMode = RINGER_MODE_VIBRATE;
+                  } else {
+                      ringerMode = RINGER_MODE_NORMAL;
+                  }
                 }
             }
-            adjustVolumeIndex = false;
+            result &= ~FLAG_ADJUST_VOLUME;
             break;
         default:
             Log.e(TAG, "checkForRingerModeChange() wrong ringer mode: "+ringerMode);
@@ -2599,7 +2616,7 @@
 
         mPrevVolDirection = direction;
 
-        return adjustVolumeIndex;
+        return result;
     }
 
     @Override
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0c45443..af7a3e1 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import java.util.ArrayList;
 
 /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
  * TO UPDATE THE CORRESPONDING NATIVE GLUE AND AudioManager.java.
@@ -458,4 +459,12 @@
 
     public static native int setLowRamDevice(boolean isLowRamDevice);
     public static native int checkAudioFlinger();
+
+    public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
+    public static native int createAudioPatch(AudioPatch[] patch,
+                                            AudioPortConfig[] sources, AudioPortConfig[] sinks);
+    public static native int releaseAudioPatch(AudioPatch patch);
+    public static native int listAudioPatches(ArrayList<AudioPatch> patches, int[] generation);
+    public static native int setAudioPortConfig(AudioPortConfig config);
 }
+
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index c4233c3..1cfc5bc 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -47,4 +47,8 @@
     void setMetadata(in MediaMetadata metadata);
     void setPlaybackState(in PlaybackState state);
     void setRatingType(int type);
+
+    // These commands relate to volume handling
+    void configureVolumeHandling(int type, int arg1, int arg2);
+    void setCurrentVolume(int currentVolume);
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 103c3f1..0316d1fa 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -45,4 +45,8 @@
     void onRewind();
     void onSeekTo(long pos);
     void onRate(in Rating rating);
+
+    // These callbacks are for volume handling
+    void onAdjustVolumeBy(int delta);
+    void onSetVolumeTo(int value);
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 38b92932..6d9888f 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -29,4 +29,5 @@
     ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
     List<IBinder> getSessions(in ComponentName compName, int userId);
     void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
+    void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 90ccf68..7972639 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -124,9 +124,21 @@
      */
     public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
 
-    private static final String KEY_COMMAND = "command";
-    private static final String KEY_EXTRAS = "extras";
-    private static final String KEY_CALLBACK = "callback";
+    /**
+     * The session uses local playback. Used for configuring volume handling
+     * with the system.
+     *
+     * @hide
+     */
+    public static final int VOLUME_TYPE_LOCAL = 1;
+
+    /**
+     * The session uses remote playback. Used for configuring volume handling
+     * with the system.
+     *
+     * @hide
+     */
+    public static final int VOLUME_TYPE_REMOTE = 2;
 
     private final Object mLock = new Object();
 
@@ -143,6 +155,7 @@
             = new ArrayMap<String, RouteInterface.EventListener>();
 
     private Route mRoute;
+    private RemoteVolumeProvider mVolumeProvider;
 
     private boolean mActive = false;;
 
@@ -242,7 +255,11 @@
      * @param stream The {@link AudioManager} stream this session is playing on.
      */
     public void setPlaybackToLocal(int stream) {
-        // TODO
+        try {
+            mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
+        }
     }
 
     /**
@@ -259,7 +276,14 @@
         if (volumeProvider == null) {
             throw new IllegalArgumentException("volumeProvider may not be null!");
         }
-        // TODO
+        mVolumeProvider = volumeProvider;
+
+        try {
+            mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(),
+                    volumeProvider.getMaxVolume());
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
+        }
     }
 
     /**
@@ -942,6 +966,26 @@
 
         }
 
+        /*
+         * (non-Javadoc)
+         * @see android.media.session.ISessionCallback#onAdjustVolumeBy(int)
+         */
+        @Override
+        public void onAdjustVolumeBy(int delta) throws RemoteException {
+            // TODO(epastern): Auto-generated method stub
+
+        }
+
+        /*
+         * (non-Javadoc)
+         * @see android.media.session.ISessionCallback#onSetVolumeTo(int)
+         */
+        @Override
+        public void onSetVolumeTo(int value) throws RemoteException {
+            // TODO(epastern): Auto-generated method stub
+
+        }
+
     }
 
     private class CallbackMessageHandler extends Handler {
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index c303e77..099f601 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -76,6 +76,13 @@
         }
     }
 
+    public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+        mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+        if (DEBUG) {
+            Log.d(TAG, "dispatched volume adjustment");
+        }
+    }
+
     public void addRccListener(PendingIntent pi,
             MediaSession.TransportControlsCallback listener) {
         if (pi == null) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 8d5e338..9e8b0d3 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -166,4 +166,22 @@
             Log.e(TAG, "Failed to send key event.", e);
         }
     }
+
+    /**
+     * Dispatch an adjust volume request to the system. It will be routed to the
+     * most relevant stream/session.
+     *
+     * @param suggestedStream The stream to fall back to if there isn't a
+     *            relevant stream
+     * @param delta The amount to adjust the volume by.
+     * @param flags Any flags to include with the volume change.
+     * @hide
+     */
+    public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+        try {
+            mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to send adjust volume.", e);
+        }
+    }
 }
diff --git a/media/lib/signer/Android.mk b/media/lib/signer/Android.mk
index bca643a..b0d3177 100644
--- a/media/lib/signer/Android.mk
+++ b/media/lib/signer/Android.mk
@@ -23,7 +23,8 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := \
-            $(call all-java-files-under, java)
+            $(call all-java-files-under, java) \
+            $(call all-aidl-files-under, java)
 
 include $(BUILD_JAVA_LIBRARY)
 
diff --git a/media/lib/signer/com.android.mediadrm.signer.xml b/media/lib/signer/com.android.mediadrm.signer.xml
index b5b1f09..fd3a115 100644
--- a/media/lib/signer/com.android.mediadrm.signer.xml
+++ b/media/lib/signer/com.android.mediadrm.signer.xml
@@ -15,6 +15,6 @@
 -->
 
 <permissions>
-    <library name="com.android.media.drm.signer"
-            file="/system/framework/com.android.media.drm.signer.jar" />
+    <library name="com.android.mediadrm.signer"
+            file="/system/framework/com.android.mediadrm.signer.jar" />
 </permissions>
diff --git a/packages/SystemUI/res/drawable/heads_up_scrim.xml b/packages/SystemUI/res/drawable/heads_up_scrim.xml
new file mode 100644
index 0000000..59000fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/heads_up_scrim.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+            android:type="linear"
+            android:angle="-90"
+            android:startColor="#55000000"
+            android:endColor="#00000000" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml b/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml
index 9f0ec67..c68238f 100644
--- a/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml
@@ -19,17 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
-        android:fill="#00000000"
-        android:stroke="#CCCCCC"
-        android:strokeWidth="1.0"
-        android:pathData="M10.2,9.0"/>
-    <path
-        android:fill="#00000000"
-        android:stroke="#CCCCCC"
-        android:strokeWidth="1.0"
-        android:pathData="M21.0,16.0l0.0,-2.0l-8.0,-5.0L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5L10.0,9.0l-8.0,5.0l0.0,2.0l8.0,-2.5L10.0,19.0l-2.0,1.5L8.0,22.0l3.5,-1.0l3.5,1.0l0.0,-1.5L13.0,19.0l0.0,-5.5L21.0,16.0z"/>
+        android:fill="#4DFFFFFF"
+        android:pathData="M26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,7.4L35.7,30.0l6.3,2.0l0.0,-4.0L26.0,18.0zM6.0,10.5l10.0,10.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0l0.0,-7.5L37.5,42.0l2.5,-2.5L8.5,8.0L6.0,10.5z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml b/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml
index 95c20bb..c1e3c7e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml
@@ -19,13 +19,13 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M10.2,9.0"/>
+        android:pathData="M20.4,18.0"/>
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M21.0,16.0l0.0,-2.0l-8.0,-5.0L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5L10.0,9.0l-8.0,5.0l0.0,2.0l8.0,-2.5L10.0,19.0l-2.0,1.5L8.0,22.0l3.5,-1.0l3.5,1.0l0.0,-1.5L13.0,19.0l0.0,-5.5L21.0,16.0z"/>
+        android:pathData="M42.0,32.0l0.0,-4.0L26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,11.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0L26.0,27.0L42.0,32.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
index 61a7777..3957d02 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
@@ -19,10 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/>
+        android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
new file mode 100644
index 0000000..e4038f9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M28.5,24.0l4.6,4.6c0.6,-1.4 0.9,-3.0 0.9,-4.7c0.0,-1.6 -0.3,-3.2 -0.9,-4.6L28.5,24.0zM39.1,13.4L36.5,16.0c1.3,2.4 2.0,5.1 2.0,8.0s-0.7,5.6 -2.0,8.0l2.4,2.4c1.9,-3.1 3.1,-6.7 3.1,-10.6C42.0,20.0 40.9,16.5 39.1,13.4zM31.4,15.4L20.0,4.0l-2.0,0.0l0.0,15.2L8.8,10.0L6.0,12.8L17.2,24.0L6.0,35.2L8.8,38.0l9.2,-9.2L18.0,44.0l2.0,0.0l11.4,-11.4L22.8,24.0L31.4,15.4zM22.0,11.7l3.8,3.8L22.0,19.2L22.0,11.7zM25.8,32.6L22.0,36.3l0.0,-7.5L25.8,32.6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
index 7ac1cb9..00c5af8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
@@ -19,12 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
-        android:fill="#00000000"
-        android:stroke="#CCCCCC"
-        android:strokeWidth="1.0"
-        android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/>
+        android:fill="#4DFFFFFF"
+        android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
index 61a7777..2b14f33 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
@@ -19,10 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/>
+        android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
index 130c639..2a9541e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
@@ -19,12 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
-        android:fill="#00000000"
-        android:stroke="#CCCCCC"
-        android:strokeWidth="1.0"
-        android:pathData="M21.0,3.0L3.0,3.0C1.9,3.0 1.0,3.9 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.0l18.0,0.0l0.0,14.0l-7.0,0.0l0.0,2.0l7.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L23.0,5.0C23.0,3.9 22.1,3.0 21.0,3.0zM1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0zM1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0zM1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z"/>
+        android:fill="#4DFFFFFF"
+        android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
index 6c82b1c..8dacdc9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
@@ -19,10 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M21.0,3.0L3.0,3.0C1.9,3.0 1.0,3.9 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.0l18.0,0.0l0.0,14.0l-7.0,0.0l0.0,2.0l7.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L23.0,5.0C23.0,3.9 22.1,3.0 21.0,3.0zM1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0zM1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0zM1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z"/>
+        android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml b/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
deleted file mode 100644
index 88a2c3a..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#4DFFFFFF"
-        android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
index 0e933cf..4887b32 100644
--- a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
@@ -19,10 +19,10 @@
         android:height="64dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+        android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
index 51be1a8..477c36b 100644
--- a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
+++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
@@ -19,10 +19,10 @@
         android:height="32dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#4DFFFFFF"
-        android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+        android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
index c8c217d..0a43a7b 100644
--- a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
+++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
@@ -19,10 +19,10 @@
         android:height="32dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+        android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
index 8b104d17..5992470 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
@@ -15,14 +15,14 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android" >
     <size
-        android:width="19dp"
-        android:height="19dp"/>
+        android:width="18dp"
+        android:height="18dp"/>
 
     <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
 
     <path
         android:fill="#FFFFFFFF"
-        android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+        android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/layout/heads_up.xml b/packages/SystemUI/res/layout/heads_up.xml
index 236fdc3..0e2b6d6 100644
--- a/packages/SystemUI/res/layout/heads_up.xml
+++ b/packages/SystemUI/res/layout/heads_up.xml
@@ -17,13 +17,14 @@
 <com.android.systemui.statusbar.policy.HeadsUpNotificationView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_height="match_parent"
-        android:layout_width="match_parent">
+        android:layout_width="match_parent"
+        android:background="@drawable/heads_up_scrim">
 
         <FrameLayout
                 android:layout_height="wrap_content"
-                android:layout_marginStart="@dimen/notification_side_padding"
-                android:layout_marginEnd="@dimen/notification_side_padding"
-                android:elevation="16dp"
+                android:paddingStart="@dimen/notification_side_padding"
+                android:paddingEnd="@dimen/notification_side_padding"
+                android:elevation="8dp"
                 android:id="@+id/content_holder"
                 style="@style/NotificationsQuickSettings" />
 
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
new file mode 100644
index 0000000..5afa967
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/qs_panel_background"
+    android:translationZ="@dimen/volume_panel_z"
+    android:layout_margin="@dimen/volume_panel_z">
+
+        <include layout="@layout/volume_panel" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml
index 98cb8f4..4a2a0c0 100644
--- a/packages/SystemUI/res/layout/volume_panel_item.xml
+++ b/packages/SystemUI/res/layout/volume_panel_item.xml
@@ -15,28 +15,34 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="80dip"
-    android:orientation="horizontal"
-    android:layout_marginTop="8dip"
-    android:layout_marginBottom="8dip"
-    android:gravity="start|center_vertical">
+        android:layout_width="match_parent"
+        android:layout_height="80dip"
+        android:orientation="horizontal"
+        android:layout_marginTop="8dip"
+        android:layout_marginBottom="8dip"
+        android:gravity="start|center_vertical">
 
     <ImageView
-        android:id="@+id/stream_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="16dip"
-        android:background="?android:attr/selectableItemBackground"
-        android:contentDescription="@null" />
-
-    <SeekBar
-        style="?android:attr/seekBarStyle"
-        android:id="@+id/seekbar"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:padding="16dip"
-        android:layout_marginEnd="16dip" />
-
+            android:id="@+id/stream_icon"
+            style="@style/BorderlessButton.Tiny"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="16dip"
+            android:contentDescription="@null" />
+    <FrameLayout
+            android:id="@+id/seekbar_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+        <SeekBar
+                style="?android:attr/seekBarStyle"
+                android:id="@+id/seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingTop="16dip"
+                android:paddingBottom="16dip"
+                android:paddingStart="11dip"
+                android:paddingEnd="11dip"
+                android:layout_marginEnd="16dip" />
+    </FrameLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3646ade..bfbdcf3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -305,6 +305,7 @@
          keyguard_clock_height_fraction_* for the difference between min and max.-->
     <dimen name="keyguard_clock_notifications_margin_min">22dp</dimen>
     <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
+    <dimen name="heads_up_window_height">250dp</dimen>
 
     <!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
     <dimen name="keyguard_min_swipe_amount">75dp</dimen>
@@ -317,4 +318,7 @@
 
     <!-- Volume panel z depth -->
     <dimen name="volume_panel_z">3dp</dimen>
+
+    <!-- Move distance for the hint animations on the lockscreen (unlock, phone, camera)-->
+    <dimen name="hint_move_distance">75dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 59f1efd..373f11f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -290,6 +290,8 @@
     <string name="accessibility_desc_off">Off.</string>
     <!-- Content description of an item that is connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_desc_connected">Connected.</string>
+    <!-- Content description of an item that is connecting for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_desc_connecting">Connecting.</string>
 
     <!-- Content description of the data connection type GPRS for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_data_connection_gprs">GPRS</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 1b12cb0..d153d09 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -51,12 +51,12 @@
     private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
     private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
 
-    public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
+    public static float SWIPE_PROGRESS_FADE_START = 0f; // fraction of thumbnail width
                                                  // where fade starts
-    static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width
-                                              // beyond which alpha->0
-    private float mMinAlpha = 0f;
-    private float mMaxAlpha = 1f;
+    static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+                                              // beyond which swipe progress->0
+    private float mMinSwipeProgress = 0f;
+    private float mMaxSwipeProgress = 1f;
 
     private float mPagingTouchSlop;
     private Callback mCallback;
@@ -137,36 +137,39 @@
                 v.getMeasuredHeight();
     }
 
-    public void setMinAlpha(float minAlpha) {
-        mMinAlpha = minAlpha;
+    public void setMinSwipeProgress(float minSwipeProgress) {
+        mMinSwipeProgress = minSwipeProgress;
     }
 
-    public void setMaxAlpha(float maxAlpha) {
-        mMaxAlpha = maxAlpha;
+    public void setMaxSwipeProgress(float maxSwipeProgress) {
+        mMaxSwipeProgress = maxSwipeProgress;
     }
 
-    private float getAlphaForOffset(View view) {
+    private float getSwipeProgressForOffset(View view) {
         float viewSize = getSize(view);
-        final float fadeSize = ALPHA_FADE_END * viewSize;
+        final float fadeSize = SWIPE_PROGRESS_FADE_END * viewSize;
         float result = 1.0f;
         float pos = getTranslation(view);
-        if (pos >= viewSize * ALPHA_FADE_START) {
-            result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
-        } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
-            result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
+        if (pos >= viewSize * SWIPE_PROGRESS_FADE_START) {
+            result = 1.0f - (pos - viewSize * SWIPE_PROGRESS_FADE_START) / fadeSize;
+        } else if (pos < viewSize * (1.0f - SWIPE_PROGRESS_FADE_START)) {
+            result = 1.0f + (viewSize * SWIPE_PROGRESS_FADE_START + pos) / fadeSize;
         }
-        return Math.min(Math.max(mMinAlpha, result), mMaxAlpha);
+        return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
     }
 
-    private void updateAlphaFromOffset(View animView, boolean dismissable) {
-        if (FADE_OUT_DURING_SWIPE && dismissable) {
-            float alpha = getAlphaForOffset(animView);
-            if (alpha != 0f && alpha != 1f) {
-                animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            } else {
-                animView.setLayerType(View.LAYER_TYPE_NONE, null);
+    private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
+        float swipeProgress = getSwipeProgressForOffset(animView);
+        if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
+            if (FADE_OUT_DURING_SWIPE && dismissable) {
+                float alpha = swipeProgress;
+                if (alpha != 0f && alpha != 1f) {
+                    animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                } else {
+                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                }
+                animView.setAlpha(getSwipeProgressForOffset(animView));
             }
-            animView.setAlpha(getAlphaForOffset(animView));
         }
         invalidateGlobalRegion(animView);
     }
@@ -307,7 +310,7 @@
         });
         anim.addUpdateListener(new AnimatorUpdateListener() {
             public void onAnimationUpdate(ValueAnimator animation) {
-                updateAlphaFromOffset(animView, canAnimViewBeDismissed);
+                updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
             }
         });
         anim.start();
@@ -321,12 +324,12 @@
         anim.setDuration(duration);
         anim.addUpdateListener(new AnimatorUpdateListener() {
             public void onAnimationUpdate(ValueAnimator animation) {
-                updateAlphaFromOffset(animView, canAnimViewBeDismissed);
+                updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
             }
         });
         anim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animator) {
-                updateAlphaFromOffset(animView, canAnimViewBeDismissed);
+                updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
                 mCallback.onChildSnappedBack(animView);
             }
         });
@@ -365,7 +368,7 @@
                     }
                     setTranslation(mCurrAnimView, delta);
 
-                    updateAlphaFromOffset(mCurrAnimView, mCanCurrViewBeDimissed);
+                    updateSwipeProgressFromOffset(mCurrAnimView, mCanCurrViewBeDimissed);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -415,5 +418,12 @@
         void onDragCancelled(View v);
 
         void onChildSnappedBack(View animView);
+
+        /**
+         * Updates the swipe progress on a child.
+         *
+         * @return if true, prevents the default alpha fading.
+         */
+        boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 4c7f3df..b280ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -204,9 +204,9 @@
         }
 
         @Override
-        public void startKeyguardExitAnimation(long fadeoutDuration) {
+        public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
             checkPermission();
-            mKeyguardViewMediator.startKeyguardExitAnimation(fadeoutDuration);
+            mKeyguardViewMediator.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7110d8d..f7b4994 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1083,7 +1083,8 @@
                     handleDismiss();
                     break;
                 case START_KEYGUARD_EXIT_ANIM:
-                    handleStartKeyguardExitAnimation((Long) msg.obj);
+                    StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
+                    handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
                     break;
             }
         }
@@ -1227,7 +1228,7 @@
         }
     }
 
-    private void handleStartKeyguardExitAnimation(long fadeoutDuration) {
+    private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         synchronized (KeyguardViewMediator.this) {
 
             // only play "unlock" noises if not on a call (since the incall UI
@@ -1236,7 +1237,7 @@
                 playSounds(false);
             }
 
-            mStatusBarKeyguardViewManager.hide();
+            mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);
             mShowing = false;
             mKeyguardDonePending = false;
             updateActivityLockScreenState();
@@ -1346,12 +1347,24 @@
         return mStatusBarKeyguardViewManager;
     }
 
-    public void startKeyguardExitAnimation(long fadeoutDuration) {
-        Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM, fadeoutDuration);
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+        Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM,
+                new StartKeyguardExitAnimParams(startTime, fadeoutDuration));
         mHandler.sendMessage(msg);
     }
 
     public ViewMediatorCallback getViewMediatorCallback() {
         return mViewMediatorCallback;
     }
+
+    private static class StartKeyguardExitAnimParams {
+
+        long startTime;
+        long fadeoutDuration;
+
+        private StartKeyguardExitAnimParams(long startTime, long fadeoutDuration) {
+            this.startTime = startTime;
+            this.fadeoutDuration = fadeoutDuration;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index c8cf05d..2bf369a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -152,6 +152,7 @@
     }
 
     private void handleShowDetail(TileRecord r, boolean show) {
+        if (r == null) return;
         AnimatorListener listener = null;
         if (show) {
             if (mDetailRecord != null) return;
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 7335ab4..d220e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
 import android.content.Intent;
 import android.provider.Settings;
+import android.text.TextUtils;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
@@ -70,18 +71,27 @@
         final boolean supported = mController.isBluetoothSupported();
         final boolean enabled = mController.isBluetoothEnabled();
         final boolean connected = mController.isBluetoothConnected();
+        final boolean connecting = mController.isBluetoothConnecting();
         state.visible = supported;
         state.value = enabled;
         final String stateContentDescription;
         if (enabled) {
+            state.label = null;
             if (connected) {
                 state.iconId = R.drawable.ic_qs_bluetooth_connected;
                 stateContentDescription = mContext.getString(R.string.accessibility_desc_connected);
+                state.label = mController.getLastDeviceName();
+            } else if (connecting) {
+                state.iconId = R.drawable.ic_qs_bluetooth_connecting;
+                stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting);
+                state.label = mController.getLastDeviceName();
             } else {
                 state.iconId = R.drawable.ic_qs_bluetooth_on;
                 stateContentDescription = mContext.getString(R.string.accessibility_desc_on);
             }
-            state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
+            if (TextUtils.isEmpty(state.label)) {
+                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
+            }
         } else {
             state.iconId = R.drawable.ic_qs_bluetooth_off;
             state.label = mContext.getString(R.string.quick_settings_bluetooth_off_label);
@@ -91,9 +101,9 @@
                 R.string.accessibility_quick_settings_bluetooth, stateContentDescription);
     }
 
-    private final BluetoothStateChangeCallback mCallback = new BluetoothStateChangeCallback() {
+    private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
         @Override
-        public void onBluetoothStateChange(boolean on) {
+        public void onBluetoothStateChange(boolean enabled, boolean connecting) {
             refreshState();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 0759b8e..72a3341 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -64,7 +64,7 @@
     }
 
     public void setMinSwipeAlpha(float minAlpha) {
-        mSwipeHelper.setMinAlpha(minAlpha);
+        mSwipeHelper.setMinSwipeProgress(minAlpha);
     }
 
     private int scrollPositionOfMostRecent() {
@@ -221,6 +221,11 @@
     public void onChildSnappedBack(View animView) {
     }
 
+    @Override
+    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+        return false;
+    }
+
     public View getChildAtPosition(MotionEvent ev) {
         final float x = ev.getX() + getScrollX();
         final float y = ev.getY() + getScrollY();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index c2dde6a..1213375 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -65,7 +65,7 @@
     }
 
     public void setMinSwipeAlpha(float minAlpha) {
-        mSwipeHelper.setMinAlpha(minAlpha);
+        mSwipeHelper.setMinSwipeProgress(minAlpha);
     }
 
     private int scrollPositionOfMostRecent() {
@@ -229,6 +229,11 @@
     public void onChildSnappedBack(View animView) {
     }
 
+    @Override
+    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+        return false;
+    }
+
     public View getChildAtPosition(MotionEvent ev) {
         final float x = ev.getX() + getScrollX();
         final float y = ev.getY() + getScrollY();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java
new file mode 100644
index 0000000..367d326
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java
@@ -0,0 +1,43 @@
+/*
+ * 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.phone;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An implementation of a bouncer interpolator optimized for unlock hinting.
+ */
+public class BounceInterpolator implements Interpolator {
+
+    private final static float SCALE_FACTOR = 7.5625f;
+
+    @Override
+    public float getInterpolation(float t) {
+        if (t < 4f / 11f) {
+            return SCALE_FACTOR * t * t;
+        } else if (t < 8f / 11f) {
+            float t2 = t - 6f / 11f;
+            return SCALE_FACTOR * t2 * t2 + 3f / 4f;
+        } else if (t < 10f / 11f) {
+            float t2 = t - 9f / 11f;
+            return SCALE_FACTOR * t2 * t2 + 15f / 16f;
+        } else {
+            float t2 = t - 21f / 22f;
+            return SCALE_FACTOR * t2 * t2 + 63f / 64f;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 994b329..97aa993 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.MediaStore;
@@ -32,6 +33,10 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
 
 /**
@@ -43,6 +48,11 @@
 
     final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
 
+    private static final Intent SECURE_CAMERA_INTENT =
+            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+                    .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+    private static final Intent INSECURE_CAMERA_INTENT =
+            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
     private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
 
     private ImageView mCameraImageView;
@@ -51,6 +61,7 @@
 
     private ActivityStarter mActivityStarter;
     private UnlockMethodCache mUnlockMethodCache;
+    private LockPatternUtils mLockPatternUtils;
 
     public KeyguardBottomAreaView(Context context) {
         super(context);
@@ -72,10 +83,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mLockPatternUtils = new LockPatternUtils(mContext);
         mCameraImageView = (ImageView) findViewById(R.id.camera_button);
         mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
         mLockIcon = (ImageView) findViewById(R.id.lock_icon);
-        watchForDevicePolicyChanges();
+        watchForCameraPolicyChanges();
         watchForAccessibilityChanges();
         updateCameraVisibility();
         updatePhoneVisibility();
@@ -88,8 +100,19 @@
         mActivityStarter = activityStarter;
     }
 
+    private Intent getCameraIntent() {
+        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
+                mLockPatternUtils.getCurrentUser());
+        return mLockPatternUtils.isSecure() && !currentUserHasTrust
+                ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+    }
+
     private void updateCameraVisibility() {
-        boolean visible = !isCameraDisabledByDpm();
+        ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
+                PackageManager.MATCH_DEFAULT_ONLY,
+                mLockPatternUtils.getCurrentUser());
+        boolean visible = !isCameraDisabledByDpm() && resolved != null;
         mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
     }
 
@@ -122,19 +145,12 @@
         return false;
     }
 
-    private void watchForDevicePolicyChanges() {
+    private void watchForCameraPolicyChanges() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        getContext().registerReceiver(new BroadcastReceiver() {
-            public void onReceive(Context context, Intent intent) {
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        updateCameraVisibility();
-                    }
-                });
-            }
-        }, filter);
+        getContext().registerReceiverAsUser(mDevicePolicyReceiver,
+                UserHandle.ALL, filter, null, null);
+        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
     }
 
     private void watchForAccessibilityChanges() {
@@ -171,9 +187,12 @@
     }
 
     public void launchCamera() {
-        mContext.startActivityAsUser(
-                new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE),
-                UserHandle.CURRENT);
+        Intent intent = getCameraIntent();
+        if (intent == SECURE_CAMERA_INTENT) {
+            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        } else {
+            mActivityStarter.startActivity(intent);
+        }
     }
 
     public void launchPhone() {
@@ -186,6 +205,7 @@
         super.onVisibilityChanged(changedView, visibility);
         if (changedView == this && visibility == VISIBLE) {
             updateTrust();
+            updateCameraVisibility();
         }
     }
 
@@ -214,5 +234,25 @@
     @Override
     public void onMethodSecureChanged(boolean methodSecure) {
         updateTrust();
+        updateCameraVisibility();
     }
+
+    private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    updateCameraVisibility();
+                }
+            });
+        }
+    };
+
+    private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
+            new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onUserSwitchComplete(int userId) {
+            updateCameraVisibility();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b7a7b0a..3aaace4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -17,10 +17,14 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.os.SystemClock;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardViewBase;
@@ -43,6 +47,8 @@
     private StatusBarWindowManager mWindowManager;
     private KeyguardViewBase mKeyguardView;
     private ViewGroup mRoot;
+    private Interpolator mFadeOutInterpolator = new LinearInterpolator();
+    private boolean mFadingOut;
 
     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
             LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
@@ -86,6 +92,29 @@
         }
     }
 
+    public void animateHide(long delay, long duration) {
+        if (isShowing()) {
+            mFadingOut = true;
+            mKeyguardView.animate()
+                    .alpha(0)
+                    .withLayer()
+
+                    // Make it disappear faster, as the focus should be on the activity behind.
+                    .setDuration(duration / 3)
+                    .setInterpolator(mFadeOutInterpolator)
+                    .setStartDelay(delay)
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mFadingOut = false;
+                            hide(true /* destroyView */);
+                        }
+                    });
+        } else {
+            hide(true /* destroyView */);
+        }
+    }
+
     /**
      * Reset the state of the view.
      */
@@ -110,7 +139,7 @@
     }
 
     public boolean isShowing() {
-        return mRoot != null && mRoot.getVisibility() == View.VISIBLE;
+        return mRoot != null && mRoot.getVisibility() == View.VISIBLE && !mFadingOut;
     }
 
     public void prepare() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c8f185c..dfd5a88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -45,12 +45,12 @@
 
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
-        View.OnClickListener, KeyguardPageSwipeHelper.Callback {
+        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
+        KeyguardPageSwipeHelper.Callback {
 
-    private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.4f;
+    private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.6f;
 
     private KeyguardPageSwipeHelper mPageSwiper;
-    PhoneStatusBar mStatusBar;
     private StatusBarHeaderView mHeader;
     private View mQsContainer;
     private View mQsPanel;
@@ -86,6 +86,7 @@
     private int mQsPeekHeight;
     private float mNotificationTranslation;
     private int mStackScrollerIntrinsicPadding;
+    private boolean mStackScrollerOverscrolling;
     private boolean mQsExpansionEnabled = true;
     private ValueAnimator mQsExpansionAnimator;
     private FlingAnimationUtils mFlingAnimationUtils;
@@ -139,6 +140,7 @@
         mNotificationStackScroller = (NotificationStackScrollLayout)
                 findViewById(R.id.notification_stack_scroller);
         mNotificationStackScroller.setOnHeightChangedListener(this);
+        mNotificationStackScroller.setOverscrollTopChangedListener(this);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
                 android.R.interpolator.fast_out_slow_in);
         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
@@ -172,7 +174,9 @@
                 setQsStackScrollerPadding(mQsMaxExpansionHeight);
             }
         } else {
-            setQsExpansion(mQsMinExpansionHeight);
+            if (!mStackScrollerOverscrolling) {
+                setQsExpansion(mQsMinExpansionHeight);
+            }
             positionClockAndNotifications();
             mNotificationStackScroller.setStackHeight(getExpandedHeight());
         }
@@ -185,7 +189,10 @@
     private void positionClockAndNotifications() {
         boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
-            mStackScrollerIntrinsicPadding = mHeader.getBottom() + mQsPeekHeight
+            int bottom = mStackScrollerOverscrolling
+                    ? mHeader.getCollapsedHeight()
+                    : mHeader.getBottom();
+            mStackScrollerIntrinsicPadding = bottom + mQsPeekHeight
                     + mNotificationTopPadding;
             mTopPaddingAdjustment = 0;
         } else {
@@ -489,6 +496,16 @@
         }
     }
 
+
+    @Override
+    public void onOverscrollTopChanged(float amount) {
+        cancelAnimation();
+        float rounded = amount >= 1f ? amount : 0f;
+        mStackScrollerOverscrolling = rounded != 0f;
+        setQsExpansion(mQsMinExpansionHeight + rounded);
+        updateQsState();
+    }
+
     private void onQsExpansionStarted() {
         onQsExpansionStarted(0);
     }
@@ -516,9 +533,10 @@
     }
 
     private void updateQsState() {
-        mHeader.setExpanded(mQsExpanded);
+        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
+        mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling);
         mNotificationStackScroller.setEnabled(!mQsExpanded);
-        mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE);
+        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
         mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded
                 ? View.INVISIBLE
                 : View.VISIBLE);
@@ -528,7 +546,7 @@
     private void setQsExpansion(float height) {
         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
         mQsFullyExpanded = height == mQsMaxExpansionHeight;
-        if (height > mQsMinExpansionHeight && !mQsExpanded) {
+        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
             setQsExpanded(true);
         } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
             setQsExpanded(false);
@@ -536,7 +554,9 @@
         mQsExpansionHeight = height;
         mHeader.setExpansion(height - mQsPeekHeight);
         setQsTranslation(height);
-        setQsStackScrollerPadding(height);
+        if (!mStackScrollerOverscrolling) {
+            setQsStackScrollerPadding(height);
+        }
         mStatusBar.userActivity();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index c5a9b85..4686933 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -27,10 +27,13 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.StatusBarState;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -44,13 +47,16 @@
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
     }
 
+    protected PhoneStatusBar mStatusBar;
     private float mPeekHeight;
+    private float mHintDistance;
     private float mInitialOffsetOnTouch;
     private float mExpandedFraction = 0;
     private float mExpandedHeight = 0;
     private boolean mJustPeeked;
     private boolean mClosing;
     private boolean mTracking;
+    private boolean mTouchSlopExceeded;
     private int mTrackingPointer;
     protected int mTouchSlop;
 
@@ -66,6 +72,9 @@
     private float mInitialTouchY;
     private float mInitialTouchX;
 
+    private Interpolator mLinearOutSlowInInterpolator;
+    private Interpolator mBounceInterpolator;
+
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
     }
@@ -89,6 +98,9 @@
     public PanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
+        mLinearOutSlowInInterpolator =
+                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
+        mBounceInterpolator = new BounceInterpolator();
     }
 
     protected void loadDimens() {
@@ -98,6 +110,7 @@
 
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
+        mHintDistance = res.getDimension(R.dimen.hint_move_distance);
     }
 
     private void trackMovement(MotionEvent event) {
@@ -138,6 +151,7 @@
                 mInitialTouchY = y;
                 mInitialTouchX = x;
                 mInitialOffsetOnTouch = mExpandedHeight;
+                mTouchSlopExceeded = false;
                 if (mVelocityTracker == null) {
                     initVelocityTracker();
                 }
@@ -170,16 +184,18 @@
 
             case MotionEvent.ACTION_MOVE:
                 float h = y - mInitialTouchY;
-                if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop
-                        && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
-                    mInitialOffsetOnTouch = mExpandedHeight;
-                    mInitialTouchX = x;
-                    mInitialTouchY = y;
-                    if (mHeightAnimator != null) {
-                        mHeightAnimator.cancel(); // end any outstanding animations
+                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+                    mTouchSlopExceeded = true;
+                    if (waitForTouchSlop && !mTracking) {
+                        mInitialOffsetOnTouch = mExpandedHeight;
+                        mInitialTouchX = x;
+                        mInitialTouchY = y;
+                        if (mHeightAnimator != null) {
+                            mHeightAnimator.cancel(); // end any outstanding animations
+                        }
+                        onTrackingStarted();
+                        h = 0;
                     }
-                    onTrackingStarted();
-                    h = 0;
                 }
                 final float newHeight = h + mInitialOffsetOnTouch;
                 if (newHeight > mPeekHeight) {
@@ -200,8 +216,15 @@
             case MotionEvent.ACTION_CANCEL:
                 mTrackingPointer = -1;
                 trackMovement(event);
-                boolean expand = flingWithCurrentVelocity();
-                onTrackingStopped(expand);
+                if (mTracking && mTouchSlopExceeded) {
+                    float vel = getCurrentVelocity();
+                    boolean expand = flingExpands(vel);
+                    onTrackingStopped(expand);
+                    fling(vel, expand);
+                } else {
+                    boolean expands = onEmptySpaceClick();
+                    onTrackingStopped(expands);
+                }
                 if (mVelocityTracker != null) {
                     mVelocityTracker.recycle();
                     mVelocityTracker = null;
@@ -262,6 +285,7 @@
                 }
                 mInitialTouchY = y;
                 mInitialTouchX = x;
+                mTouchSlopExceeded = false;
                 initVelocityTracker();
                 trackMovement(event);
                 break;
@@ -285,6 +309,7 @@
                         mInitialTouchY = y;
                         mInitialTouchX = x;
                         mTracking = true;
+                        mTouchSlopExceeded = true;
                         onTrackingStarted();
                         return true;
                     }
@@ -323,18 +348,15 @@
     }
 
     /**
-     * @return whether the panel will be expanded after the animation
+     * @param vel the current velocity of the motion
+     * @return whether a fling should expands the panel; contracts otherwise
      */
-    private boolean flingWithCurrentVelocity() {
-        float vel = getCurrentVelocity();
-        boolean expand;
+    private boolean flingExpands(float vel) {
         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            expand = getExpandedFraction() > 0.5f;
+            return getExpandedFraction() > 0.5f;
         } else {
-            expand = vel > 0;
+            return vel > 0;
         }
-        fling(vel, expand);
-        return expand;
     }
 
     protected void fling(float vel, boolean expand) {
@@ -342,9 +364,10 @@
         float target = expand ? getMaxPanelHeight() : 0.0f;
         if (target == mExpandedHeight) {
             onExpandingFinished();
+            mBar.panelExpansionChanged(this, mExpandedFraction);
             return;
         }
-        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, target);
+        ValueAnimator animator = createHeightAnimator(target);
         if (expand) {
             mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
         } else {
@@ -356,12 +379,6 @@
                 animator.setDuration((long) (animator.getDuration() / 1.75f));
             }
         }
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                setExpandedHeight((Float) animation.getAnimatedValue());
-            }
-        });
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -430,7 +447,7 @@
 
     public void setExpandedHeightInternal(float h) {
         float fh = getMaxPanelHeight();
-        mExpandedHeight = Math.min(fh, h);
+        mExpandedHeight = Math.max(0, Math.min(fh, h));
         float overExpansion = h - fh;
         overExpansion = Math.max(0, overExpansion);
         if (overExpansion != mOverExpansion) {
@@ -442,16 +459,14 @@
         }
 
         onHeightUpdated(mExpandedHeight);
-        mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
+        mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : mExpandedHeight / fh);
     }
 
     protected void onOverExpansionChanged(float overExpansion) {
         mOverExpansion = overExpansion;
     }
 
-    protected void onHeightUpdated(float expandedHeight) {
-        requestLayout();
-    }
+    protected abstract void onHeightUpdated(float expandedHeight);
 
     /**
      * This returns the maximum height of the panel. Children should override this if their
@@ -526,6 +541,101 @@
         }
     }
 
+    protected void startUnlockHintAnimation() {
+
+        // We don't need to hint the user if an animation is already running or the user is changing
+        // the expansion.
+        if (mHeightAnimator != null || mTracking) {
+            return;
+        }
+        cancelPeek();
+        onExpandingStarted();
+        startUnlockHintAnimationPhase1();
+        mStatusBar.onUnlockHintStarted();
+    }
+
+    /**
+     * Phase 1: Move everything upwards.
+     */
+    private void startUnlockHintAnimationPhase1() {
+        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+        ValueAnimator animator = createHeightAnimator(target);
+        animator.setDuration(250);
+        animator.setInterpolator(mLinearOutSlowInInterpolator);
+        animator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCancelled) {
+                    mHeightAnimator = null;
+                    onExpandingFinished();
+                    mStatusBar.onUnlockHintFinished();
+                } else {
+                    startUnlockHintAnimationPhase2();
+                }
+            }
+        });
+        animator.start();
+        mHeightAnimator = animator;
+    }
+
+    /**
+     * Phase 2: Bounce down.
+     */
+    private void startUnlockHintAnimationPhase2() {
+        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+        animator.setDuration(450);
+        animator.setInterpolator(mBounceInterpolator);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mHeightAnimator = null;
+                onExpandingFinished();
+                mStatusBar.onUnlockHintFinished();
+            }
+        });
+        animator.start();
+        mHeightAnimator = animator;
+    }
+
+    private ValueAnimator createHeightAnimator(float targetHeight) {
+        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setExpandedHeight((Float) animation.getAnimatedValue());
+            }
+        });
+        return animator;
+    }
+
+    /**
+     * Gets called when the user performs a click anywhere in the empty area of the panel.
+     *
+     * @return whether the panel will be expanded after the action performed by this method
+     */
+    private boolean onEmptySpaceClick() {
+        switch (mStatusBar.getBarState()) {
+            case StatusBarState.KEYGUARD:
+                startUnlockHintAnimation();
+                return true;
+            case StatusBarState.SHADE_LOCKED:
+                // TODO: Go to Keyguard again.
+                return true;
+            case StatusBarState.SHADE:
+                collapse();
+                return false;
+            default:
+                return true;
+        }
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
                 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f9afcf3..b1216e69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -980,8 +980,10 @@
     }
 
     private void addHeadsUpView() {
+        int headsUpHeight = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.heads_up_window_height);
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT, headsUpHeight,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
@@ -2955,15 +2957,17 @@
     }
 
     public void onTrackingStarted() {
-        if (mState == StatusBarState.KEYGUARD) {
-            mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock);
-        }
+    }
+
+    public void onUnlockHintStarted() {
+        mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock);
+    }
+
+    public void onUnlockHintFinished() {
+        mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
     }
 
     public void onTrackingStopped(boolean expand) {
-        if (mState == StatusBarState.KEYGUARD) {
-            mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
-        }
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             if (!expand && !mUnlockMethodCache.isMethodInsecure()) {
                 showBouncer();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 1344703..7c87580 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -95,7 +95,6 @@
         mTiles.add(new LocationTile(this));
         mTiles.add(new CastTile(this));
         mTiles.add(new HotspotTile(this));
-        mTiles.add(new BugreportTile(this));
 
         mUserTracker = new CurrentUserTracker(mContext) {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 6156fc3..1264d75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -19,16 +19,12 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.view.View;
 import android.view.ViewTreeObserver;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
 
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
@@ -53,6 +49,11 @@
     private boolean mAnimateChange;
     private boolean mUpdatePending;
     private boolean mExpanding;
+    private boolean mAnimateKeyguardFadingOut;
+    private long mDurationOverride = -1;
+    private long mAnimationDelay;
+    private Runnable mOnAnimationFinished;
+    private boolean mAnimationStarted;
 
     private final Interpolator mInterpolator = new DecelerateInterpolator();
 
@@ -87,14 +88,26 @@
         scheduleUpdate();
     }
 
+    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) {
+        mAnimateKeyguardFadingOut = true;
+        mDurationOverride = duration;
+        mAnimationDelay = delay;
+        mAnimateChange = true;
+        mOnAnimationFinished = onAnimationFinished;
+        scheduleUpdate();
+    }
+
     private void scheduleUpdate() {
         if (mUpdatePending) return;
+
+        // Make sure that a frame gets scheduled.
+        mScrimBehind.invalidate();
         mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
         mUpdatePending = true;
     }
 
     private void updateScrims() {
-        if (!mKeyguardShowing) {
+        if (!mKeyguardShowing || mAnimateKeyguardFadingOut) {
             updateScrimNormal();
             setScrimInFrontColor(0);
         } else {
@@ -170,8 +183,20 @@
             }
         });
         anim.setInterpolator(mInterpolator);
-        anim.setDuration(ANIMATION_DURATION);
+        anim.setStartDelay(mAnimationDelay);
+        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+        anim.addListener(new AnimatorListenerAdapter() {
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mOnAnimationFinished != null) {
+                    mOnAnimationFinished.run();
+                    mOnAnimationFinished = null;
+                }
+            }
+        });
         anim.start();
+        mAnimationStarted = true;
     }
 
     private int getBackgroundAlpha(View scrim) {
@@ -188,6 +213,16 @@
         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
         mUpdatePending = false;
         updateScrims();
+        mAnimateKeyguardFadingOut = false;
+        mDurationOverride = -1;
+        mAnimationDelay = 0;
+
+        // Make sure that we always call the listener even if we didn't start an animation.
+        if (!mAnimationStarted && mOnAnimationFinished != null) {
+            mOnAnimationFinished.run();
+            mOnAnimationFinished = null;
+        }
+        mAnimationStarted = false;
         return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index c097e2d..13d3291 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -44,6 +44,7 @@
     private static final float EXPANSION_RUBBERBAND_FACTOR = 0.35f;
 
     private boolean mExpanded;
+    private boolean mOverscrolled;
     private boolean mKeyguardShowing;
 
     private View mBackground;
@@ -125,10 +126,12 @@
         return mExpandedHeight;
     }
 
-    public void setExpanded(boolean expanded) {
+    public void setExpanded(boolean expanded, boolean overscrolled) {
         boolean changed = expanded != mExpanded;
+        boolean overscrollChanged = overscrolled != mOverscrolled;
         mExpanded = expanded;
-        if (changed) {
+        mOverscrolled = overscrolled;
+        if (changed || overscrollChanged) {
             updateHeights();
             updateVisibilities();
             updateSystemIconsLayoutParams();
@@ -136,7 +139,7 @@
             updateZTranslation();
             updateClickTargets();
             if (mQSPanel != null) {
-                mQSPanel.setExpanded(expanded);
+                mQSPanel.setExpanded(expanded && !overscrolled);
             }
         }
     }
@@ -184,13 +187,13 @@
         mDateTime.setVisibility(onKeyguardAndCollapsed ? View.INVISIBLE : View.VISIBLE);
         mKeyguardCarrierText.setVisibility(onKeyguardAndCollapsed ? View.VISIBLE : View.GONE);
         mDate.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
-        mSettingsButton.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
+        mSettingsButton.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE);
         mBrightnessContainer.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
         if (mStatusIcons != null) {
-            mStatusIcons.setVisibility(!mExpanded ? View.VISIBLE : View.GONE);
+            mStatusIcons.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
         }
         if (mSignalCluster != null) {
-            mSignalCluster.setVisibility(!mExpanded ? View.VISIBLE : View.GONE);
+            mSignalCluster.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d5551b8..e3145a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.View;
@@ -183,11 +184,23 @@
     /**
      * Hides the keyguard view
      */
-    public void hide() {
+    public void hide(long startTime, long fadeoutDuration) {
         mShowing = false;
         mPhoneStatusBar.hideKeyguard();
+        mStatusBarWindowManager.setKeyguardFadingAway(true);
         mStatusBarWindowManager.setKeyguardShowing(false);
-        mBouncer.hide(true /* destroyView */);
+        long uptimeMillis = SystemClock.uptimeMillis();
+        long delay = startTime - uptimeMillis;
+        if (delay < 0) {
+            delay = 0;
+        }
+        mBouncer.animateHide(delay, fadeoutDuration);
+        mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
+            @Override
+            public void run() {
+                mStatusBarWindowManager.setKeyguardFadingAway(false);
+            }
+        });
         mViewMediatorCallback.keyguardGone();
         updateStates();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index b7bf6cd..fe57cef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -124,7 +124,8 @@
     }
 
     private void applyHeight(State state) {
-        boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded;
+        boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded
+                || state.keyguardFadingAway;
         if (expanded) {
             mLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
         } else {
@@ -201,6 +202,11 @@
         apply(mCurrentState);
     }
 
+    public void setKeyguardFadingAway(boolean keyguardFadingAway) {
+        mCurrentState.keyguardFadingAway = keyguardFadingAway;
+        apply(mCurrentState);
+    }
+
     /**
      * @param state The {@link StatusBarState} of the status bar.
      */
@@ -217,6 +223,7 @@
         boolean statusBarFocusable;
         long keyguardUserActivityTimeout;
         boolean bouncerShowing;
+        boolean keyguardFadingAway;
 
         /**
          * The {@link BaseStatusBar} state from the status bar.
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 f4145cd..8e9fb30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,14 +16,18 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
-
 public interface BluetoothController {
-    void addStateChangedCallback(BluetoothStateChangeCallback callback);
-    void removeStateChangedCallback(BluetoothStateChangeCallback callback);
+    void addStateChangedCallback(Callback callback);
+    void removeStateChangedCallback(Callback callback);
 
     boolean isBluetoothSupported();
     boolean isBluetoothEnabled();
     boolean isBluetoothConnected();
+    boolean isBluetoothConnecting();
+    String getLastDeviceName();
     void setBluetoothEnabled(boolean enabled);
+
+    public interface Callback {
+        void onBluetoothStateChange(boolean enabled, boolean connecting);
+    }
 }
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 5a19881..117bf61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -31,14 +30,13 @@
 public class BluetoothControllerImpl extends BroadcastReceiver implements BluetoothController {
     private static final String TAG = "StatusBar.BluetoothController";
 
+    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+    private final Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
     private final BluetoothAdapter mAdapter;
 
-    private boolean mEnabled = false;
-
-    private Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
-
-    private ArrayList<BluetoothStateChangeCallback> mChangeCallbacks =
-            new ArrayList<BluetoothStateChangeCallback>();
+    private boolean mEnabled;
+    private boolean mConnecting;
+    private BluetoothDevice mLastDevice;
 
     public BluetoothControllerImpl(Context context) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -57,14 +55,14 @@
         updateBondedBluetoothDevices();
     }
 
-    public void addStateChangedCallback(BluetoothStateChangeCallback cb) {
-        mChangeCallbacks.add(cb);
+    public void addStateChangedCallback(Callback cb) {
+        mCallbacks.add(cb);
         fireCallback(cb);
     }
 
     @Override
-    public void removeStateChangedCallback(BluetoothStateChangeCallback cb) {
-        mChangeCallbacks.remove(cb);
+    public void removeStateChangedCallback(Callback cb) {
+        mCallbacks.remove(cb);
     }
 
     @Override
@@ -79,6 +77,12 @@
     }
 
     @Override
+    public boolean isBluetoothConnecting() {
+        return mAdapter != null
+                && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
+    }
+
+    @Override
     public void setBluetoothEnabled(boolean enabled) {
         if (mAdapter != null) {
             if (enabled) {
@@ -99,6 +103,13 @@
     }
 
     @Override
+    public String getLastDeviceName() {
+        return mLastDevice != null ? mLastDevice.getName()
+                : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getName()
+                : null;
+    }
+
+    @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
 
@@ -106,6 +117,11 @@
             handleAdapterStateChange(
                     intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
         }
+        if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+            mConnecting = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)
+                    == BluetoothAdapter.STATE_CONNECTING;
+            mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        }
         fireCallbacks();
         updateBondedBluetoothDevices();
     }
@@ -131,12 +147,12 @@
     }
 
     private void fireCallbacks() {
-        for (BluetoothStateChangeCallback cb : mChangeCallbacks) {
+        for (Callback cb : mCallbacks) {
             fireCallback(cb);
         }
     }
 
-    private void fireCallback(BluetoothStateChangeCallback cb) {
-        cb.onBluetoothStateChange(mEnabled);
+    private void fireCallback(Callback cb) {
+        cb.onBluetoothStateChange(mEnabled, mConnecting);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 9271e71..ac26da2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -47,7 +47,7 @@
     int[] mTmpTwoArray = new int[2];
 
     private final int mTouchSensitivityDelay;
-    private final float mMaxAlpha = 0.95f;
+    private final float mMaxAlpha = 1f;
     private SwipeHelper mSwipeHelper;
     private EdgeSwipeHelper mEdgeSwipeHelper;
 
@@ -114,7 +114,7 @@
         float pagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
         float touchSlop = viewConfiguration.getScaledTouchSlop();
         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
-        mSwipeHelper.setMaxAlpha(mMaxAlpha);
+        mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
         mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
 
         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
@@ -184,7 +184,13 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         Outline o = new Outline();
-        o.setRect(0, 0, mContentHolder.getWidth(), mContentHolder.getHeight());
+
+        // Apply padding to shadow.
+        int outlineLeft = mContentHolder.getPaddingLeft();
+        int outlineTop = mContentHolder.getPaddingTop();
+        o.setRect(outlineLeft, outlineTop,
+                mContentHolder.getWidth() - outlineLeft - mContentHolder.getPaddingRight(),
+                mContentHolder.getHeight() - outlineTop - mContentHolder.getPaddingBottom());
         mContentHolder.setOutline(o);
     }
 
@@ -246,6 +252,12 @@
     }
 
     @Override
+    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+        getBackground().setAlpha((int) (255 * swipeProgress));
+        return false;
+    }
+
+    @Override
     public View getChildAtPosition(MotionEvent ev) {
         return mContentHolder;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 58176b9..6892b85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -18,13 +18,10 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
-
 import android.graphics.Canvas;
 import android.graphics.Paint;
-
 import android.util.AttributeSet;
 import android.util.Log;
-
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -40,8 +37,8 @@
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.SpeedBumpView;
-import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
+import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
 
 import java.util.ArrayList;
 
@@ -121,6 +118,7 @@
     private float mOverScrolledBottomPixels;
 
     private OnChildLocationsChangedListener mListener;
+    private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private boolean mNeedsAnimation;
     private boolean mTopPaddingNeedsAnimation;
@@ -449,6 +447,11 @@
         }
     }
 
+    @Override
+    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+        return false;
+    }
+
     public void onBeginDrag(View v) {
         setSwipingInProgress(true);
         mAmbientState.onBeginDrag(v);
@@ -870,9 +873,24 @@
             setOverScrolledPixels(amount / RUBBER_BAND_FACTOR, onTop);
             mAmbientState.setOverScrollAmount(amount, onTop);
             requestChildrenUpdate();
+            if (onTop) {
+                float scrollAmount = mOwnScrollY < 0 ? -mOwnScrollY : 0;
+                notifyOverscrollTopListener(scrollAmount + amount);
+            }
         }
     }
 
+    private void notifyOverscrollTopListener(float amount) {
+        if (mOverscrollTopChangedListener != null) {
+            mOverscrollTopChangedListener.onOverscrollTopChanged(amount);
+        }
+    }
+
+    public void setOverscrollTopChangedListener(
+            OnOverscrollTopChangedListener overscrollTopChangedListener) {
+        mOverscrollTopChangedListener = overscrollTopChangedListener;
+    }
+
     public float getCurrentOverScrollAmount(boolean top) {
         return mAmbientState.getOverScrollAmount(top);
     }
@@ -908,6 +926,12 @@
                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
                 invalidateParentIfNeeded();
                 updateChildren();
+                float overScrollTop = getCurrentOverScrollAmount(true);
+                if (mOwnScrollY < 0) {
+                    notifyOverscrollTopListener(-mOwnScrollY + overScrollTop);
+                } else {
+                    notifyOverscrollTopListener(overScrollTop);
+                }
             }
         } else {
             customScrollTo(scrollY);
@@ -1591,6 +1615,13 @@
         public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
     }
 
+    /**
+     * A listener that gets notified when the overscroll at the top has changed.
+     */
+    public interface OnOverscrollTopChangedListener {
+        public void onOverscrollTopChanged(float amount);
+    }
+
     static class AnimationEvent {
 
         static AnimationFilter[] FILTERS = new AnimationFilter[] {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index f41ab3a..2edd7d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -715,6 +715,9 @@
 
     public void animateOverScrollToAmount(float targetAmount, final boolean onTop) {
         final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
+        if (targetAmount == startOverScrollAmount) {
+            return;
+        }
         cancelOverScrollAnimators(onTop);
         ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
                 targetAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index cbfc641..898b46e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.volume;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.BroadcastReceiver;
@@ -42,12 +44,15 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.widget.FrameLayout;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.widget.ImageView;
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
@@ -81,6 +86,7 @@
     private static final int FREE_DELAY = 10000;
     private static final int TIMEOUT_DELAY = 3000;
     private static final int TIMEOUT_DELAY_EXPANDED = 10000;
+    private static final float ICON_PULSE_SCALE = 1.3f;
 
     private static final int MSG_VOLUME_CHANGED = 0;
     private static final int MSG_FREE_RESOURCES = 1;
@@ -105,6 +111,7 @@
     protected final Context mContext;
     private final AudioManager mAudioManager;
     private final ZenModeController mZenController;
+    private final Interpolator mFastOutSlowInInterpolator;
     private boolean mRingIsSilent;
     private boolean mVoiceCapable;
     private boolean mZenModeCapable;
@@ -220,6 +227,7 @@
         ViewGroup group;
         ImageView icon;
         SeekBar seekbarView;
+        View seekbarContainer;
         int iconRes;
         int iconMuteRes;
     }
@@ -273,6 +281,8 @@
         mParent = parent;
         mZenController = zenController;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.fast_out_slow_in);
 
         // For now, only show master volume if master volume is supported
         final Resources res = context.getResources();
@@ -284,7 +294,6 @@
             }
         }
         if (LOGD) Log.d(mTag, String.format("new VolumePanel hasParent=%s", parent != null));
-        final int layoutId = com.android.systemui.R.layout.volume_panel;
         if (parent == null) {
             // dialog mode
             mDialog = new Dialog(context) {
@@ -318,15 +327,7 @@
                     | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                     | LayoutParams.FLAG_HARDWARE_ACCELERATED);
             mDialog.setCanceledOnTouchOutside(true);
-            // temporary workaround for no window shadows
-            final FrameLayout layout = new FrameLayout(mContext);
-            final int z = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_z);
-            layout.setPadding(z, z, z, z);
-            final View v = LayoutInflater.from(mContext).inflate(layoutId, layout, false);
-            v.setBackgroundResource(com.android.systemui.R.drawable.qs_panel_background);
-            v.setElevation(z);
-            layout.addView(v);
-            mDialog.setContentView(layout);
+            mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
             mDialog.setOnDismissListener(new OnDismissListener() {
                 @Override
                 public void onDismiss(DialogInterface dialog) {
@@ -336,6 +337,7 @@
             });
 
             mDialog.create();
+            // temporary workaround, until we support window-level shadows
             mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000));
 
             mView = window.findViewById(R.id.content);
@@ -350,7 +352,8 @@
         } else {
             // embedded mode
             mDialog = null;
-            mView = LayoutInflater.from(mContext).inflate(layoutId, parent, true);
+            mView = LayoutInflater.from(mContext).inflate(
+                    com.android.systemui.R.layout.volume_panel, parent, true);
         }
         mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel);
         mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel);
@@ -460,6 +463,26 @@
             sc.iconRes = streamRes.iconRes;
             sc.iconMuteRes = streamRes.iconMuteRes;
             sc.icon.setImageResource(sc.iconRes);
+            sc.icon.setClickable(isNotificationOrRing(streamType));
+            if (sc.icon.isClickable()) {
+                sc.icon.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        resetTimeout();
+                        toggle(sc);
+                    }
+                });
+                sc.icon.setOnLongClickListener(new OnLongClickListener() {
+                    @Override
+                    public boolean onLongClick(View v) {
+                        resetTimeout();
+                        longToggle(sc);
+                        return true;
+                    }
+                });
+            }
+            sc.seekbarContainer =
+                    sc.group.findViewById(com.android.systemui.R.id.seekbar_container);
             sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
             final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
                     streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
@@ -470,6 +493,26 @@
         }
     }
 
+    private void toggle(StreamControl sc) {
+        if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
+            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+            postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
+        } else {
+            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
+        }
+    }
+
+    private void longToggle(StreamControl sc) {
+        if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
+        } else {
+            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+            postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI); // disable the slider
+        }
+    }
+
     private void reorderSliders(int activeStreamType) {
         mSliderPanel.removeAllViews();
 
@@ -493,21 +536,62 @@
         // Force reloading the image resource
         sc.icon.setImageDrawable(null);
         sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
-        if (((sc.streamType == AudioManager.STREAM_RING) ||
-                (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
+        if (isNotificationOrRing(sc.streamType) &&
                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
             sc.icon.setImageResource(com.android.systemui.R.drawable.ic_ringer_vibrate);
         }
+        updateSliderEnabled(sc, muted, false);
+    }
+
+    private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
+        final boolean wasEnabled = sc.seekbarView.isEnabled();
         if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
             // never disable touch interactions for remote playback, the muting is not tied to
             // the state of the phone.
             sc.seekbarView.setEnabled(true);
-        } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
+        } else if (fixedVolume ||
+                        (sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
                         (sConfirmSafeVolumeDialog != null)) {
             sc.seekbarView.setEnabled(false);
+        } else if (isNotificationOrRing(sc.streamType)
+                && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+            sc.seekbarView.setEnabled(false);
         } else {
             sc.seekbarView.setEnabled(true);
         }
+        // pulse the ringer icon when the disabled slider is touched in silent mode
+        if (sc.icon.isClickable() && wasEnabled != sc.seekbarView.isEnabled()) {
+            if (sc.seekbarView.isEnabled()) {
+                sc.seekbarContainer.setOnTouchListener(null);
+            } else {
+                sc.seekbarContainer.setOnTouchListener(new View.OnTouchListener() {
+                    @Override
+                    public boolean onTouch(View v, MotionEvent event) {
+                        resetTimeout();
+                        pulseIcon(sc.icon);
+                        return false;
+                    }
+                });
+            }
+        }
+    }
+
+    private void pulseIcon(final ImageView icon) {
+        if (icon.getScaleX() != 1) return;  // already running
+        icon.animate().cancel();
+        icon.animate().scaleX(ICON_PULSE_SCALE).scaleY(ICON_PULSE_SCALE)
+                .setInterpolator(mFastOutSlowInInterpolator)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        icon.animate().scaleX(1).scaleY(1).setListener(null);
+                    }
+                });
+    }
+
+    private static boolean isNotificationOrRing(int streamType) {
+        return streamType == AudioManager.STREAM_RING
+                || streamType == AudioManager.STREAM_NOTIFICATION;
     }
 
     public void setZenModePanelCallback(ZenModePanel.Callback callback) {
@@ -562,8 +646,7 @@
 
     private void updateZenMode(boolean zen) {
         if (mZenModeCapable) {
-            final boolean show = mActiveStreamType == AudioManager.STREAM_NOTIFICATION
-                    || mActiveStreamType == AudioManager.STREAM_RING;
+            final boolean show = isNotificationOrRing(mActiveStreamType);
             mExpandButton.setVisibility(show ? View.VISIBLE : View.GONE);
             mExpandDivider.setVisibility(show ? View.VISIBLE : View.GONE);
             mExpandButton.setImageResource(zen ? com.android.systemui.R.drawable.ic_vol_zen_on
@@ -576,7 +659,7 @@
 
     public void postZenModeChanged(boolean zen) {
         removeMessages(MSG_ZEN_MODE_CHANGED);
-        obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0).sendToTarget();
+        obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0, 0).sendToTarget();
     }
 
     public void postVolumeChanged(int streamType, int flags) {
@@ -654,7 +737,7 @@
 
     public void postLayoutDirection(int layoutDirection) {
         removeMessages(MSG_LAYOUT_DIRECTION);
-        obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection).sendToTarget();
+        obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget();
     }
 
     /**
@@ -790,15 +873,8 @@
             }
 
             sc.seekbarView.setProgress(index);
-            if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
-                    (streamType != mAudioManager.getMasterStreamType() &&
-                     streamType != AudioService.STREAM_REMOTE_MUSIC &&
-                     isMuted(streamType)) ||
-                     sConfirmSafeVolumeDialog != null) {
-                sc.seekbarView.setEnabled(false);
-            } else {
-                sc.seekbarView.setEnabled(true);
-            }
+            updateSliderEnabled(sc, isMuted(streamType),
+                    (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
         }
 
         if (!isShowing()) {
@@ -822,6 +898,11 @@
                 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
         }
+
+        // Pulse the slider icon if an adjustment was suppressed due to silent mode.
+        if (sc != null && (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
+            pulseIcon(sc.icon);
+        }
     }
 
     private boolean isShowing() {
@@ -1173,7 +1254,7 @@
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         public void onZenChanged(boolean zen) {
-            updateZenMode(zen);
+            postZenModeChanged(zen);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 77d267e..c338563 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -45,6 +45,7 @@
     private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
     public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
 
+    private final Context mContext;
     private final LayoutInflater mInflater;
     private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>();
     private final H mHandler = new H();
@@ -56,6 +57,7 @@
 
     public ZenModePanel(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mContext = context;
         mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme));
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index 673ce0b..0c16b78 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -35,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -51,6 +52,7 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -185,7 +187,8 @@
 
         // If we only have 1 item and it's a simple press action, just do this action.
         if (mAdapter.getCount() == 1
-                && mAdapter.getItem(0) instanceof SinglePressAction) {
+                && mAdapter.getItem(0) instanceof SinglePressAction
+                && !(mAdapter.getItem(0) instanceof LongPressAction)) {
             ((SinglePressAction) mAdapter.getItem(0)).onPress();
         } else {
             WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
@@ -262,7 +265,7 @@
                 continue;
             }
             if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
-                mItems.add(getPowerAction());
+                mItems.add(new PowerAction());
             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                 mItems.add(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)
@@ -300,7 +303,11 @@
                     @Override
                     public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
                             long id) {
-                        return mAdapter.getItem(position).onLongPress();
+                        final Action action = mAdapter.getItem(position);
+                        if (action instanceof LongPressAction) {
+                            return ((LongPressAction) action).onLongPress();
+                        }
+                        return false;
                     }
         });
         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
@@ -310,34 +317,38 @@
         return dialog;
     }
 
-    private Action getPowerAction() {
-        return new SinglePressAction(
-                com.android.internal.R.drawable.ic_lock_power_off,
-                R.string.global_action_power_off) {
+    private final class PowerAction extends SinglePressAction implements LongPressAction {
+        private PowerAction() {
+            super(com.android.internal.R.drawable.ic_lock_power_off,
+                R.string.global_action_power_off);
+        }
 
-            public void onPress() {
-                // shutdown by making sure radio and power are handled accordingly.
-                mWindowManagerFuncs.shutdown(true);
-            }
+        @Override
+        public boolean onLongPress() {
+            mWindowManagerFuncs.rebootSafeMode(true);
+            return true;
+        }
 
-            public boolean onLongPress() {
-                mWindowManagerFuncs.rebootSafeMode(true);
-                return true;
-            }
+        @Override
+        public boolean showDuringKeyguard() {
+            return true;
+        }
 
-            public boolean showDuringKeyguard() {
-                return true;
-            }
+        @Override
+        public boolean showBeforeProvisioning() {
+            return true;
+        }
 
-            public boolean showBeforeProvisioning() {
-                return true;
-            }
-        };
+        @Override
+        public void onPress() {
+            // shutdown by making sure radio and power are handled accordingly.
+            mWindowManagerFuncs.shutdown(false /* confirm */);
+        }
     }
 
     private Action getBugReportAction() {
-        return new SinglePressAction(com.android.internal.R.drawable.stat_sys_adb,
-                R.string.global_action_bug_report) {
+        return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport,
+                R.string.bugreport_title) {
 
             public void onPress() {
                 AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
@@ -367,10 +378,6 @@
                 dialog.show();
             }
 
-            public boolean onLongPress() {
-                return false;
-            }
-
             public boolean showDuringKeyguard() {
                 return true;
             }
@@ -378,6 +385,14 @@
             public boolean showBeforeProvisioning() {
                 return false;
             }
+
+            @Override
+            public String getStatus() {
+                return mContext.getString(
+                        com.android.internal.R.string.bugreport_status,
+                        Build.VERSION.RELEASE,
+                        Build.ID);
+            }
         };
     }
 
@@ -393,11 +408,6 @@
             }
 
             @Override
-            public boolean onLongPress() {
-                return false;
-            }
-
-            @Override
             public boolean showDuringKeyguard() {
                 return true;
             }
@@ -583,8 +593,6 @@
 
         void onPress();
 
-        public boolean onLongPress();
-
         /**
          * @return whether this action should appear in the dialog when the keygaurd
          *    is showing.
@@ -601,6 +609,13 @@
     }
 
     /**
+     * An action that also supports long press.
+     */
+    private interface LongPressAction extends Action {
+        boolean onLongPress();
+    }
+
+    /**
      * A single press action maintains no state, just responds to a press
      * and takes an action.
      */
@@ -635,12 +650,12 @@
             return true;
         }
 
-        abstract public void onPress();
-
-        public boolean onLongPress() {
-            return false;
+        public String getStatus() {
+            return null;
         }
 
+        abstract public void onPress();
+
         public View create(
                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
             View v = inflater.inflate(R.layout.global_actions_item, parent, false);
@@ -648,7 +663,13 @@
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
             TextView messageView = (TextView) v.findViewById(R.id.message);
 
-            v.findViewById(R.id.status).setVisibility(View.GONE);
+            TextView statusView = (TextView) v.findViewById(R.id.status);
+            final String status = getStatus();
+            if (!TextUtils.isEmpty(status)) {
+                statusView.setText(status);
+            } else {
+                statusView.setVisibility(View.GONE);
+            }
             if (mIcon != null) {
                 icon.setImageDrawable(mIcon);
                 icon.setScaleType(ScaleType.CENTER_CROP);
@@ -769,10 +790,6 @@
             changeStateFromPress(nowOn);
         }
 
-        public boolean onLongPress() {
-            return false;
-        }
-
         public boolean isEnabled() {
             return !mState.inTransition();
         }
@@ -862,10 +879,6 @@
         public void onPress() {
         }
 
-        public boolean onLongPress() {
-            return false;
-        }
-
         public boolean showDuringKeyguard() {
             return true;
         }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c483836..30282dd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1914,9 +1914,8 @@
 
     @Override
     public Animation createForceHideEnterAnimation(boolean onWallpaper) {
-        return AnimationUtils.loadAnimation(mContext, onWallpaper
-                ? com.android.internal.R.anim.lock_screen_wallpaper_behind_enter
-                : com.android.internal.R.anim.lock_screen_behind_enter);
+        return AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.lock_screen_behind_enter);
     }
 
     private static void awakenDreams() {
@@ -4571,14 +4570,9 @@
     }
 
     @Override
-    public void startKeyguardExitAnimation(final long fadeoutDuration) {
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         if (mKeyguardDelegate != null) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mKeyguardDelegate.startKeyguardExitAnimation(fadeoutDuration);
-                }
-            });
+            mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
index faf7020..63a5850 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
@@ -274,9 +274,9 @@
         mKeyguardState.currentUser = newUserId;
     }
 
-    public void startKeyguardExitAnimation(long fadeoutDuration) {
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         if (mKeyguardService != null) {
-            mKeyguardService.startKeyguardExitAnimation(fadeoutDuration);
+            mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
index f236ce7..5096bd3 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
@@ -190,9 +190,9 @@
         }
     }
 
-    public void startKeyguardExitAnimation(long fadeoutDuration) {
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         try {
-            mService.startKeyguardExitAnimation(fadeoutDuration);
+            mService.startKeyguardExitAnimation(startTime, fadeoutDuration);
         } catch (RemoteException e) {
             Slog.w(TAG , "Remote Exception", e);
         }
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index d5f045e..d31fb60 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2372,6 +2372,18 @@
             }
         }
 
+        voldPath = maybeTranslatePathForVold(appPath,
+                userEnv.buildExternalStorageAppMediaDirs(callingPkg),
+                userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg));
+        if (voldPath != null) {
+            try {
+                mConnector.execute("volume", "mkdirs", voldPath);
+                return 0;
+            } catch (NativeDaemonConnectorException e) {
+                return e.getCode();
+            }
+        }
+
         throw new SecurityException("Invalid mkdirs path: " + appPath);
     }
 
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
new file mode 100644
index 0000000..579f89f
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -0,0 +1,369 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Feature action that handles device discovery sequences.
+ * Device discovery is launched when TV device is woken from "Standby" state
+ * or enabled "Control for Hdmi" from disabled state.
+ *
+ * <p>Device discovery goes through the following steps.
+ * <ol>
+ *   <li>Poll all non-local devices by sending &lt;Polling Message&gt;
+ *   <li>Gather "Physical address" and "device type" of all acknowledged devices
+ *   <li>Gather "OSD (display) name" of all acknowledge devices
+ *   <li>Gather "Vendor id" of all acknowledge devices
+ * </ol>
+ */
+final class DeviceDiscoveryAction extends FeatureAction {
+    private static final String TAG = "DeviceDiscoveryAction";
+
+    // State in which the action is waiting for device polling.
+    private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
+    // State in which the action is waiting for gathering physical address of non-local devices.
+    private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
+    // State in which the action is waiting for gathering osd name of non-local devices.
+    private static final int STATE_WAITING_FOR_OSD_NAME = 3;
+    // State in which the action is waiting for gathering vendor id of non-local devices.
+    private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
+
+    private static final int DEVICE_POLLING_RETRY = 1;
+
+    // TODO: Move this to common place
+    private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
+
+    /**
+     * Interface used to report result of device discovery.
+     */
+    interface DeviceDiscoveryCallback {
+        /**
+         * Called when device discovery is done.
+         *
+         * @param deviceInfos a list of all non-local devices. It can be empty list.
+         */
+        void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos);
+    }
+
+    // An internal container used to keep track of device information during
+    // this action.
+    private static final class DeviceInfo {
+        private final int mLogicalAddress;
+
+        private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+        private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+        private String mDisplayName = "";
+        private int mDeviceType = HdmiCec.DEVICE_INACTIVE;
+
+        private DeviceInfo(int logicalAddress) {
+            mLogicalAddress = logicalAddress;
+        }
+
+        private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
+            return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
+                    mDisplayName);
+        }
+    }
+
+    private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
+    private final DeviceDiscoveryCallback mCallback;
+    private int mProcessedDeviceCount = 0;
+
+    /**
+     * @Constructor
+     *
+     * @param service
+     * @param sourceAddress
+     */
+    DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
+            DeviceDiscoveryCallback callback) {
+        super(service, sourceAddress);
+        mCallback = Preconditions.checkNotNull(callback);
+    }
+
+    @Override
+    boolean start() {
+        mDevices.clear();
+        mState = STATE_WAITING_FOR_DEVICE_POLLING;
+
+        mService.pollDevices(new DevicePollingCallback() {
+            @Override
+            public void onPollingFinished(List<Integer> ackedAddress) {
+                if (ackedAddress.isEmpty()) {
+                    Slog.v(TAG, "No device is detected.");
+                    finish();
+                    return;
+                }
+
+                Slog.v(TAG, "Device detected: " + ackedAddress);
+                allocateDevices(ackedAddress);
+                startPhysicalAddressStage();
+            }
+        }, DEVICE_POLLING_RETRY);
+        return true;
+    }
+
+    private void allocateDevices(List<Integer> addresses) {
+        for (Integer i : addresses) {
+            DeviceInfo info = new DeviceInfo(i);
+            mDevices.add(info);
+        }
+    }
+
+    private void startPhysicalAddressStage() {
+        Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
+        mProcessedDeviceCount = 0;
+        mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
+
+        checkAndProceedStage();
+    }
+
+    private boolean verifyValidLogicalAddress(int address) {
+        return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED;
+    }
+
+    private void queryPhysicalAddress(int address) {
+        if (!verifyValidLogicalAddress(address)) {
+            checkAndProceedStage();
+            return;
+        }
+
+        mActionTimer.clearTimerMessage();
+        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
+        addTimer(mState, TIMEOUT_MS);
+    }
+
+    private void startOsdNameStage() {
+        Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
+        mProcessedDeviceCount = 0;
+        mState = STATE_WAITING_FOR_OSD_NAME;
+
+        checkAndProceedStage();
+    }
+
+    private void queryOsdName(int address) {
+        if (!verifyValidLogicalAddress(address)) {
+            checkAndProceedStage();
+            return;
+        }
+
+        mActionTimer.clearTimerMessage();
+        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
+        addTimer(mState, TIMEOUT_MS);
+    }
+
+    private void startVendorIdStage() {
+        Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
+
+        mProcessedDeviceCount = 0;
+        mState = STATE_WAITING_FOR_VENDOR_ID;
+
+        checkAndProceedStage();
+    }
+
+    private void queryVendorId(int address) {
+        if (!verifyValidLogicalAddress(address)) {
+            checkAndProceedStage();
+            return;
+        }
+
+        mActionTimer.clearTimerMessage();
+        sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
+        addTimer(mState, TIMEOUT_MS);
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        switch (mState) {
+            case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+                if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
+                    handleReportPhysicalAddress(cmd);
+                    return true;
+                }
+                return false;
+            case STATE_WAITING_FOR_OSD_NAME:
+                if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) {
+                    handleSetOsdName(cmd);
+                    return true;
+                }
+                return false;
+            case STATE_WAITING_FOR_VENDOR_ID:
+                if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+                    handleVendorId(cmd);
+                    return true;
+                }
+                return false;
+            case STATE_WAITING_FOR_DEVICE_POLLING:
+                // Fall through.
+            default:
+                return false;
+        }
+    }
+
+    private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
+        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+        if (current.mLogicalAddress != cmd.getSource()) {
+            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+                    cmd.getSource());
+            return;
+        }
+
+        byte params[] = cmd.getParams();
+        if (params.length == 3) {
+            current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF);
+            current.mDeviceType = params[2] & 0xFF;
+
+            increaseProcessedDeviceCount();
+            checkAndProceedStage();
+        } else {
+            // Physical address is a critical element in device info.
+            // If failed, remove device from device list and proceed to the next device.
+            removeDevice(mProcessedDeviceCount);
+            checkAndProceedStage();
+        }
+    }
+
+    private void handleSetOsdName(HdmiCecMessage cmd) {
+        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+        if (current.mLogicalAddress != cmd.getSource()) {
+            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+                    cmd.getSource());
+            return;
+        }
+
+        String displayName = null;
+        try {
+            displayName = new String(cmd.getParams(), "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
+            // If failed to get display name, use the default name of device.
+            displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress);
+        }
+        current.mDisplayName = displayName;
+        increaseProcessedDeviceCount();
+        checkAndProceedStage();
+    }
+
+    private void handleVendorId(HdmiCecMessage cmd) {
+        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+        if (current.mLogicalAddress != cmd.getSource()) {
+            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+                    cmd.getSource());
+            return;
+        }
+
+        byte[] params = cmd.getParams();
+        if (params.length == 3) {
+            int vendorId = ((params[0] & 0xFF) << 16)
+                    | ((params[1] & 0xFF) << 8)
+                    | (params[2] & 0xFF);
+            current.mVendorId = vendorId;
+        } else {
+            Slog.w(TAG, "Invalid vendor id: " + cmd.toString());
+        }
+        increaseProcessedDeviceCount();
+        checkAndProceedStage();
+    }
+
+    private void increaseProcessedDeviceCount() {
+        mProcessedDeviceCount++;
+    }
+
+    private void removeDevice(int index) {
+        mDevices.remove(index);
+    }
+
+    private void wrapUpAndFinish() {
+        Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
+        ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>();
+        for (DeviceInfo info : mDevices) {
+            HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo();
+            Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
+            result.add(cecDeviceInfo);
+        }
+        Slog.v(TAG, "--------------------------------------------");
+        mCallback.onDeviceDiscoveryDone(result);
+        finish();
+    }
+
+    private void checkAndProceedStage() {
+        if (mDevices.isEmpty()) {
+            wrapUpAndFinish();
+            return;
+        }
+
+        // If finished current stage, move on to next stage.
+        if (mProcessedDeviceCount == mDevices.size()) {
+            mProcessedDeviceCount = 0;
+            switch (mState) {
+                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+                    startOsdNameStage();
+                    return;
+                case STATE_WAITING_FOR_OSD_NAME:
+                    startVendorIdStage();
+                    return;
+                case STATE_WAITING_FOR_VENDOR_ID:
+                    wrapUpAndFinish();
+                    return;
+                default:
+                    return;
+            }
+        } else {
+            int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
+            switch (mState) {
+                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+                    queryPhysicalAddress(address);
+                    return;
+                case STATE_WAITING_FOR_OSD_NAME:
+                    queryOsdName(address);
+                    return;
+                case STATE_WAITING_FOR_VENDOR_ID:
+                    queryVendorId(address);
+                default:
+                    return;
+            }
+        }
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState == STATE_NONE || mState != state) {
+            return;
+        }
+
+        Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
+        removeDevice(mProcessedDeviceCount);
+        checkAndProceedStage();
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 1bc278d..0ba7773 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -133,7 +133,8 @@
 
         @Override
         public void sendTimerMessage(int state, long delayMillis) {
-            sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state), delayMillis);
+            // The third argument(0) is not used.
+            sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis);
         }
 
         @Override
@@ -164,8 +165,13 @@
         mActionTimer.sendTimerMessage(state, delayMillis);
     }
 
-    protected final boolean sendCommand(HdmiCecMessage cmd) {
-        return mService.sendCecCommand(cmd);
+    protected final void sendCommand(HdmiCecMessage cmd) {
+        mService.sendCecCommand(cmd);
+    }
+
+    protected final void sendCommand(HdmiCecMessage cmd,
+            HdmiControlService.SendMessageCallback callback) {
+        mService.sendCecCommand(cmd, callback);
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f99a01d..d0b716d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -21,15 +21,15 @@
 import android.hardware.hdmi.HdmiCecMessage;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
+import android.os.MessageQueue;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
+
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
 
 import libcore.util.EmptyArray;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -37,6 +37,9 @@
  * and pass it to CEC HAL so that it sends message to other device. For incoming
  * message it translates the message and delegates it to proper module.
  *
+ * <p>It should be careful to access member variables on IO thread because
+ * it can be accessed from system thread as well.
+ *
  * <p>It can be created only by {@link HdmiCecController#create}
  *
  * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
@@ -58,8 +61,7 @@
 
     private static final int NUM_LOGICAL_ADDRESS = 16;
 
-    // TODO: define other constants for errors.
-    private static final int ERROR_SUCCESS = 0;
+    private static final int RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION = 3;
 
     // Handler instance to process synchronous I/O (mainly send) message.
     private Handler mIoHandler;
@@ -70,22 +72,19 @@
 
     // Stores the pointer to the native implementation of the service that
     // interacts with HAL.
-    private long mNativePtr;
+    private volatile long mNativePtr;
 
     private HdmiControlService mService;
 
-    // Map-like container of all cec devices. A logical address of device is
-    // used as key of container.
-    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
-            new SparseArray<HdmiCecDeviceInfo>();
-    // Set-like container for all local devices' logical address.
-    // Key and value are same.
-    private final SparseIntArray mLocalAddresses = new SparseIntArray();
+    // Map-like container of all cec devices including local ones.
+    // A logical address of device is used as key of container.
+    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>();
+
+    // Stores the local CEC devices in the system. Device type is used for key.
+    private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
 
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController() {
-        // TODO: Consider restoring the local device addresses from persistent storage
-        //       to allocate the same addresses again if possible.
     }
 
     /**
@@ -99,53 +98,43 @@
      *         returns {@code null}.
      */
     static HdmiCecController create(HdmiControlService service) {
-        HdmiCecController handler = new HdmiCecController();
-        long nativePtr = nativeInit(handler);
+        HdmiCecController controller = new HdmiCecController();
+        long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
         if (nativePtr == 0L) {
-            handler = null;
+            controller = null;
             return null;
         }
 
-        handler.init(service, nativePtr);
-        return handler;
+        controller.init(service, nativePtr);
+        return controller;
+    }
+
+    private void init(HdmiControlService service, long nativePtr) {
+        mService = service;
+        mIoHandler = new Handler(service.getServiceLooper());
+        mControlHandler = new Handler(service.getServiceLooper());
+        mNativePtr = nativePtr;
     }
 
     /**
-     * Initialize {@link #mLocalAddresses} by allocating logical addresses for each hosted type.
+     * Perform initialization for each hosted device.
      *
-     * @param deviceTypes local device types
+     * @param deviceTypes array of device types
      */
-    void initializeLocalDevices(int[] deviceTypes) {
-        for (int deviceType : deviceTypes) {
-            int preferred = getPreferredAddress(deviceType);
-            allocateLogicalAddress(deviceType, preferred, new AllocateLogicalAddressCallback() {
-                @Override
-                public void onAllocated(int deviceType, int logicalAddress) {
-                    addLogicalAddress(logicalAddress);
-                }
-            });
-        }
-    }
-
-    /**
-     * Get the preferred address for a given type.
-     *
-     * @param deviceType logical device type to get the address for
-     * @return preferred address; {@link HdmiCec#ADDR_UNREGISTERED} if not available.
-     */
-    private int getPreferredAddress(int deviceType) {
-        // Uses the data restored from persistent memory at boot up if they are available.
-        // Otherwise we return UNREGISTERED indicating there is no preferred address.
-        // Note that for address SPECIFIC_USE(14), HdmiCec.getTypeFromAddress() returns DEVICE_TV,
-        // meaning that we do not support device type video processor yet.
-        for (int i = 0; i < mLocalAddresses.size(); ++i) {
-            int address = mLocalAddresses.keyAt(i);
-            int type = HdmiCec.getTypeFromAddress(address);
-            if (type == deviceType) {
-                return address;
+    void initializeLocalDevices(int[] deviceTypes,
+            HdmiCecLocalDevice.AddressAllocationCallback callback) {
+        assertRunOnServiceThread();
+        for (int type : deviceTypes) {
+            HdmiCecLocalDevice device = HdmiCecLocalDevice.create(this, type, callback);
+            if (device == null) {
+                continue;
             }
+            // TODO: Consider restoring the local device addresses from persistent storage
+            //       to allocate the same addresses again if possible.
+            device.setPreferredAddress(HdmiCec.ADDR_UNREGISTERED);
+            mLocalDevices.put(type, device);
+            device.init();
         }
-        return HdmiCec.ADDR_UNREGISTERED;
     }
 
     /**
@@ -176,13 +165,55 @@
      *                         Otherwise, scan address will start from {@code preferredAddress}
      * @param callback callback interface to report allocated logical address to caller
      */
-    void allocateLogicalAddress(int deviceType, int preferredAddress,
-            AllocateLogicalAddressCallback callback) {
-        Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS);
-        msg.arg1 = deviceType;
-        msg.arg2 = preferredAddress;
-        msg.obj = callback;
-        mIoHandler.sendMessage(msg);
+    void allocateLogicalAddress(final int deviceType, final int preferredAddress,
+            final AllocateLogicalAddressCallback callback) {
+        assertRunOnServiceThread();
+
+        runOnIoThread(new Runnable() {
+            @Override
+            public void run() {
+                handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
+            }
+        });
+    }
+
+    private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
+            final AllocateLogicalAddressCallback callback) {
+        assertRunOnIoThread();
+        int startAddress = preferredAddress;
+        // If preferred address is "unregistered", start address will be the smallest
+        // address matched with the given device type.
+        if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
+            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+                if (deviceType == HdmiCec.getTypeFromAddress(i)) {
+                    startAddress = i;
+                    break;
+                }
+            }
+        }
+
+        int logicalAddress = HdmiCec.ADDR_UNREGISTERED;
+        // Iterates all possible addresses which has the same device type.
+        for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+            int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
+            if (curAddress != HdmiCec.ADDR_UNREGISTERED
+                    && deviceType == HdmiCec.getTypeFromAddress(curAddress)) {
+                if (!sendPollMessage(curAddress, RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) {
+                    logicalAddress = curAddress;
+                    break;
+                }
+            }
+        }
+
+        final int assignedAddress = logicalAddress;
+        if (callback != null) {
+            runOnServiceThread(new Runnable() {
+                    @Override
+                public void run() {
+                    callback.onAllocated(deviceType, assignedAddress);
+                }
+            });
+        }
     }
 
     private static byte[] buildBody(int opcode, byte[] params) {
@@ -192,101 +223,6 @@
         return body;
     }
 
-    private final class IoHandler extends Handler {
-        private IoHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SEND_CEC_COMMAND:
-                    HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
-                    byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
-                    nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
-                            cecMessage.getDestination(), body);
-                    break;
-                case MSG_ALLOCATE_LOGICAL_ADDRESS:
-                    int deviceType = msg.arg1;
-                    int preferredAddress = msg.arg2;
-                    AllocateLogicalAddressCallback callback =
-                            (AllocateLogicalAddressCallback) msg.obj;
-                    handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
-                    break;
-                default:
-                    Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
-                    break;
-            }
-        }
-
-        private void handleAllocateLogicalAddress(int deviceType, int preferredAddress,
-                AllocateLogicalAddressCallback callback) {
-            int startAddress = preferredAddress;
-            // If preferred address is "unregistered", start_index will be the smallest
-            // address matched with the given device type.
-            if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
-                for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
-                    if (deviceType == HdmiCec.getTypeFromAddress(i)) {
-                        startAddress = i;
-                        break;
-                    }
-                }
-            }
-
-            int logcialAddress = HdmiCec.ADDR_UNREGISTERED;
-            // Iterates all possible addresses which has the same device type.
-            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
-                int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
-                if (curAddress != HdmiCec.ADDR_UNREGISTERED
-                        && deviceType == HdmiCec.getTypeFromAddress(i)) {
-                    // <Polling Message> is a message which has empty body and
-                    // uses same address for both source and destination address.
-                    // If sending <Polling Message> failed (NAK), it becomes
-                    // new logical address for the device because no device uses
-                    // it as logical address of the device.
-                    int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
-                            EMPTY_BODY);
-                    if (error != ERROR_SUCCESS) {
-                        logcialAddress = curAddress;
-                        break;
-                    }
-                }
-            }
-
-            Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS);
-            msg.arg1 = deviceType;
-            msg.arg2 = logcialAddress;
-            msg.obj = callback;
-            mControlHandler.sendMessage(msg);
-        }
-    }
-
-    private final class ControlHandler extends Handler {
-        private ControlHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_RECEIVE_CEC_COMMAND:
-                    // TODO: delegate it to HdmiControl service.
-                    onReceiveCommand((HdmiCecMessage) msg.obj);
-                    break;
-                case MSG_REPORT_LOGICAL_ADDRESS:
-                    int deviceType = msg.arg1;
-                    int logicalAddress = msg.arg2;
-                    AllocateLogicalAddressCallback callback =
-                            (AllocateLogicalAddressCallback) msg.obj;
-                    callback.onAllocated(deviceType, logicalAddress);
-                    break;
-                default:
-                    Slog.i(TAG, "Unsupported message type:" + msg.what);
-                    break;
-            }
-        }
-    }
-
     /**
      * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
      * logical address as new device info's.
@@ -298,6 +234,7 @@
      *         that has the same logical address as new one has.
      */
     HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
+        assertRunOnServiceThread();
         HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
         if (oldDeviceInfo != null) {
             removeDeviceInfo(deviceInfo.getLogicalAddress());
@@ -316,6 +253,7 @@
      * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
      */
     HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
+        assertRunOnServiceThread();
         HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
         if (deviceInfo != null) {
             mDeviceInfos.remove(logicalAddress);
@@ -324,17 +262,23 @@
     }
 
     /**
-     * Return a list of all {@HdmiCecDeviceInfo}.
+     * Clear all device info.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     */
+    void clearDeviceInfoList() {
+        assertRunOnServiceThread();
+        mDeviceInfos.clear();
+    }
+
+    /**
+     * Return a list of all {@link HdmiCecDeviceInfo}.
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
     List<HdmiCecDeviceInfo> getDeviceInfoList() {
-        List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>(
-                mDeviceInfos.size());
-        for (int i = 0; i < mDeviceInfos.size(); ++i) {
-            deviceInfoList.add(mDeviceInfos.valueAt(i));
-        }
-        return deviceInfoList;
+        assertRunOnServiceThread();
+        return sparseArrayToList(mDeviceInfos);
     }
 
     /**
@@ -347,10 +291,22 @@
      *         Returns null if no logical address matched
      */
     HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
+        assertRunOnServiceThread();
         return mDeviceInfos.get(logicalAddress);
     }
 
     /**
+     * Return the locally hosted logical device of a given type.
+     *
+     * @param deviceType logical device type
+     * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
+     *          otherwise null.
+     */
+    HdmiCecLocalDevice getLocalDevice(int deviceType) {
+        return mLocalDevices.get(deviceType);
+    }
+
+    /**
      * Add a new logical address to the device. Device's HW should be notified
      * when a new logical address is assigned to a device, so that it can accept
      * a command having available destinations.
@@ -361,8 +317,8 @@
      * @return 0 on success. Otherwise, returns negative value
      */
     int addLogicalAddress(int newLogicalAddress) {
+        assertRunOnServiceThread();
         if (HdmiCec.isValidAddress(newLogicalAddress)) {
-            mLocalAddresses.put(newLogicalAddress, newLogicalAddress);
             return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
         } else {
             return -1;
@@ -375,9 +331,12 @@
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
     void clearLogicalAddress() {
+        assertRunOnServiceThread();
         // TODO: consider to backup logical address so that new logical address
         // allocation can use it as preferred address.
-        mLocalAddresses.clear();
+        for (int i = 0; i < mLocalDevices.size(); ++i) {
+            mLocalDevices.valueAt(i).clearAddress();
+        }
         nativeClearLogicalAddress(mNativePtr);
     }
 
@@ -390,6 +349,7 @@
      *         is between 0x0000 and 0xFFFF. If failed it returns -1
      */
     int getPhysicalAddress() {
+        assertRunOnServiceThread();
         return nativeGetPhysicalAddress(mNativePtr);
     }
 
@@ -399,6 +359,7 @@
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
     int getVersion() {
+        assertRunOnServiceThread();
         return nativeGetVersion(mNativePtr);
     }
 
@@ -408,57 +369,180 @@
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
     int getVendorId() {
+        assertRunOnServiceThread();
         return nativeGetVendorId(mNativePtr);
     }
 
-    private void init(HdmiControlService service, long nativePtr) {
-        mService = service;
-        mIoHandler = new IoHandler(service.getServiceLooper());
-        mControlHandler = new ControlHandler(service.getServiceLooper());
-        mNativePtr = nativePtr;
+    /**
+     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
+     * devices.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     *
+     * @param callback an interface used to get a list of all remote devices' address
+     * @param retryCount the number of retry used to send polling message to remote devices
+     */
+    void pollDevices(DevicePollingCallback callback, int retryCount) {
+        assertRunOnServiceThread();
+        // Extract polling candidates. No need to poll against local devices.
+        ArrayList<Integer> pollingCandidates = new ArrayList<>();
+        for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) {
+            if (!isAllocatedLocalDeviceAddress(i)) {
+                pollingCandidates.add(i);
+            }
+        }
+
+        runDevicePolling(pollingCandidates, retryCount, callback);
+    }
+
+    /**
+     * Return a list of all {@link HdmiCecLocalDevice}s.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     */
+    List<HdmiCecLocalDevice> getLocalDeviceList() {
+        assertRunOnServiceThread();
+        return sparseArrayToList(mLocalDevices);
+    }
+
+    private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
+        ArrayList<T> list = new ArrayList<>();
+        for (int i = 0; i < array.size(); ++i) {
+            list.add(array.valueAt(i));
+        }
+        return list;
+    }
+
+    private boolean isAllocatedLocalDeviceAddress(int address) {
+        for (int i = 0; i < mLocalDevices.size(); ++i) {
+            if (mLocalDevices.valueAt(i).isAddressOf(address)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void runDevicePolling(final List<Integer> candidates, final int retryCount,
+            final DevicePollingCallback callback) {
+        assertRunOnServiceThread();
+        runOnIoThread(new Runnable() {
+            @Override
+            public void run() {
+                final ArrayList<Integer> allocated = new ArrayList<>();
+                for (Integer address : candidates) {
+                    if (sendPollMessage(address, retryCount)) {
+                        allocated.add(address);
+                    }
+                }
+                if (callback != null) {
+                    runOnServiceThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onPollingFinished(allocated);
+                        }
+                    });
+                }
+            }
+        });
+    }
+
+    private boolean sendPollMessage(int address, int retryCount) {
+        assertRunOnIoThread();
+        for (int i = 0; i < retryCount; ++i) {
+            // <Polling Message> is a message which has empty body and
+            // uses same address for both source and destination address.
+            // If sending <Polling Message> failed (NAK), it becomes
+            // new logical address for the device because no device uses
+            // it as logical address of the device.
+            if (nativeSendCecCommand(mNativePtr, address, address, EMPTY_BODY)
+                    == HdmiControlService.SEND_RESULT_SUCCESS) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void assertRunOnIoThread() {
+        if (Looper.myLooper() != mIoHandler.getLooper()) {
+            throw new IllegalStateException("Should run on io thread.");
+        }
+    }
+
+    private void assertRunOnServiceThread() {
+        if (Looper.myLooper() != mControlHandler.getLooper()) {
+            throw new IllegalStateException("Should run on service thread.");
+        }
+    }
+
+    // Run a Runnable on IO thread.
+    // It should be careful to access member variables on IO thread because
+    // it can be accessed from system thread as well.
+    private void runOnIoThread(Runnable runnable) {
+        mIoHandler.post(runnable);
+    }
+
+    private void runOnServiceThread(Runnable runnable) {
+        mControlHandler.post(runnable);
     }
 
     private boolean isAcceptableAddress(int address) {
-        // Can access command targeting devices available in local device or
-        // broadcast command.
-        return address == HdmiCec.ADDR_BROADCAST
-                || mLocalAddresses.indexOfKey(address) < 0;
+        // Can access command targeting devices available in local device or broadcast command.
+        if (address == HdmiCec.ADDR_BROADCAST) {
+            return true;
+        }
+        return isAllocatedLocalDeviceAddress(address);
     }
 
     private void onReceiveCommand(HdmiCecMessage message) {
-        if (isAcceptableAddress(message.getDestination()) &&
-                mService.handleCecCommand(message)) {
+        assertRunOnServiceThread();
+        if (isAcceptableAddress(message.getDestination())
+                && mService.handleCecCommand(message)) {
             return;
         }
 
-        // TODO: Use device's source address for broadcast message.
-        int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
-                message.getDestination() : 0;
-        // Reply <Feature Abort> to initiator (source) for all requests.
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
-                (sourceAddress, message.getSource(), message.getOpcode(),
-                        HdmiCecMessageBuilder.ABORT_REFUSED);
-        sendCommand(cecMessage);
-
+        if (message.getDestination() != HdmiCec.ADDR_BROADCAST) {
+            int sourceAddress = message.getDestination();
+            // Reply <Feature Abort> to initiator (source) for all requests.
+            HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                    sourceAddress, message.getSource(), message.getOpcode(),
+                    HdmiCecMessageBuilder.ABORT_REFUSED);
+            sendCommand(cecMessage, null);
+        }
     }
 
-    boolean sendCommand(HdmiCecMessage cecMessage) {
-        Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
-        return mIoHandler.sendMessage(message);
+    void sendCommand(HdmiCecMessage cecMessage) {
+        sendCommand(cecMessage, null);
+    }
+
+    void sendCommand(final HdmiCecMessage cecMessage,
+            final HdmiControlService.SendMessageCallback callback) {
+        runOnIoThread(new Runnable() {
+            @Override
+            public void run() {
+                byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
+                final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
+                        cecMessage.getDestination(), body);
+                if (error != HdmiControlService.SEND_RESULT_SUCCESS) {
+                    Slog.w(TAG, "Failed to send " + cecMessage);
+                }
+                if (callback != null) {
+                    runOnServiceThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onSendCompleted(error);
+                        }
+                    });
+                }
+            }
+        });
     }
 
     /**
      * Called by native when incoming CEC message arrived.
      */
     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
-        byte opcode = body[0];
-        byte params[] = Arrays.copyOfRange(body, 1, body.length);
-        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
-
-        // Delegate message to main handler so that it handles in main thread.
-        Message message = mControlHandler.obtainMessage(
-                MSG_RECEIVE_CEC_COMMAND, cecMessage);
-        mControlHandler.sendMessage(message);
+        assertRunOnServiceThread();
+        onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
     }
 
     /**
@@ -470,7 +554,7 @@
         mService.onHotplug(0, connected);
     }
 
-    private static native long nativeInit(HdmiCecController handler);
+    private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
     private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
             int dstAddress, byte[] body);
     private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
new file mode 100644
index 0000000..aac2a15
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.hdmi;
+
+import com.android.server.hdmi.HdmiCecController.AllocateLogicalAddressCallback;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+
+/**
+ * Class that models a logical CEC device hosted in this system. Handles initialization,
+ * CEC commands that call for actions customized per device type.
+ */
+abstract class HdmiCecLocalDevice {
+
+    protected final HdmiCecController mController;
+    protected final int mDeviceType;
+    protected final AddressAllocationCallback mAllocationCallback;
+    protected int mAddress;
+    protected int mPreferredAddress;
+    protected HdmiCecDeviceInfo mDeviceInfo;
+
+    /**
+     * Callback interface to notify newly allocated logical address of the given
+     * local device.
+     */
+    interface AddressAllocationCallback {
+        /**
+         * Called when a logical address of the given device is allocated.
+         *
+         * @param deviceType original device type
+         * @param logicalAddress newly allocated logical address
+         */
+        void onAddressAllocated(int deviceType, int logicalAddress);
+    }
+
+    protected HdmiCecLocalDevice(HdmiCecController controller, int deviceType,
+            AddressAllocationCallback callback) {
+        mController = controller;
+        mDeviceType = deviceType;
+        mAllocationCallback = callback;
+        mAddress = HdmiCec.ADDR_UNREGISTERED;
+    }
+
+    // Factory method that returns HdmiCecLocalDevice of corresponding type.
+    static HdmiCecLocalDevice create(HdmiCecController controller, int deviceType,
+            AddressAllocationCallback callback) {
+        switch (deviceType) {
+        case HdmiCec.DEVICE_TV:
+            return new HdmiCecLocalDeviceTv(controller, callback);
+        case HdmiCec.DEVICE_PLAYBACK:
+            return new HdmiCecLocalDevicePlayback(controller, callback);
+        default:
+            return null;
+        }
+    }
+
+    abstract void init();
+
+    /**
+     * Called when a logical address of the local device is allocated.
+     * Note that internal variables are updated before it's called.
+     */
+    protected abstract void onAddressAllocated(int logicalAddress);
+
+    protected void allocateAddress(int type) {
+        mController.allocateLogicalAddress(type, mPreferredAddress,
+                new AllocateLogicalAddressCallback() {
+            @Override
+            public void onAllocated(int deviceType, int logicalAddress) {
+                mAddress = mPreferredAddress = logicalAddress;
+
+                // Create and set device info.
+                HdmiCecDeviceInfo deviceInfo = createDeviceInfo(mAddress, deviceType);
+                setDeviceInfo(deviceInfo);
+                mController.addDeviceInfo(deviceInfo);
+
+                mController.addLogicalAddress(logicalAddress);
+                onAddressAllocated(logicalAddress);
+                if (mAllocationCallback != null) {
+                    mAllocationCallback.onAddressAllocated(deviceType, logicalAddress);
+                }
+            }
+        });
+    }
+
+    private final HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
+        int vendorId = mController.getVendorId();
+        int physicalAddress = mController.getPhysicalAddress();
+        // TODO: get device name read from system configuration.
+        String displayName = HdmiCec.getDefaultDeviceName(logicalAddress);
+        return new HdmiCecDeviceInfo(logicalAddress,
+                physicalAddress, deviceType, vendorId, displayName);
+    }
+
+    HdmiCecDeviceInfo getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    void setDeviceInfo(HdmiCecDeviceInfo info) {
+        mDeviceInfo = info;
+    }
+
+    // Returns true if the logical address is same as the argument.
+    boolean isAddressOf(int addr) {
+        return addr == mAddress;
+    }
+
+    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
+    void clearAddress() {
+        mAddress = HdmiCec.ADDR_UNREGISTERED;
+    }
+
+    void setPreferredAddress(int addr) {
+        mPreferredAddress = addr;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
new file mode 100644
index 0000000..3347725
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+
+/**
+ * Represent a logical device of type Playback residing in Android system.
+ */
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+
+    HdmiCecLocalDevicePlayback(HdmiCecController controller, AddressAllocationCallback callback) {
+        super(controller, HdmiCec.DEVICE_PLAYBACK, callback);
+    }
+
+    @Override
+    void init() {
+        allocateAddress(mDeviceType);
+    }
+
+    @Override
+    protected void onAddressAllocated(int logicalAddress) {
+        mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, mController.getPhysicalAddress(), mDeviceType));
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
new file mode 100644
index 0000000..93761ab
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+
+/**
+ * Represent a logical device of type TV residing in Android system.
+ */
+final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
+
+    HdmiCecLocalDeviceTv(HdmiCecController controller, AddressAllocationCallback callback) {
+        super(controller, HdmiCec.DEVICE_TV, callback);
+    }
+
+    @Override
+    void init() {
+        allocateAddress(mDeviceType);
+    }
+
+    @Override
+    protected void onAddressAllocated(int logicalAddress) {
+        // TODO: vendor-specific initialization here.
+
+        mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, mController.getPhysicalAddress(), mDeviceType));
+        mController.sendCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+                mAddress, mController.getVendorId()));
+
+        // TODO: Start routing control action, device discovery action.
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index be270b9..9a76734 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -20,6 +20,7 @@
 import android.hardware.hdmi.HdmiCecMessage;
 
 import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
 
 /**
  * A helper class to build {@link HdmiCecMessage} from various cec commands.
@@ -39,6 +40,20 @@
     private HdmiCecMessageBuilder() {}
 
     /**
+     * Build {@link HdmiCecMessage} from raw data.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param body body of message. It includes opcode.
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage of(int src, int dest, byte[] body) {
+        byte opcode = body[0];
+        byte params[] = Arrays.copyOfRange(body, 1, body.length);
+        return new HdmiCecMessage(src, dest, opcode, params);
+    }
+
+    /**
      * Build &lt;Feature Abort&gt; command. &lt;Feature Abort&gt; consists of
      * 1 byte original opcode and 1 byte reason fields with basic fields.
      *
@@ -58,6 +73,17 @@
     }
 
     /**
+     * Build &lt;Give Physical Address&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) {
+        return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS);
+    }
+
+    /**
      * Build &lt;Give Osd Name&gt; command.
      *
      * @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecService.java b/services/core/java/com/android/server/hdmi/HdmiCecService.java
index 0a4c719..98dc72f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecService.java
@@ -75,13 +75,8 @@
 
     @Override
     public void onStart() {
-        mNativePtr = nativeInit(this);
-        if (mNativePtr != 0) {
-            // TODO: Consider using a dedicated, configurable identifier for OSD name, maybe from
-            //       Settings. It should be ASCII only, not a very long one (limited to 15 chars).
-            setOsdNameLocked(Build.MODEL);
-            publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService());
-        }
+        // Stop publishing the service. Soon to be deprecated.
+        Log.w(TAG, "In transition to HdmiControlService. May not work.");
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 09153b9..d775733 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,27 +18,26 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.hdmi.IHdmiControlService;
-import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.HdmiCec;
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.util.SparseArray;
-
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.SystemService;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
+import com.android.server.hdmi.HdmiCecLocalDevice.AddressAllocationCallback;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -54,6 +53,37 @@
     // TODO: Rename the permission to HDMI_CONTROL.
     private static final String PERMISSION = "android.permission.HDMI_CEC";
 
+    static final int SEND_RESULT_SUCCESS = 0;
+    static final int SEND_RESULT_NAK = -1;
+    static final int SEND_RESULT_FAILURE = -2;
+
+    /**
+     * Interface to report send result.
+     */
+    interface SendMessageCallback {
+        /**
+         * Called when {@link HdmiControlService#sendCecCommand} is completed.
+         *
+         * @param error result of send request.
+         * @see {@link #SEND_RESULT_SUCCESS}
+         * @see {@link #SEND_RESULT_NAK}
+         * @see {@link #SEND_RESULT_FAILURE}
+         */
+        void onSendCompleted(int error);
+    }
+
+    /**
+     * Interface to get a list of available logical devices.
+     */
+    interface DevicePollingCallback {
+        /**
+         * Called when device polling is finished.
+         *
+         * @param ackedAddress a list of logical addresses of available devices
+         */
+        void onPollingFinished(List<Integer> ackedAddress);
+    }
+
     // A thread to handle synchronous IO of CEC and MHL control service.
     // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
     // and sparse call it shares a thread to handle IO operations.
@@ -102,7 +132,22 @@
         mIoThread.start();
         mCecController = HdmiCecController.create(this);
         if (mCecController != null) {
-            mCecController.initializeLocalDevices(mLocalDevices);
+            mCecController.initializeLocalDevices(mLocalDevices, new AddressAllocationCallback() {
+                private final SparseIntArray mAllocated = new SparseIntArray();
+
+                @Override
+                public void onAddressAllocated(int deviceType, int logicalAddress) {
+                    mAllocated.append(deviceType, logicalAddress);
+                    // TODO: get HdmiLCecLocalDevice and call onAddressAllocated here.
+
+                    // Once all logical allocation is done, launch device discovery
+                    // action if one of local device is TV.
+                    int tvAddress = mAllocated.get(HdmiCec.DEVICE_TV, -1);
+                    if (mLocalDevices.length == mAllocated.size() && tvAddress != -1) {
+                        launchDeviceDiscovery(tvAddress);
+                    }
+                }
+            });
         } else {
             Slog.i(TAG, "Device does not support HDMI-CEC.");
         }
@@ -112,8 +157,7 @@
             Slog.i(TAG, "Device does not support MHL-control.");
         }
 
-        // TODO: Publish the BinderService
-        // publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
+        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
     }
 
     /**
@@ -151,6 +195,16 @@
         });
     }
 
+    // See if we have an action of a given type in progress.
+    private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
+        for (FeatureAction action : mActions) {
+            if (action.getClass().equals(clazz)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Remove the given {@link FeatureAction} object from the action queue.
      *
@@ -205,10 +259,14 @@
      * Transmit a CEC command to CEC bus.
      *
      * @param command CEC command to send out
-     * @return {@code true} if succeeds to send command
+     * @param callback interface used to the result of send command
      */
-    boolean sendCecCommand(HdmiCecMessage command) {
-        return mCecController.sendCommand(command);
+    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+        mCecController.sendCommand(command, callback);
+    }
+
+    void sendCecCommand(HdmiCecMessage command) {
+        mCecController.sendCommand(command, null);
     }
 
     /**
@@ -245,24 +303,53 @@
             case HdmiCec.MESSAGE_TERMINATE_ARC:
                 handleTerminateArc(message);
                 return true;
+            case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+                handleReportPhysicalAddress(message);
+                return true;
             // TODO: Add remaining system information query such as
             // <Give Device Power Status> and <Request Active Source> handler.
             default:
-                Slog.w(TAG, "Unsupported cec command:" + message.toString());
-                return false;
+                return dispatchMessageToAction(message);
         }
     }
 
     /**
      * Called when a new hotplug event is issued.
      *
-     * @param port hdmi port number where hot plug event issued.
+     * @param portNo hdmi port number where hot plug event issued.
      * @param connected whether to be plugged in or not
      */
     void onHotplug(int portNo, boolean connected) {
         // TODO: Start "RequestArcInitiationAction" if ARC port.
     }
 
+    /**
+     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
+     * devices.
+     *
+     * @param callback an interface used to get a list of all remote devices' address
+     * @param retryCount the number of retry used to send polling message to remote devices
+     */
+    void pollDevices(DevicePollingCallback callback, int retryCount) {
+        mCecController.pollDevices(callback, retryCount);
+    }
+
+    private void handleReportPhysicalAddress(HdmiCecMessage message) {
+        // At first, try to consume it.
+        if (dispatchMessageToAction(message)) {
+            return;
+        }
+
+        // Ignore if [Device Discovery Action] is on going ignore message.
+        if (hasAction(DeviceDiscoveryAction.class)) {
+            Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
+                    + "because Device Discovery Action is on-going:" + message);
+            return;
+        }
+
+        // TODO: start new device action.
+    }
+
     private void handleInitiateArc(HdmiCecMessage message){
         // In case where <Initiate Arc> is started by <Request ARC Initiation>
         // need to clean up RequestArcInitiationAction.
@@ -341,6 +428,16 @@
         }
     }
 
+    private boolean dispatchMessageToAction(HdmiCecMessage message) {
+        for (FeatureAction action : mActions) {
+            if (action.processCommand(message)) {
+                return true;
+            }
+        }
+        Slog.w(TAG, "Unsupported cec command:" + message);
+        return false;
+    }
+
     // Record class that monitors the event of the caller of being killed. Used to clean up
     // the listener list and record list accordingly.
     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
@@ -359,6 +456,38 @@
         }
     }
 
+    void addCecDevice(HdmiCecDeviceInfo info) {
+        mCecController.addDeviceInfo(info);
+    }
+
+    // Launch device discovery sequence.
+    // It starts with clearing the existing device info list.
+    // Note that it assumes that logical address of all local devices is already allocated.
+    private void launchDeviceDiscovery(int sourceAddress) {
+        // At first, clear all existing device infos.
+        mCecController.clearDeviceInfoList();
+
+        // TODO: check whether TV is one of local devices.
+        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
+                new DeviceDiscoveryCallback() {
+                    @Override
+                    public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
+                        for (HdmiCecDeviceInfo info : deviceInfos) {
+                            mCecController.addDeviceInfo(info);
+                        }
+
+                        // Add device info of all local devices.
+                        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+                            mCecController.addDeviceInfo(device.getDeviceInfo());
+                        }
+
+                        // TODO: start hot-plug detection sequence here.
+                        // addAndStartAction(new HotplugDetectionAction());
+                    }
+                });
+        addAndStartAction(action);
+    }
+
     private void enforceAccessPermission() {
         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
     }
@@ -373,36 +502,95 @@
         }
 
         @Override
-        public void oneTouchPlay(IHdmiControlCallback callback) {
+        public void oneTouchPlay(final IHdmiControlCallback callback) {
             enforceAccessPermission();
-            // TODO: Post a message for HdmiControlService#oneTouchPlay()
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.oneTouchPlay(callback);
+                }
+            });
         }
 
         @Override
-        public void queryDisplayStatus(IHdmiControlCallback callback) {
+        public void queryDisplayStatus(final IHdmiControlCallback callback) {
             enforceAccessPermission();
-            // TODO: Post a message for HdmiControlService#queryDisplayStatus()
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.queryDisplayStatus(callback);
+                }
+            });
         }
 
         @Override
-        public void addHotplugEventListener(IHdmiHotplugEventListener listener) {
+        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
             enforceAccessPermission();
-            // TODO: Post a message for HdmiControlService#addHotplugEventListener()
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.addHotplugEventListener(listener);
+                }
+            });
         }
 
         @Override
-        public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
+        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
             enforceAccessPermission();
-            // TODO: Post a message for HdmiControlService#removeHotplugEventListener()
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.removeHotplugEventListener(listener);
+                }
+            });
         }
     }
 
     private void oneTouchPlay(IHdmiControlCallback callback) {
-        // TODO: Create a new action
+        if (hasAction(OneTouchPlayAction.class)) {
+            Slog.w(TAG, "oneTouchPlay already in progress");
+            invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
+            return;
+        }
+        HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+        if (source == null) {
+            Slog.w(TAG, "Local playback device not available");
+            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+            return;
+        }
+        // TODO: Consider the case of multiple TV sets. For now we always direct the command
+        //       to the primary one.
+        OneTouchPlayAction action = OneTouchPlayAction.create(this,
+                source.getDeviceInfo().getLogicalAddress(),
+                source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate oneTouchPlay");
+            invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
     }
 
     private void queryDisplayStatus(IHdmiControlCallback callback) {
-        // TODO: Create a new action
+        if (hasAction(DevicePowerStatusAction.class)) {
+            Slog.w(TAG, "queryDisplayStatus already in progress");
+            invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
+            return;
+        }
+        HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+        if (source == null) {
+            Slog.w(TAG, "Local playback device not available");
+            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+            return;
+        }
+        DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
+                source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate queryDisplayStatus");
+            invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
     }
 
     private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
@@ -431,4 +619,12 @@
             mHotplugEventListeners.remove(listener);
         }
     }
+
+    private void invokeCallback(IHdmiControlCallback callback, int result) {
+        try {
+            callback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Invoking callback failed:" + e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index 0a701f9..343aff7 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -16,7 +16,7 @@
 
 package com.android.server.hdmi;
 
-import android.util.Slog;
+import android.hardware.hdmi.HdmiCecMessage;
 
 /**
  * Feature action that handles ARC action initiated by TV devices.
@@ -37,17 +37,22 @@
 
     @Override
     boolean start() {
-        if (sendCommand(
-                HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress, mAvrAddress))) {
-            mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
-            addTimer(mState, TIMEOUT_MS);
-        } else {
-            Slog.w(TAG, "Failed to send <Request ARC Initiation>");
-            // If failed to send <Request ARC Initiation>, start "Disabled" ARC transmission
-            // action.
-            disableArcTransmission();
-            finish();
-        }
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress,
+                mAvrAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    // If failed to send <Request ARC Initiation>, start "Disabled"
+                    // ARC transmission action.
+                    disableArcTransmission();
+                    finish();
+                }
+            }
+        });
         return true;
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
index db1b992..d4a35f8 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
@@ -16,7 +16,7 @@
 
 package com.android.server.hdmi;
 
-import android.util.Slog;
+import android.hardware.hdmi.HdmiCecMessage;
 
 /**
  * Feature action to handle <Request ARC Termination>.
@@ -37,17 +37,22 @@
 
     @Override
     boolean start() {
-        if (sendCommand(
-                HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress))) {
-            mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
-            addTimer(mState, TIMEOUT_MS);
-        } else {
-            Slog.w(TAG, "Failed to send <Request ARC Initiation>");
-            // If failed to send <Request ARC Termination>, start "Disabled" ARC transmission
-            // action.
-            disableArcTransmission();
-            finish();
-        }
+        HdmiCecMessage command =
+                HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    // If failed to send <Request ARC Termination>, start "Disabled" ARC
+                    // transmission action.
+                    disableArcTransmission();
+                    finish();
+                }
+            }
+        });
         return true;
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
index 6bf149b..e3525d8 100644
--- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
+++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
@@ -64,26 +64,7 @@
     @Override
     boolean start() {
         if (mEnabled) {
-            if (sendCommand(
-                    HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress))) {
-                // Enable ARC status immediately after sending <Report Arc Initiated>.
-                // If AVR responds with <Feature Abort>, disable ARC status again.
-                // This is different from spec that says that turns ARC status to "Enabled"
-                // if <Report ARC Initiated> is acknowledged and no <Feature Abort> is received.
-                // But implemented this way to save the time having to wait for <Feature Abort>.
-                setArcStatus(true);
-                // If succeeds to send <Report ARC Initiated>, wait general timeout
-                // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
-                mState = STATE_WAITING_TIMEOUT;
-                addTimer(mState, TIMEOUT_MS);
-            } else {
-                // If fails to send <Report ARC Initiated>, disable ARC and
-                // send <Report ARC Terminated> directly.
-                Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:" + mSourceAddress
-                        + ", avr Address:" + mAvrAddress + "]");
-                setArcStatus(false);
-                finish();
-            }
+            sendReportArcInitiated();
         } else {
             setArcStatus(false);
             finish();
@@ -91,6 +72,35 @@
         return true;
     }
 
+    private void sendReportArcInitiated() {
+        HdmiCecMessage command =
+                HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                    // Enable ARC status immediately after sending <Report Arc Initiated>.
+                    // If AVR responds with <Feature Abort>, disable ARC status again.
+                    // This is different from spec that says that turns ARC status to
+                    // "Enabled" if <Report ARC Initiated> is acknowledged and no
+                    // <Feature Abort> is received.
+                    // But implemented this way to save the time having to wait for
+                    // <Feature Abort>.
+                    setArcStatus(true);
+                    // If succeeds to send <Report ARC Initiated>, wait general timeout
+                    // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
+                    mState = STATE_WAITING_TIMEOUT;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    // If fails to send <Report ARC Initiated>, disable ARC and
+                    // send <Report ARC Terminated> directly.
+                    setArcStatus(false);
+                    finish();
+                }
+            }
+        });
+    }
+
     private void setArcStatus(boolean enabled) {
         boolean wasEnabled = mService.setArcStatus(enabled);
         Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index c909a54..737ffda 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -25,6 +25,7 @@
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.MediaController;
+import android.media.session.RemoteVolumeProvider;
 import android.media.session.RouteCommand;
 import android.media.session.RouteInfo;
 import android.media.session.RouteOptions;
@@ -33,6 +34,7 @@
 import android.media.session.MediaSessionInfo;
 import android.media.session.RouteInterface;
 import android.media.session.PlaybackState;
+import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.os.Bundle;
@@ -112,6 +114,14 @@
     private long mLastActiveTime;
     // End TransportPerformer fields
 
+    // Volume handling fields
+    private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
+    private int mAudioStream = AudioManager.STREAM_MUSIC;
+    private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+    private int mMaxVolume = 0;
+    private int mCurrentVolume = 0;
+    // End volume handling fields
+
     private boolean mIsActive = false;
     private boolean mDestroyed = false;
 
@@ -248,6 +258,27 @@
     }
 
     /**
+     * Send a volume adjustment to the session owner.
+     *
+     * @param delta The amount to adjust the volume by.
+     */
+    public void adjustVolumeBy(int delta) {
+        if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) {
+            // Nothing to do, the volume cannot be changed
+            return;
+        }
+        mSessionCb.adjustVolumeBy(delta);
+    }
+
+    public void setVolumeTo(int value) {
+        if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+            // Nothing to do. The volume can't be set directly.
+            return;
+        }
+        mSessionCb.setVolumeTo(value);
+    }
+
+    /**
      * Set the connection to use for the selected route and notify the app it is
      * now connected.
      *
@@ -294,14 +325,16 @@
      * Check if the session is currently performing playback. This will also
      * return true if the session was recently paused.
      *
+     * @param includeRecentlyActive True if playback that was recently paused
+     *            should count, false if it shouldn't.
      * @return True if the session is performing playback, false otherwise.
      */
-    public boolean isPlaybackActive() {
+    public boolean isPlaybackActive(boolean includeRecentlyActive) {
         int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
         if (isActiveState(state)) {
             return true;
         }
-        if (state == mPlaybackState.STATE_PAUSED) {
+        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
             long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
             if (inactiveTime < ACTIVE_BUFFER) {
                 return true;
@@ -311,6 +344,54 @@
     }
 
     /**
+     * Get the type of playback, either local or remote.
+     *
+     * @return The current type of playback.
+     */
+    public int getPlaybackType() {
+        return mPlaybackType;
+    }
+
+    /**
+     * Get the local audio stream being used. Only valid if playback type is
+     * local.
+     *
+     * @return The audio stream the session is using.
+     */
+    public int getAudioStream() {
+        return mAudioStream;
+    }
+
+    /**
+     * Get the type of volume control. Only valid if playback type is remote.
+     *
+     * @return The volume control type being used.
+     */
+    public int getVolumeControl() {
+        return mVolumeControlType;
+    }
+
+    /**
+     * Get the max volume that can be set. Only valid if playback type is
+     * remote.
+     *
+     * @return The max volume that can be set.
+     */
+    public int getMaxVolume() {
+        return mMaxVolume;
+    }
+
+    /**
+     * Get the current volume for this session. Only valid if playback type is
+     * remote.
+     *
+     * @return The current volume of the remote playback.
+     */
+    public int getCurrentVolume() {
+        return mCurrentVolume;
+    }
+
+    /**
      * @return True if this session is currently connected to a route.
      */
     public boolean isConnected() {
@@ -640,6 +721,40 @@
                 mRequests.add(request);
             }
         }
+
+        @Override
+        public void setCurrentVolume(int volume) {
+            mCurrentVolume = volume;
+        }
+
+        @Override
+        public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
+            switch(type) {
+                case MediaSession.VOLUME_TYPE_LOCAL:
+                    mPlaybackType = type;
+                    int audioStream = arg1;
+                    if (isValidStream(audioStream)) {
+                        mAudioStream = audioStream;
+                    } else {
+                        Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream");
+                        mAudioStream = AudioManager.STREAM_MUSIC;
+                    }
+                    break;
+                case MediaSession.VOLUME_TYPE_REMOTE:
+                    mPlaybackType = type;
+                    mVolumeControlType = arg1;
+                    mMaxVolume = arg2;
+                    break;
+                default:
+                    throw new IllegalArgumentException("Volume handling type " + type
+                            + " not recognized.");
+            }
+        }
+
+        private boolean isValidStream(int stream) {
+            return stream >= AudioManager.STREAM_VOICE_CALL
+                    && stream <= AudioManager.STREAM_NOTIFICATION;
+        }
     }
 
     class SessionCb {
@@ -780,6 +895,22 @@
                 Slog.e(TAG, "Remote failure in rate.", e);
             }
         }
+
+        public void adjustVolumeBy(int delta) {
+            try {
+                mCb.onAdjustVolumeBy(delta);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+            }
+        }
+
+        public void setVolumeTo(int value) {
+            try {
+                mCb.onSetVolumeTo(value);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+            }
+        }
     }
 
     class ControllerStub extends ISessionController.Stub {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9d85167..87665e1 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -26,6 +26,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.IAudioService;
 import android.media.routeprovider.RouteRequest;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -40,6 +42,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.speech.RecognizerIntent;
@@ -79,6 +82,7 @@
     private final PowerManager.WakeLock mMediaEventWakeLock;
 
     private KeyguardManager mKeyguardManager;
+    private IAudioService mAudioService;
 
     private MediaSessionRecord mPrioritySession;
     private int mCurrentUserId = -1;
@@ -105,6 +109,12 @@
         updateUser();
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
+        mAudioService = getAudioService();
+    }
+
+    private IAudioService getAudioService() {
+        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+        return IAudioService.Stub.asInterface(b);
     }
 
     /**
@@ -703,6 +713,23 @@
         }
 
         @Override
+        public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
+                throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    MediaSessionRecord session = mPriorityStack
+                            .getDefaultVolumeSession(mCurrentUserId);
+                    dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -737,6 +764,49 @@
             }
         }
 
+        private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
+                MediaSessionRecord session) {
+            int direction = 0;
+            int steps = delta;
+            if (delta > 0) {
+                direction = 1;
+            } else if (delta < 0) {
+                direction = -1;
+                steps = -delta;
+            }
+            if (DEBUG) {
+                String sessionInfo = session == null ? null : session.getSessionInfo().toString();
+                Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
+                        + ", suggestedStream=" + suggestedStream);
+
+            }
+            if (session == null) {
+                for (int i = 0; i < steps; i++) {
+                    try {
+                        mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
+                                flags, getContext().getOpPackageName());
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error adjusting default volume.", e);
+                    }
+                }
+            } else {
+                if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
+                    for (int i = 0; i < steps; i++) {
+                        try {
+                            mAudioService.adjustSuggestedStreamVolume(direction,
+                                    session.getAudioStream(), flags,
+                                    getContext().getOpPackageName());
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error adjusting volume for stream "
+                                    + session.getAudioStream(), e);
+                        }
+                    }
+                } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
+                    session.adjustVolumeBy(delta);
+                }
+            }
+        }
+
         private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
                 MediaSessionRecord session) {
             if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 56236f8..803dee2 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -52,6 +52,7 @@
 
     private MediaSessionRecord mCachedButtonReceiver;
     private MediaSessionRecord mCachedDefault;
+    private MediaSessionRecord mCachedVolumeDefault;
     private ArrayList<MediaSessionRecord> mCachedActiveList;
     private ArrayList<MediaSessionRecord> mCachedTransportControlList;
 
@@ -93,6 +94,9 @@
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache();
+        } else if (newState == PlaybackState.STATE_PAUSED) {
+            // Just clear the volume cache in this case
+            mCachedVolumeDefault = null;
         }
     }
 
@@ -177,6 +181,25 @@
         return mCachedButtonReceiver;
     }
 
+    public MediaSessionRecord getDefaultVolumeSession(int userId) {
+        if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
+            return mGlobalPrioritySession;
+        }
+        if (mCachedVolumeDefault != null) {
+            return mCachedVolumeDefault;
+        }
+        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
+        int size = records.size();
+        for (int i = 0; i < size; i++) {
+            MediaSessionRecord record = records.get(i);
+            if (record.isPlaybackActive(false)) {
+                mCachedVolumeDefault = record;
+                return record;
+            }
+        }
+        return null;
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
                 UserHandle.USER_ALL);
@@ -237,7 +260,7 @@
                 lastLocalIndex++;
                 lastActiveIndex++;
                 lastPublishedIndex++;
-            } else if (session.isPlaybackActive()) {
+            } else if (session.isPlaybackActive(true)) {
                 // TODO replace getRoute() == null with real local route check
                 if(session.getRoute() == null) {
                     // Active local sessions get top priority
@@ -284,6 +307,7 @@
 
     private void clearCache() {
         mCachedDefault = null;
+        mCachedVolumeDefault = null;
         mCachedButtonReceiver = null;
         mCachedActiveList = null;
         mCachedTransportControlList = null;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6c38a4c..e52f218 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -33,6 +33,7 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -338,6 +339,41 @@
                     channels[0].dispose();
                 }
             }
+
+            @Override
+            public void onVideoSizeChanged(int width, int height) throws RemoteException {
+                synchronized (mLock) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
+                    }
+                    if (sessionState.mSession == null || sessionState.mClient == null) {
+                        return;
+                    }
+                    try {
+                        sessionState.mClient.onVideoSizeChanged(width, height, sessionState.mSeq);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in onSessionEvent");
+                    }
+                }
+            }
+
+            @Override
+            public void onSessionEvent(String eventType, Bundle eventArgs) {
+                synchronized (mLock) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
+                    }
+                    if (sessionState.mSession == null || sessionState.mClient == null) {
+                        return;
+                    }
+                    try {
+                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
+                                sessionState.mSeq);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in onSessionEvent");
+                    }
+                }
+            }
         };
 
         // Create a session. When failed, send a null token immediately.
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 81db8b3..a354c45 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -174,14 +174,14 @@
             stackNdx = 0;
         } else {
             stackNdx = mTasks.size();
-            final int currentUserId = mService.mCurrentUserId;
-            if (task.mUserId != currentUserId) {
+            if (!mService.isCurrentProfileLocked(task.mUserId)) {
                 // Place the task below all current user tasks.
                 while (--stackNdx >= 0) {
-                    if (currentUserId != mTasks.get(stackNdx).mUserId) {
+                    if (!mService.isCurrentProfileLocked(mTasks.get(stackNdx).mUserId)) {
                         break;
                     }
                 }
+                // Put it above first non-current user task.
                 ++stackNdx;
             }
         }
@@ -352,7 +352,7 @@
         int top = mTasks.size();
         for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
             Task task = mTasks.get(taskNdx);
-            if (task.mUserId == userId) {
+            if (mService.isCurrentProfileLocked(task.mUserId)) {
                 mTasks.remove(taskNdx);
                 mTasks.add(task);
                 --top;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 6fdd535..008d2fc 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -284,7 +284,7 @@
                     } else if (mKeyguardGoingAway && !nowAnimating) {
                         // Timeout!!
                         Slog.e(TAG, "Timeout waiting for animation to startup");
-                        mPolicy.startKeyguardExitAnimation(0);
+                        mPolicy.startKeyguardExitAnimation(0, 0);
                         mKeyguardGoingAway = false;
                     }
                     if (win.isReadyForDisplay()) {
@@ -392,7 +392,9 @@
                     winAnimator.mAnimationIsEntrance = true;
                     if (startKeyguardExit) {
                         // Do one time only.
-                        mPolicy.startKeyguardExitAnimation(a.getStartOffset());
+                        mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(),
+                                a.getDuration());
+                        mKeyguardGoingAway = false;
                         startKeyguardExit = false;
                     }
                 }
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
index 27c8876..a734026 100644
--- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -21,12 +21,15 @@
 #include "JNIHelp.h"
 #include "ScopedPrimitiveArray.h"
 
-#include <string>
+#include <cstring>
 
+#include <android_os_MessageQueue.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <hardware/hdmi_cec.h>
 #include <sys/param.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
 
 namespace android {
 
@@ -37,7 +40,8 @@
 
 class HdmiCecController {
 public:
-    HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj);
+    HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj,
+            const sp<Looper>& looper);
 
     void init();
 
@@ -54,51 +58,137 @@
     // Get vendor id used for vendor command.
     uint32_t getVendorId();
 
-private:
-    // Propagate the message up to Java layer.
-    void propagateCecCommand(const cec_message_t& message);
-    void propagateHotplugEvent(const hotplug_event_t& event);
+    jobject getCallbacksObj() const {
+        return mCallbacksObj;
+    }
 
+private:
     static void onReceived(const hdmi_event_t* event, void* arg);
-    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
     hdmi_cec_device_t* mDevice;
     jobject mCallbacksObj;
+    sp<Looper> mLooper;
 };
 
-HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj) :
+// RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL
+// may keep its own lifetime, we need to copy it in order to delegate
+// it to service thread.
+class CecEventWrapper : public LightRefBase<CecEventWrapper> {
+public:
+    CecEventWrapper(const hdmi_event_t& event) {
+        // Copy message.
+        switch (event.type) {
+        case HDMI_EVENT_CEC_MESSAGE:
+            mEvent.cec.initiator = event.cec.initiator;
+            mEvent.cec.destination = event.cec.destination;
+            mEvent.cec.length = event.cec.length;
+            std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length);
+            break;
+        case HDMI_EVENT_HOT_PLUG:
+            mEvent.hotplug.connected = event.hotplug.connected;
+            mEvent.hotplug.port = event.hotplug.port;
+            break;
+        case HDMI_EVENT_TX_STATUS:
+            mEvent.tx_status.status = event.tx_status.status;
+            mEvent.tx_status.opcode = event.tx_status.opcode;
+            break;
+        default:
+            // TODO: add more type whenever new type is introduced.
+            break;
+        }
+    }
+
+    const cec_message_t& cec() const {
+        return mEvent.cec;
+    }
+
+    const hotplug_event_t& hotplug() const {
+        return mEvent.hotplug;
+    }
+
+    virtual ~CecEventWrapper() {}
+
+private:
+    hdmi_event_t mEvent;
+};
+
+// Handler class to delegate incoming message to service thread.
+class HdmiCecEventHandler : public MessageHandler {
+public:
+    HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event)
+        : mController(controller),
+          mEventWrapper(event) {
+    }
+
+    virtual ~HdmiCecEventHandler() {}
+
+    void handleMessage(const Message& message) {
+        switch (message.what) {
+        case HDMI_EVENT_CEC_MESSAGE:
+            propagateCecCommand(mEventWrapper->cec());
+            break;
+        case HDMI_EVENT_HOT_PLUG:
+            propagateHotplugEvent(mEventWrapper->hotplug());
+            break;
+        case HDMI_EVENT_TX_STATUS:
+            // TODO: propagate this to controller.
+        default:
+            // TODO: add more type whenever new type is introduced.
+            break;
+        }
+    }
+
+private:
+    // Propagate the message up to Java layer.
+    void propagateCecCommand(const cec_message_t& message) {
+        jint srcAddr = message.initiator;
+        jint dstAddr = message.destination;
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        jbyteArray body = env->NewByteArray(message.length);
+        const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
+        env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
+
+        env->CallVoidMethod(mController->getCallbacksObj(),
+                gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
+                dstAddr, body);
+        env->DeleteLocalRef(body);
+
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    }
+
+    void propagateHotplugEvent(const hotplug_event_t& event) {
+        // Note that this method should be called in service thread.
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->CallVoidMethod(mController->getCallbacksObj(),
+                gHdmiCecControllerClassInfo.handleHotplug, event.connected);
+
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    }
+
+    // static
+    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+        if (env->ExceptionCheck()) {
+            ALOGE("An exception was thrown by callback '%s'.", methodName);
+            LOGE_EX(env);
+            env->ExceptionClear();
+        }
+    }
+
+    HdmiCecController* mController;
+    sp<CecEventWrapper> mEventWrapper;
+};
+
+HdmiCecController::HdmiCecController(hdmi_cec_device_t* device,
+        jobject callbacksObj, const sp<Looper>& looper) :
     mDevice(device),
-    mCallbacksObj(callbacksObj) {
+    mCallbacksObj(callbacksObj),
+    mLooper(looper) {
 }
 
 void HdmiCecController::init() {
     mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
 }
 
-void HdmiCecController::propagateCecCommand(const cec_message_t& message) {
-    jint srcAddr = message.initiator;
-    jint dstAddr = message.destination;
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jbyteArray body = env->NewByteArray(message.length);
-    const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
-    env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
-
-    env->CallVoidMethod(mCallbacksObj,
-            gHdmiCecControllerClassInfo.handleIncomingCecCommand,
-            srcAddr, dstAddr, body);
-    env->DeleteLocalRef(body);
-
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void HdmiCecController::propagateHotplugEvent(const hotplug_event_t& event) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->CallVoidMethod(mCallbacksObj,
-            gHdmiCecControllerClassInfo.handleHotplug, event.connected);
-
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
 int HdmiCecController::sendMessage(const cec_message_t& message) {
     // TODO: propagate send_message's return value.
     return mDevice->send_message(mDevice, &message);
@@ -132,15 +222,6 @@
     return vendorId;
 }
 
-// static
-void HdmiCecController::checkAndClearExceptionFromCallback(JNIEnv* env,
-        const char* methodName) {
-    if (env->ExceptionCheck()) {
-        ALOGE("An exception was thrown by callback '%s'.", methodName);
-        LOGE_EX(env);
-        env->ExceptionClear();
-    }
-}
 
 // static
 void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
@@ -149,17 +230,9 @@
         return;
     }
 
-    switch (event->type) {
-    case HDMI_EVENT_CEC_MESSAGE:
-        controller->propagateCecCommand(event->cec);
-        break;
-    case HDMI_EVENT_HOT_PLUG:
-        controller->propagateHotplugEvent(event->hotplug);
-        break;
-    default:
-        ALOGE("Unsupported event type: %d", event->type);
-        break;
-    }
+    sp<CecEventWrapper> spEvent(new CecEventWrapper(*event));
+    sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent));
+    controller->mLooper->sendMessage(handler, event->type);
 }
 
 //------------------------------------------------------------------------------
@@ -167,31 +240,31 @@
         var = env->GetMethodID(clazz, methodName, methodDescriptor); \
         LOG_FATAL_IF(! var, "Unable to find method " methodName);
 
-static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
+        jobject messageQueueObj) {
     int err;
-    // If use same hardware module id between HdmiCecService and
-    // HdmiControlSservice it may conflict and cause abnormal state of HAL.
-    // TODO: use HDMI_CEC_HARDWARE_MODULE_ID of hdmi_cec.h for module id
-    //       once migration to HdmiControlService is done.
     hw_module_t* module;
-    err = hw_get_module("hdmi_cec_module",
+    err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
             const_cast<const hw_module_t **>(&module));
     if (err != 0) {
         ALOGE("Error acquiring hardware module: %d", err);
         return 0;
     }
+
     hw_device_t* device;
-    // TODO: use HDMI_CEC_HARDWARE_INTERFACE of hdmi_cec.h for interface name
-    //       once migration to HdmiControlService is done.
-    err = module->methods->open(module, "hdmi_cec_module_hw_if", &device);
+    err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
     if (err != 0) {
         ALOGE("Error opening hardware module: %d", err);
         return 0;
     }
 
+    sp<MessageQueue> messageQueue =
+            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
+
     HdmiCecController* controller = new HdmiCecController(
             reinterpret_cast<hdmi_cec_device*>(device),
-            env->NewGlobalRef(callbacksObj));
+            env->NewGlobalRef(callbacksObj),
+            messageQueue->getLooper());
     controller->init();
 
     GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
@@ -255,8 +328,9 @@
 
 static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
-    { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J",
-            (void *) nativeInit },
+    { "nativeInit",
+      "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
+      (void *) nativeInit },
     { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
     { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
     { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
@@ -268,7 +342,8 @@
 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
 
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
+    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods,
+            NELEM(sMethods));
     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
     return 0;
 }
diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java
index 51f10c1..d452172 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -27,6 +27,8 @@
 import com.android.internal.telecomm.ICallService;
 import com.android.internal.telecomm.ICallServiceAdapter;
 
+import java.util.List;
+
 /**
  * Base implementation of CallService which can be used to provide calls for the system
  * in-call UI. CallService is a one-way service from the framework's CallsManager to any app
@@ -59,6 +61,8 @@
     private static final int MSG_ON_AUDIO_STATE_CHANGED = 11;
     private static final int MSG_PLAY_DTMF_TONE = 12;
     private static final int MSG_STOP_DTMF_TONE = 13;
+    private static final int MSG_ADD_TO_CONFERENCE = 14;
+    private static final int MSG_SPLIT_FROM_CONFERENCE = 15;
 
     /**
      * Default Handler used to consolidate binder method calls onto a single thread.
@@ -123,6 +127,29 @@
                 case MSG_STOP_DTMF_TONE:
                     stopDtmfTone((String) msg.obj);
                     break;
+                case MSG_ADD_TO_CONFERENCE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        @SuppressWarnings("unchecked")
+                        List<String> callIds = (List<String>) args.arg2;
+                        String conferenceCallId = (String) args.arg1;
+                        addToConference(conferenceCallId, callIds);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SPLIT_FROM_CONFERENCE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String conferenceCallId = (String) args.arg1;
+                        String callId = (String) args.arg2;
+                        splitFromConference(conferenceCallId, callId);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -204,6 +231,22 @@
             args.arg2 = audioState;
             mMessageHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget();
         }
+
+        @Override
+        public void addToConference(String conferenceCallId, List<String> callsToConference) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = conferenceCallId;
+            args.arg2 = callsToConference;
+            mMessageHandler.obtainMessage(MSG_ADD_TO_CONFERENCE, args).sendToTarget();
+        }
+
+        @Override
+        public void splitFromConference(String conferenceCallId, String callId) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = conferenceCallId;
+            args.arg2 = callId;
+            mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
+        }
     }
 
     /**
@@ -359,4 +402,24 @@
      * @param audioState The new {@link CallAudioState}.
      */
     public abstract void onAudioStateChanged(String activeCallId, CallAudioState audioState);
+
+    /**
+     * Adds the specified calls to the specified conference call.
+     *
+     * @param conferenceCallId The unique ID of the conference call onto which the specified calls
+     *         should be added.
+     * @param callIds The calls to add to the conference call.
+     * @hide
+     */
+    public abstract void addToConference(String conferenceCallId, List<String> callIds);
+
+    /**
+     * Removes the specified call from the specified conference call. This is a no-op if the call
+     * is not already part of the conference call.
+     *
+     * @param conferenceCallId The conference call.
+     * @param callId The call to remove from the conference call
+     * @hide
+     */
+    public abstract void splitFromConference(String conferenceCallId, String callId);
 }
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index dafc310..7396808 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.telecomm.ICallServiceAdapter;
 
+import java.util.List;
+
 /**
  * Provides methods for ICallService implementations to interact with the system phone app.
  * TODO(santoscordon): Need final public-facing comments in this file.
@@ -169,4 +171,46 @@
         }
     }
 
+    /**
+     * Indicates that the specified call can conference with any of the specified list of calls.
+     *
+     * @param callId The unique ID of the call.
+     * @param conferenceCapableCallIds The unique IDs of the calls which can be conferenced.
+     * @hide
+     */
+    public void setCanConferenceWith(String callId, List<String> conferenceCapableCallIds) {
+        try {
+            mAdapter.setCanConferenceWith(callId, conferenceCapableCallIds);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Indicates whether or not the specified call is currently conferenced into the specified
+     * conference call.
+     *
+     * @param conferenceCallId The unique ID of the conference call.
+     * @param callId The unique ID of the call being conferenced.
+     * @hide
+     */
+    public void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced) {
+        try {
+            mAdapter.setIsConferenced(conferenceCallId, callId, isConferenced);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Indicates that the call no longer exists. Can be used with either a call or a conference
+     * call.
+     *
+     * @param callId The unique ID of the call.
+     * @hide
+     */
+    public void removeCall(String callId) {
+        try {
+            mAdapter.removeCall(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
 }
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 8d02842..aeb1c33 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -20,6 +20,8 @@
 import android.os.Bundle;
 
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -248,6 +250,39 @@
         findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
     }
 
+    /** @hide */
+    @Override
+    public final void addToConference(String conferenceCallId, List<String> callIds) {
+        Log.d(this, "addToConference %s, %s", conferenceCallId, callIds);
+
+        List<Connection> connections = new LinkedList<>();
+        for (String id : callIds) {
+            Connection connection = findConnectionForAction(id, "addToConference");
+            if (connection == NULL_CONNECTION) {
+                Log.w(this, "Connection missing in conference request %s.", id);
+                return;
+            }
+            connections.add(connection);
+        }
+
+        // TODO(santoscordon): Find an existing conference call or create a new one. Then call
+        // conferenceWith on it.
+    }
+
+    /** @hide */
+    @Override
+    public final void splitFromConference(String conferenceCallId, String callId) {
+        Log.d(this, "splitFromConference(%s, %s)", conferenceCallId, callId);
+
+        Connection connection = findConnectionForAction(callId, "splitFromConference");
+        if (connection == NULL_CONNECTION) {
+            Log.w(this, "Connection missing in conference request %s.", callId);
+            return;
+        }
+
+        // TODO(santoscordon): Find existing conference call and invoke split(connection).
+    }
+
     /**
      * Find a set of Subscriptions matching a given handle (e.g. phone number).
      *
@@ -342,4 +377,4 @@
         Log.w(this, "%s - Cannot find Connection %s", action, callId);
         return NULL_CONNECTION;
     }
-}
\ No newline at end of file
+}
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index e41d3f6..6838ede 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -196,4 +196,32 @@
         } catch (RemoteException e) {
         }
     }
+
+    /**
+     * Instructs Telecomm to conference the specified calls together.
+     *
+     * @param callId The unique ID of the call.
+     * @param callIdToConference The unique ID of the call to conference with.
+     * @hide
+     */
+    void conferenceWith(String callId, String callIdToConference) {
+        try {
+            mAdapter.conferenceWith(callId, callIdToConference);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecomm to split the specified call from any conference call with which it may be
+     * connected.
+     *
+     * @param callId The unique ID of the call.
+     * @hide
+     */
+    void splitFromConference(String callId) {
+        try {
+            mAdapter.splitFromConference(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
 }
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index c3b2ae7..346d207 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -17,13 +17,13 @@
 package android.telecomm;
 
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.DisconnectCause;
 
-import java.util.Date;
-import java.util.UUID;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Information about a call that is used between InCallService and Telecomm.
@@ -38,6 +38,26 @@
     private final GatewayInfo mGatewayInfo;
     private final CallServiceDescriptor mCurrentCallServiceDescriptor;
     private final CallServiceDescriptor mHandoffCallServiceDescriptor;
+    private final List<String> mConferenceCapableCallIds;
+    private final String mParentCallId;
+    private final List<String> mChildCallIds;
+
+    /** @hide */
+    @SuppressWarnings("unchecked")
+    public InCallCall(
+            String id,
+            CallState state,
+            int disconnectCause,
+            int capabilities,
+            long connectTimeMillis,
+            Uri handle,
+            GatewayInfo gatewayInfo,
+            CallServiceDescriptor descriptor,
+            CallServiceDescriptor handoffDescriptor) {
+        this(id, state, disconnectCause, capabilities, connectTimeMillis, handle, gatewayInfo,
+                descriptor, handoffDescriptor, Collections.EMPTY_LIST, null,
+                Collections.EMPTY_LIST);
+    }
 
     /** @hide */
     public InCallCall(
@@ -49,7 +69,10 @@
             Uri handle,
             GatewayInfo gatewayInfo,
             CallServiceDescriptor descriptor,
-            CallServiceDescriptor handoffDescriptor) {
+            CallServiceDescriptor handoffDescriptor,
+            List<String> conferenceCapableCallIds,
+            String parentCallId,
+            List<String> childCallIds) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -59,6 +82,9 @@
         mGatewayInfo = gatewayInfo;
         mCurrentCallServiceDescriptor = descriptor;
         mHandoffCallServiceDescriptor = handoffDescriptor;
+        mConferenceCapableCallIds = conferenceCapableCallIds;
+        mParentCallId = parentCallId;
+        mChildCallIds = childCallIds;
     }
 
     /** The unique ID of the call. */
@@ -112,6 +138,31 @@
         return mHandoffCallServiceDescriptor;
     }
 
+    /**
+     * The calls with which this call can conference.
+     * @hide
+     */
+    public List<String> getConferenceCapableCallIds() {
+        return mConferenceCapableCallIds;
+    }
+
+    /**
+     * The conference call to which this call is conferenced. Null if not conferenced.
+     * @hide
+     */
+    public String getParentCallId() {
+        return mParentCallId;
+    }
+
+    /**
+     * The child call-IDs if this call is a conference call. Returns an empty list if this is not
+     * a conference call or if the conference call contains no children.
+     * @hide
+     */
+    public List<String> getChildCallIds() {
+        return mChildCallIds;
+    }
+
     /** Responsible for creating InCallCall objects for deserialized Parcels. */
     public static final Parcelable.Creator<InCallCall> CREATOR =
             new Parcelable.Creator<InCallCall> () {
@@ -127,8 +178,14 @@
             GatewayInfo gatewayInfo = source.readParcelable(classLoader);
             CallServiceDescriptor descriptor = source.readParcelable(classLoader);
             CallServiceDescriptor handoffDescriptor = source.readParcelable(classLoader);
+            List<String> conferenceCapableCallIds = new ArrayList<>();
+            source.readList(conferenceCapableCallIds, classLoader);
+            String parentCallId = source.readString();
+            List<String> childCallIds = new ArrayList<>();
+            source.readList(childCallIds, classLoader);
             return new InCallCall(id, state, disconnectCause, capabilities, connectTimeMillis,
-                    handle, gatewayInfo, descriptor, handoffDescriptor);
+                    handle, gatewayInfo, descriptor, handoffDescriptor, conferenceCapableCallIds,
+                    parentCallId, childCallIds);
         }
 
         @Override
@@ -155,5 +212,8 @@
         destination.writeParcelable(mGatewayInfo, 0);
         destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
         destination.writeParcelable(mHandoffCallServiceDescriptor, 0);
+        destination.writeList(mConferenceCapableCallIds);
+        destination.writeString(mParentCallId);
+        destination.writeList(mChildCallIds);
     }
 }
diff --git a/telecomm/java/com/android/internal/telecomm/ICallService.aidl b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
index cc0641c..771a3ae 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
@@ -55,4 +55,8 @@
     void playDtmfTone(String callId, char digit);
 
     void stopDtmfTone(String callId);
+
+    void addToConference(String conferenceCallId, in List<String> callIds);
+
+    void splitFromConference(String conferenceCallId, String callId);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index 6d36494..a92b176 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -45,4 +45,10 @@
     void setOnHold(String callId);
 
     void setRequestingRingback(String callId, boolean ringing);
+
+    void setCanConferenceWith(String callId, in List<String> conferenceCapableCallIds);
+
+    void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced);
+
+    void removeCall(String callId);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index 512e898..6a27217 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -47,4 +47,8 @@
     void postDialContinue(String callId);
 
     void handoffCall(String callId);
+
+    void conferenceWith(String callId, String callIdToConference);
+
+    void splitFromConference(String callId);
 }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index c162bf28..a54936b 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -608,4 +608,9 @@
     public File[] getExternalCacheDirs() {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public File[] getExternalMediaDirs() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
index 5b0aa66..a81e063 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
@@ -118,19 +118,25 @@
                     mRadius, mToggle ? 250.0f : 150.0f));
 
             mRunningAnimations.add(new RenderNodeAnimator(
-                    mPaint, RenderNodeAnimator.PAINT_ALPHA,
-                    mToggle ? 64.0f : 255.0f));
-
-            mRunningAnimations.add(new RenderNodeAnimator(
                     mPaint, RenderNodeAnimator.PAINT_STROKE_WIDTH,
                     mToggle ? 5.0f : 60.0f));
 
-            TimeInterpolator interp = new OvershootInterpolator(3.0f);
+            mRunningAnimations.add(new RenderNodeAnimator(
+                    mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f));
+
+            // Will be "chained" to run after the above
+            mRunningAnimations.add(new RenderNodeAnimator(
+                    mPaint, RenderNodeAnimator.PAINT_ALPHA, 255.0f));
+
             for (int i = 0; i < mRunningAnimations.size(); i++) {
                 RenderNodeAnimator anim = mRunningAnimations.get(i);
-                anim.setInterpolator(interp);
                 anim.setDuration(1000);
                 anim.setTarget(this);
+                if (i == (mRunningAnimations.size() - 1)) {
+                    // "chain" test
+                    anim.setStartValue(64.0f);
+                    anim.setStartDelay(anim.getDuration());
+                }
                 anim.start();
             }
 
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index c1fa74f..d6f8118 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -82,7 +82,7 @@
         Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
         mSession = man.createSession("OneMedia");
         mSession.addCallback(mCallback);
-        mSession.addTransportControlsCallback(new TransportListener());
+        mSession.addTransportControlsCallback(new TransportCallback());
         mSession.setPlaybackState(mPlaybackState);
         mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
         mSession.setRouteOptions(mRouteOptions);
@@ -255,7 +255,7 @@
         }
     }
 
-    private class TransportListener extends MediaSession.TransportControlsCallback {
+    private class TransportCallback extends MediaSession.TransportControlsCallback {
         @Override
         public void onPlay() {
             mRenderer.onPlay();
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
index 2abf65194..4c0c67a 100644
--- a/tests/VoiceInteraction/res/layout/test_interaction.xml
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -18,6 +18,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
+    android:padding="8dp"
     >
 
     <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
@@ -29,9 +30,16 @@
         android:layout_width="match_parent"
         android:layout_height="0px"
         android:layout_weight="1"
-        android:layout_marginTop="10dp"
-        android:textSize="12sp"
+        android:layout_marginTop="16dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
         android:textColor="#ffffffff"
         />
 
+    <Button android:id="@+id/abort"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:text="@string/abortVoice"
+        />
+
 </LinearLayout>
diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
index 563fa44..142d781 100644
--- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
+++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
@@ -14,26 +14,49 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="#ffffffff"
-    android:fitsSystemWindows="true"
-    >
+    android:fitsSystemWindows="true">
 
-    <TextView android:id="@+id/text"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="32dp"
-        />
+    <FrameLayout android:layout_width="fill_parent"
+        android:layout_height="match_parent"
+        android:padding="8dp">
 
-    <Button android:id="@+id/start"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/start"
-        />
+        <LinearLayout android:id="@+id/content"
+            android:layout_width="fill_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:background="#ffffffff"
+            android:elevation="8dp"
+            >
 
-</LinearLayout>
+            <TextView android:id="@+id/text"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                />
 
+            <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+                <Button android:id="@+id/start"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/start"
+                    />
+                <Button android:id="@+id/confirm"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/confirm"
+                    />
+                <Button android:id="@+id/abort"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/abort"
+                    />
+            </LinearLayout>
 
+        </LinearLayout>
+    </FrameLayout>
+</FrameLayout>
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index 12edb31..70baa52 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -16,7 +16,10 @@
 
 <resources>
 
-    <string name="start">Start!</string>
+    <string name="start">Start</string>
+    <string name="confirm">Confirm</string>
+    <string name="abort">Abort</string>
+    <string name="abortVoice">Abort Voice</string>
 
 </resources>
 
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index a3af284..c24a088 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -33,9 +33,17 @@
     View mContentView;
     TextView mText;
     Button mStartButton;
+    Button mConfirmButton;
+    Button mAbortButton;
 
+    static final int STATE_IDLE = 0;
+    static final int STATE_LAUNCHING = 1;
+    static final int STATE_CONFIRM = 2;
+    static final int STATE_COMMAND = 3;
+    static final int STATE_ABORT_VOICE = 4;
+
+    int mState = STATE_IDLE;
     Request mPendingRequest;
-    boolean mPendingConfirm;
 
     MainInteractionSession(Context context) {
         super(context);
@@ -54,21 +62,39 @@
         mText = (TextView)mContentView.findViewById(R.id.text);
         mStartButton = (Button)mContentView.findViewById(R.id.start);
         mStartButton.setOnClickListener(this);
+        mConfirmButton = (Button)mContentView.findViewById(R.id.confirm);
+        mConfirmButton.setOnClickListener(this);
+        mAbortButton = (Button)mContentView.findViewById(R.id.abort);
+        mAbortButton.setOnClickListener(this);
+        updateState();
         return mContentView;
     }
 
+    void updateState() {
+        mStartButton.setEnabled(mState == STATE_IDLE);
+        mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND);
+        mAbortButton.setEnabled(mState == STATE_ABORT_VOICE);
+    }
+
     public void onClick(View v) {
-        if (mPendingRequest == null) {
-            mStartButton.setEnabled(false);
+        if (v == mStartButton) {
+            mState = STATE_LAUNCHING;
+            updateState();
             startVoiceActivity(mStartIntent);
-        } else {
-            if (mPendingConfirm) {
+        } else if (v == mConfirmButton) {
+            if (mState == STATE_CONFIRM) {
                 mPendingRequest.sendConfirmResult(true, null);
             } else {
                 mPendingRequest.sendCommandResult(true, null);
             }
             mPendingRequest = null;
-            mStartButton.setText("Start");
+            mState = STATE_IDLE;
+            updateState();
+        } else if (v == mAbortButton) {
+            mPendingRequest.sendAbortVoiceResult(null);
+            mPendingRequest = null;
+            mState = STATE_IDLE;
+            updateState();
         }
     }
 
@@ -78,23 +104,32 @@
     }
 
     @Override
-    public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) {
+    public void onConfirm(Caller caller, Request request, CharSequence prompt, Bundle extras) {
         Log.i(TAG, "onConfirm: prompt=" + prompt + " extras=" + extras);
         mText.setText(prompt);
-        mStartButton.setEnabled(true);
         mStartButton.setText("Confirm");
         mPendingRequest = request;
-        mPendingConfirm = true;
+        mState = STATE_CONFIRM;
+        updateState();
+    }
+
+    @Override
+    public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+        Log.i(TAG, "onAbortVoice: message=" + message + " extras=" + extras);
+        mText.setText(message);
+        mPendingRequest = request;
+        mState = STATE_ABORT_VOICE;
+        updateState();
     }
 
     @Override
     public void onCommand(Caller caller, Request request, String command, Bundle extras) {
         Log.i(TAG, "onCommand: command=" + command + " extras=" + extras);
         mText.setText("Command: " + command);
-        mStartButton.setEnabled(true);
         mStartButton.setText("Finish Command");
         mPendingRequest = request;
-        mPendingConfirm = false;
+        mState = STATE_COMMAND;
+        updateState();
     }
 
     @Override
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index a61e0da..3ae6a36 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -21,12 +21,15 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.Gravity;
+import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 
-public class TestInteractionActivity extends Activity {
+public class TestInteractionActivity extends Activity implements View.OnClickListener {
     static final String TAG = "TestInteractionActivity";
 
     VoiceInteractor mInteractor;
+    Button mAbortButton;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -39,6 +42,8 @@
         }
 
         setContentView(R.layout.test_interaction);
+        mAbortButton = (Button)findViewById(R.id.abort);
+        mAbortButton.setOnClickListener(this);
 
         // Framework should take care of these.
         getWindow().setGravity(Gravity.TOP);
@@ -69,6 +74,26 @@
     }
 
     @Override
+    public void onClick(View v) {
+        if (v == mAbortButton) {
+            VoiceInteractor.AbortVoiceRequest req = new VoiceInteractor.AbortVoiceRequest(
+                    "Dammit, we suck :(", null) {
+                @Override
+                public void onCancel() {
+                    Log.i(TAG, "Canceled!");
+                }
+
+                @Override
+                public void onAbortResult(Bundle result) {
+                    Log.i(TAG, "Abort result: result=" + result);
+                    getActivity().finish();
+                }
+            };
+            mInteractor.submitRequest(req);
+        }
+    }
+
+    @Override
     public void onDestroy() {
         super.onDestroy();
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index d31239b..5c51c63 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1466,4 +1466,10 @@
         // pass
         return new File[0];
     }
+
+    @Override
+    public File[] getExternalMediaDirs() {
+        // pass
+        return new File[0];
+    }
 }
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 58b0d61..f6d7f55 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -90,6 +90,19 @@
      */
     public final static int UNSPECIFIED = -1;
 
+    /** information element from beacon
+     * @hide
+     */
+    public static class InformationElement {
+        public int id;
+        public byte[] bytes;
+    }
+
+    /** information elements found in the beacon
+     * @hide
+     */
+    public InformationElement informationElements[];
+
     /** {@hide} */
     public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
             long tsf) {
@@ -199,6 +212,16 @@
         } else {
             dest.writeInt(0);
         }
+        if (informationElements != null) {
+            dest.writeInt(informationElements.length);
+            for (int i = 0; i < informationElements.length; i++) {
+                dest.writeInt(informationElements[i].id);
+                dest.writeInt(informationElements[i].bytes.length);
+                dest.writeByteArray(informationElements[i].bytes);
+            }
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -223,6 +246,17 @@
                 if (in.readInt() == 1) {
                     sr.passpoint = WifiPasspointInfo.CREATOR.createFromParcel(in);
                 }
+                int n = in.readInt();
+                if (n != 0) {
+                    sr.informationElements = new InformationElement[n];
+                    for (int i = 0; i < n; i++) {
+                        sr.informationElements[i] = new InformationElement();
+                        sr.informationElements[i].id = in.readInt();
+                        int len = in.readInt();
+                        sr.informationElements[i].bytes = new byte[len];
+                        in.readByteArray(sr.informationElements[i].bytes);
+                    }
+                }
                 return sr;
             }
 
@@ -230,5 +264,4 @@
                 return new ScanResult[size];
             }
         };
-
 }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 9ea7027..3b65ca8 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -40,6 +40,7 @@
  * Get an instance of this class by calling
  * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context
  * .WIFI_SCANNING_SERVICE)}.
+ * @hide
  */
 public class WifiScanner {
 
@@ -72,16 +73,14 @@
     public static final int REASON_INVALID_LISTENER = -2;
     /** Invalid request */
     public static final int REASON_INVALID_REQUEST = -3;
-    /** Request conflicts with other scans that may be going on */
-    public static final int REASON_CONFLICTING_REQUEST = -4;
 
     /**
      * Generic action callback invocation interface
      *  @hide
      */
     public static interface ActionListener {
-        public void onSuccess(Object result);
-        public void onFailure(int reason, Object exception);
+        public void onSuccess();
+        public void onFailure(int reason, String description);
     }
 
     /**
@@ -193,58 +192,6 @@
 
     }
 
-    /** information element from beacon */
-    public static class InformationElement {
-        public int id;
-        public byte[] bytes;
-    }
-
-    /** scan result with information elements from beacons */
-    public static class FullScanResult implements Parcelable {
-        public ScanResult result;
-        public InformationElement informationElements[];
-
-        /** Implement the Parcelable interface {@hide} */
-        public int describeContents() {
-            return 0;
-        }
-
-        /** Implement the Parcelable interface {@hide} */
-        public void writeToParcel(Parcel dest, int flags) {
-            result.writeToParcel(dest, flags);
-            dest.writeInt(informationElements.length);
-            for (int i = 0; i < informationElements.length; i++) {
-                dest.writeInt(informationElements[i].id);
-                dest.writeInt(informationElements[i].bytes.length);
-                dest.writeByteArray(informationElements[i].bytes);
-            }
-        }
-
-        /** Implement the Parcelable interface {@hide} */
-        public static final Creator<FullScanResult> CREATOR =
-                new Creator<FullScanResult>() {
-                    public FullScanResult createFromParcel(Parcel in) {
-                        FullScanResult result = new FullScanResult();
-                        result.result = ScanResult.CREATOR.createFromParcel(in);
-                        int n = in.readInt();
-                        result.informationElements = new InformationElement[n];
-                        for (int i = 0; i < n; i++) {
-                            result.informationElements[i] = new InformationElement();
-                            result.informationElements[i].id = in.readInt();
-                            int len = in.readInt();
-                            result.informationElements[i].bytes = new byte[len];
-                            in.readByteArray(result.informationElements[i].bytes);
-                        }
-
-                        return result;
-                    }
-
-                    public FullScanResult[] newArray(int size) {
-                        return new FullScanResult[size];
-                    }
-                };
-    }
-
     /** @hide */
     public static class ParcelableScanResults implements Parcelable {
         public ScanResult mResults[];
@@ -305,7 +252,7 @@
         /**
          * reports full scan result for each access point found in scan
          */
-        public void onFullResult(FullScanResult fullScanResult);
+        public void onFullResult(ScanResult fullScanResult);
     }
 
     /** @hide */
@@ -336,13 +283,12 @@
     }
     /**
      * retrieves currently available scan results
-     * @param flush {@code true} means flush all results
-     * @param listener specifies which scan to cancel; must be same object as passed in {@link
-     *                 #startBackgroundScan}
      */
-    public void retrieveScanResults(boolean flush, ScanListener listener) {
+    public ScanResult[] getScanResults() {
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener));
+        Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
+        ScanResult[] results = (ScanResult[]) reply.obj;
+        return results;
     }
 
     /** specifies information about an access point of interest */
@@ -490,7 +436,7 @@
     }
 
     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
-    public static interface HotlistListener extends ActionListener {
+    public static interface HotspotListener extends ActionListener {
         /** indicates that access points were found by on going scans
          * @param results list of scan results, one for each access point visible currently
          */
@@ -550,10 +496,10 @@
      * @param hotspots access points of interest
      * @param apLostThreshold number of scans needed to indicate that AP is lost
      * @param listener object provided to report events on; this object must be unique and must
-     *                 also be provided on {@link #resetHotlist}
+     *                 also be provided on {@link #stopTrackingHotspots}
      */
-    public void setHotlist(HotspotInfo[] hotspots,
-            int apLostThreshold, HotlistListener listener) {
+    public void startTrackingHotspots(HotspotInfo[] hotspots,
+            int apLostThreshold, HotspotListener listener) {
         validateChannel();
         HotlistSettings settings = new HotlistSettings();
         settings.hotspotInfos = hotspots;
@@ -562,9 +508,9 @@
 
     /**
      * remove tracking of interesting access points
-     * @param listener same object provided in {@link #setHotlist}
+     * @param listener same object provided in {@link #startTrackingHotspots}
      */
-    public void resetHotlist(HotlistListener listener) {
+    public void stopTrackingHotspots(HotspotListener listener) {
         validateChannel();
         sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
     }
@@ -769,10 +715,10 @@
             switch (msg.what) {
                     /* ActionListeners grouped together */
                 case CMD_OP_SUCCEEDED :
-                    ((ActionListener) listener).onSuccess(msg.obj);
+                    ((ActionListener) listener).onSuccess();
                     break;
                 case CMD_OP_FAILED :
-                    ((ActionListener) listener).onFailure(msg.arg1, msg.obj);
+                    ((ActionListener) listener).onFailure(msg.arg1, (String)msg.obj);
                     removeListener(msg.arg2);
                     break;
                 case CMD_SCAN_RESULT :
@@ -780,14 +726,14 @@
                             ((ParcelableScanResults) msg.obj).getResults());
                     return;
                 case CMD_FULL_SCAN_RESULT :
-                    FullScanResult result = (FullScanResult) msg.obj;
+                    ScanResult result = (ScanResult) msg.obj;
                     ((ScanListener) listener).onFullResult(result);
                     return;
                 case CMD_PERIOD_CHANGED:
                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
                     return;
                 case CMD_AP_FOUND:
-                    ((HotlistListener) listener).onFound(
+                    ((HotspotListener) listener).onFound(
                             ((ParcelableScanResults) msg.obj).getResults());
                     return;
                 case CMD_WIFI_CHANGE_DETECTED: