Merge "AVD now support path morphing."
diff --git a/Android.mk b/Android.mk
index 56e9df6..8f7779e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -175,6 +175,7 @@
 	core/java/android/nfc/INfcAdapterExtras.aidl \
 	core/java/android/nfc/INfcTag.aidl \
 	core/java/android/nfc/INfcCardEmulation.aidl \
+	core/java/android/nfc/INfcLockscreenDispatch.aidl \
 	core/java/android/os/IBatteryPropertiesListener.aidl \
 	core/java/android/os/IBatteryPropertiesRegistrar.aidl \
 	core/java/android/os/ICancellationSignal.aidl \
diff --git a/api/current.txt b/api/current.txt
index e85e1d3..a263946 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -957,6 +957,7 @@
     field public static final int ratingBarStyleIndicator = 16843280; // 0x1010210
     field public static final int ratingBarStyleSmall = 16842877; // 0x101007d
     field public static final int readPermission = 16842759; // 0x1010007
+    field public static final int relinquishTaskIdentity = 16843896; // 0x1010478
     field public static final int repeatCount = 16843199; // 0x10101bf
     field public static final int repeatMode = 16843200; // 0x10101c0
     field public static final int reqFiveWayNav = 16843314; // 0x1010232
@@ -2524,7 +2525,14 @@
 
   public static final class R.transition {
     ctor public R.transition();
+    field public static final int explode = 17760259; // 0x10f0003
+    field public static final int fade = 17760258; // 0x10f0002
+    field public static final int move = 17760257; // 0x10f0001
     field public static final int no_transition = 17760256; // 0x10f0000
+    field public static final int slide_bottom = 17760260; // 0x10f0004
+    field public static final int slide_left = 17760263; // 0x10f0007
+    field public static final int slide_right = 17760262; // 0x10f0006
+    field public static final int slide_top = 17760261; // 0x10f0005
   }
 
   public static final class R.xml {
@@ -8085,6 +8093,7 @@
     field public static final int FLAG_IMMERSIVE = 2048; // 0x800
     field public static final int FLAG_MULTIPROCESS = 1; // 0x1
     field public static final int FLAG_NO_HISTORY = 128; // 0x80
+    field public static final int FLAG_RELINQUISH_TASK_IDENTITY = 4096; // 0x1000
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10
     field public static final int LAUNCH_MULTIPLE = 0; // 0x0
@@ -12834,6 +12843,7 @@
     method public boolean isOutputSupportedFor(int);
     method public static boolean isOutputSupportedFor(java.lang.Class<T>);
     method public boolean isOutputSupportedFor(android.view.Surface);
+    field public static final long NO_MIN_FRAME_DURATION = 0L; // 0x0L
   }
 
   public final class TonemapCurve {
@@ -13795,6 +13805,7 @@
     method public deprecated void setWiredHeadsetOn(boolean);
     method public deprecated boolean shouldVibrate(int);
     method public void startBluetoothSco();
+    method public void startBluetoothScoVirtualCall();
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterMediaButtonEventReceiver(android.content.ComponentName);
@@ -14479,6 +14490,11 @@
     method public java.lang.String getDefaultUrl();
   }
 
+  public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
+    ctor public MediaDrm.MediaDrmStateException(int, java.lang.String);
+    method public int getErrorCode();
+  }
+
   public static abstract interface MediaDrm.OnEventListener {
     method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
   }
@@ -15347,6 +15363,19 @@
     ctor public UnsupportedSchemeException(java.lang.String);
   }
 
+  public abstract class VolumeProvider {
+    ctor public VolumeProvider(int, int);
+    method public final int getMaxVolume();
+    method public final int getVolumeControl();
+    method public final void notifyVolumeChanged();
+    method public void onAdjustVolumeBy(int);
+    method public abstract int onGetCurrentVolume();
+    method public void onSetVolumeTo(int);
+    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+  }
+
 }
 
 package android.media.audiofx {
@@ -15713,6 +15742,7 @@
     method public android.media.session.PlaybackState getPlaybackState();
     method public int getRatingType();
     method public android.media.session.MediaController.TransportControls getTransportControls();
+    method public android.media.session.MediaController.VolumeInfo getVolumeInfo();
     method public void removeCallback(android.media.session.MediaController.Callback);
     method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
   }
@@ -15736,6 +15766,14 @@
     method public void stop();
   }
 
+  public static final class MediaController.VolumeInfo {
+    method public int getAudioStream();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getVolumeControl();
+    method public int getVolumeType();
+  }
+
   public final class MediaSession {
     method public void addCallback(android.media.session.MediaSession.Callback);
     method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
@@ -15753,9 +15791,11 @@
     method public void setMetadata(android.media.MediaMetadata);
     method public void setPlaybackState(android.media.session.PlaybackState);
     method public void setPlaybackToLocal(int);
-    method public void setPlaybackToRemote(android.media.session.RemoteVolumeProvider);
+    method public void setPlaybackToRemote(android.media.VolumeProvider);
     field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+    field public static final int VOLUME_TYPE_LOCAL = 1; // 0x1
+    field public static final int VOLUME_TYPE_REMOTE = 2; // 0x2
   }
 
   public static abstract class MediaSession.Callback {
@@ -15826,19 +15866,6 @@
     field public static final int STATE_STOPPED = 1; // 0x1
   }
 
-  public abstract class RemoteVolumeProvider {
-    ctor public RemoteVolumeProvider(int, int);
-    method public final int getMaxVolume();
-    method public final int getVolumeControl();
-    method public final void notifyVolumeChanged();
-    method public void onAdjustVolumeBy(int);
-    method public abstract int onGetCurrentVolume();
-    method public void onSetVolumeTo(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
 }
 
 package android.media.tv {
@@ -17637,6 +17664,7 @@
     method public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
     method public boolean isNdefPushEnabled();
+    method public boolean registerLockscreenDispatch(android.nfc.NfcAdapter.NfcLockscreenDispatch, int[]);
     method public void setBeamPushUris(android.net.Uri[], android.app.Activity);
     method public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
@@ -17672,6 +17700,10 @@
     method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
   }
 
+  public static abstract interface NfcAdapter.NfcLockscreenDispatch {
+    method public abstract boolean onTagDetected(android.nfc.Tag);
+  }
+
   public static abstract interface NfcAdapter.OnNdefPushCompleteCallback {
     method public abstract void onNdefPushComplete(android.nfc.NfcEvent);
   }
@@ -23460,7 +23492,7 @@
     field public static final java.lang.String CONTENT_DIRECTORY = "data";
   }
 
-  public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
+  public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.DataUsageStatColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
     field public static final java.lang.String CONTENT_DIRECTORY = "entities";
     field public static final java.lang.String DATA_ID = "data_id";
     field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
@@ -27794,7 +27826,6 @@
     field public static final java.lang.String ACTION_CALL_SERVICE;
     field public static final java.lang.String ACTION_CALL_SERVICE_PROVIDER;
     field public static final java.lang.String ACTION_CALL_SERVICE_SELECTOR;
-    field public static final java.lang.String ACTION_CHANGE_DEFAULT_PHONE = "android.telecomm.ACTION_CHANGE_DEFAULT_PHONE";
     field public static final java.lang.String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
     field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
     field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
@@ -27802,7 +27833,6 @@
     field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecomm.extra.CALL_DISCONNECT_MESSAGE";
     field public static final java.lang.String EXTRA_CALL_SERVICE_DESCRIPTOR = "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
     field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.intent.extra.INCOMING_CALL_EXTRAS";
-    field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.intent.extra.START_CALL_WITH_SPEAKERPHONE";
   }
 
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 6296dd1..6b2a0e2 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -52,13 +52,13 @@
     );
 }
 
-static SkBitmap::Config flinger2skia(PixelFormat f)
+static SkColorType flinger2skia(PixelFormat f)
 {
     switch (f) {
         case PIXEL_FORMAT_RGB_565:
-            return SkBitmap::kRGB_565_Config;
+            return kRGB_565_SkColorType;
         default:
-            return SkBitmap::kARGB_8888_Config;
+            return kN32_SkColorType;
     }
 }
 
@@ -174,9 +174,10 @@
 
     if (base) {
         if (png) {
+            const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f),
+                                                       kPremul_SkAlphaType);
             SkBitmap b;
-            b.setConfig(flinger2skia(f), w, h, s*bytesPerPixel(f));
-            b.setPixels((void*)base);
+            b.installPixels(info, const_cast<void*>(base), s*bytesPerPixel(f));
             SkDynamicMemoryWStream stream;
             SkImageEncoder::EncodeStream(&stream, b,
                     SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9132883..d0d0289 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -30,6 +30,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -5935,14 +5936,21 @@
     }
 
     /**
-     * Put this Activity in a mode where the user is locked to the
+     * Request to put this Activity in a mode where the user is locked to the
      * current task.
      *
      * This will prevent the user from launching other apps, going to settings,
      * or reaching the home screen.
      *
-     * Lock task mode will only start if the activity has been whitelisted by the
-     * Device Owner through DevicePolicyManager#setLockTaskComponents.
+     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true
+     * for this component then the app will go directly into Lock Task mode.  The user
+     * will not be able to exit this mode until {@link Activity#stopLockTask()} is called.
+     *
+     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
+     * then the system will prompt the user with a dialog requesting permission to enter
+     * this mode.  When entered through this method the user can exit at any time by
+     * swiping down twice from the top of the screen.  Calling stopLockTask will also
+     * exit the mode.
      */
     public void startLockTask() {
         try {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 56462ae..572d389 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2145,6 +2145,13 @@
             return true;
         }
 
+        case START_LOCK_TASK_BY_CURRENT: {
+            data.enforceInterface(IActivityManager.descriptor);
+            startLockTaskModeOnCurrent();
+            reply.writeNoException();
+            return true;
+        }
+
         case STOP_LOCK_TASK_MODE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             stopLockTaskMode();
@@ -2152,6 +2159,13 @@
             return true;
         }
 
+        case STOP_LOCK_TASK_BY_CURRENT: {
+            data.enforceInterface(IActivityManager.descriptor);
+            stopLockTaskModeOnCurrent();
+            reply.writeNoException();
+            return true;
+        }
+
         case IS_IN_LOCK_TASK_MODE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final boolean isInLockTaskMode = isInLockTaskMode();
@@ -4947,6 +4961,17 @@
     }
 
     @Override
+    public void startLockTaskModeOnCurrent() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(START_LOCK_TASK_BY_CURRENT, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public void stopLockTaskMode() throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -4958,6 +4983,17 @@
     }
 
     @Override
+    public void stopLockTaskModeOnCurrent() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(STOP_LOCK_TASK_BY_CURRENT, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public boolean isInLockTaskMode() throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index c7030b0..fa9b1e3 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -233,6 +233,13 @@
         if (getViewsTransition() != null) {
             getDecor().captureTransitioningViews(mTransitioningViews);
             mTransitioningViews.removeAll(mSharedElements);
+            Rect r = new Rect();
+            for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+                View view = mTransitioningViews.get(i);
+                if (!view.getGlobalVisibleRect(r)) {
+                    mTransitioningViews.remove(i);
+                }
+            }
         }
         setEpicenter();
     }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index bf2d7e5..b630278 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -429,6 +429,9 @@
     public IBinder getHomeActivityToken() throws RemoteException;
 
     /** @hide */
+    public void startLockTaskModeOnCurrent() throws RemoteException;
+
+    /** @hide */
     public void startLockTaskMode(int taskId) throws RemoteException;
 
     /** @hide */
@@ -438,6 +441,9 @@
     public void stopLockTaskMode() throws RemoteException;
 
     /** @hide */
+    public void stopLockTaskModeOnCurrent() throws RemoteException;
+
+    /** @hide */
     public boolean isInLockTaskMode() throws RemoteException;
 
     /** @hide */
@@ -744,4 +750,6 @@
     int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
     int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219;
     int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220;
+    int START_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+221;
+    int STOP_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+222;
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b78b9c9..c583998 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -188,6 +188,9 @@
             endPerformanceSnapshot();
         }
         if (mPerfMetrics != null) {
+            if (results == null) {
+                results = new Bundle();
+            }
             results.putAll(mPerfMetrics);
         }
         if (mUiAutomation != null) {
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 706ef04..4631323 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -31,12 +31,22 @@
  * @hide
  */
 public class BackupTransport {
+    // Zero return always means things are okay.  If returned from
+    // getNextFullRestoreDataChunk(), it means that no data could be delivered at
+    // this time, but the restore is still running and the caller should simply
+    // retry.
     public static final int TRANSPORT_OK = 0;
-    public static final int TRANSPORT_ERROR = 1;
-    public static final int TRANSPORT_NOT_INITIALIZED = 2;
-    public static final int TRANSPORT_PACKAGE_REJECTED = 3;
-    public static final int AGENT_ERROR = 4;
-    public static final int AGENT_UNKNOWN = 5;
+
+    // -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that
+    // we've delivered the entire data stream for the current restore target.
+    public static final int NO_MORE_DATA = -1;
+
+    // Result codes that indicate real errors are negative and not -1
+    public static final int TRANSPORT_ERROR = -1000;
+    public static final int TRANSPORT_NOT_INITIALIZED = -1001;
+    public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
+    public static final int AGENT_ERROR = -1003;
+    public static final int AGENT_UNKNOWN = -1004;
 
     IBackupTransport mBinderImpl = new TransportImpl();
     /** @hide */
@@ -370,11 +380,14 @@
      * @param socket The file descriptor that the transport will use for delivering the
      *    streamed archive.  The transport must close this socket in all cases when returning
      *    from this method.
-     * @return 0 when no more data for the current package is available.  A positive value
-     *    indicates the presence of that many bytes to be delivered to the app.  Any negative
-     *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
-     *    indicating a fatal error condition that precludes further restore operations
-     *    on the current dataset.
+     * @return {@link #NO_MORE_DATA} when no more data for the current package is available.
+     *    A positive value indicates the presence of that many bytes to be delivered to the app.
+     *    A value of zero indicates that no data was deliverable at this time, but the restore
+     *    is still running and the caller should retry.  {@link #TRANSPORT_PACKAGE_REJECTED}
+     *    means that the current package's restore operation should be aborted, but that
+     *    the transport itself is still in a good state and so a multiple-package restore
+     *    sequence can still be continued.  Any other negative return value is treated as a
+     *    fatal error condition that aborts all further restore operations on the current dataset.
      */
     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
         return 0;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3dfa78b..e24dc84 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -6821,72 +6822,12 @@
         if (other == null) {
             return false;
         }
-        if (mAction != other.mAction) {
-            if (mAction != null) {
-                if (!mAction.equals(other.mAction)) {
-                    return false;
-                }
-            } else {
-                if (!other.mAction.equals(mAction)) {
-                    return false;
-                }
-            }
-        }
-        if (mData != other.mData) {
-            if (mData != null) {
-                if (!mData.equals(other.mData)) {
-                    return false;
-                }
-            } else {
-                if (!other.mData.equals(mData)) {
-                    return false;
-                }
-            }
-        }
-        if (mType != other.mType) {
-            if (mType != null) {
-                if (!mType.equals(other.mType)) {
-                    return false;
-                }
-            } else {
-                if (!other.mType.equals(mType)) {
-                    return false;
-                }
-            }
-        }
-        if (mPackage != other.mPackage) {
-            if (mPackage != null) {
-                if (!mPackage.equals(other.mPackage)) {
-                    return false;
-                }
-            } else {
-                if (!other.mPackage.equals(mPackage)) {
-                    return false;
-                }
-            }
-        }
-        if (mComponent != other.mComponent) {
-            if (mComponent != null) {
-                if (!mComponent.equals(other.mComponent)) {
-                    return false;
-                }
-            } else {
-                if (!other.mComponent.equals(mComponent)) {
-                    return false;
-                }
-            }
-        }
-        if (mCategories != other.mCategories) {
-            if (mCategories != null) {
-                if (!mCategories.equals(other.mCategories)) {
-                    return false;
-                }
-            } else {
-                if (!other.mCategories.equals(mCategories)) {
-                    return false;
-                }
-            }
-        }
+        if (!Objects.equals(this.mAction, other.mAction)) return false;
+        if (!Objects.equals(this.mData, other.mData)) return false;
+        if (!Objects.equals(this.mType, other.mType)) return false;
+        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
+        if (!Objects.equals(this.mComponent, other.mComponent)) return false;
+        if (!Objects.equals(this.mCategories, other.mCategories)) return false;
 
         return true;
     }
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index abc8cde..bcf1e87 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -252,6 +252,13 @@
      */
     public static final int FLAG_IMMERSIVE = 0x0800;
     /**
+     * Bit in {@link #flags}: If set, a task rooted at this activity will have its
+     * baseIntent replaced by the activity immediately above this. Each activity may further
+     * relinquish its identity to the activity above it using this flag. Set from the
+     * android.R.attr#relinquishTaskIdentity attribute.
+     */
+    public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000;
+    /**
      * Bit in {@link #flags} indicating that tasks started with this activity are to be
      * removed from the recent list of tasks when the last activity in the task is finished.
      * {@link android.R.attr#autoRemoveFromRecents}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b5ceebe..9d871c5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2892,6 +2892,7 @@
             PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
             if ((flags & GET_SIGNATURES) != 0) {
                 parser.collectCertificates(pkg, 0);
+                parser.collectManifestDigest(pkg);
             }
             PackageUserState state = new PackageUserState();
             return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 546f3a5..b40a441 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -744,6 +744,8 @@
      * {@code AndroidManifest.xml}, {@code true} is returned.
      */
     public void collectManifestDigest(Package pkg) throws PackageParserException {
+        pkg.manifestDigest = null;
+
         // TODO: extend to gather digest for split APKs
         try {
             final StrictJarFile jarFile = new StrictJarFile(pkg.codePath);
@@ -2627,6 +2629,12 @@
                     false)) {
                 a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
             }
+
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
+                    false)) {
+                a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+            }
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 20dcf83..d2146ac 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -885,9 +885,9 @@
 
     /**
      * Extracts theme attributes from a typed array for later resolution using
-     * {@link Theme#resolveAttributes(int[], int[])}. Removes the entries from
-     * the typed array so that subsequent calls to typed getters will return the
-     * default value without crashing.
+     * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
+     * Removes the entries from the typed array so that subsequent calls to typed
+     * getters will return the default value without crashing.
      *
      * @return an array of length {@link #getIndexCount()} populated with theme
      *         attributes, or null if there are no theme attributes in the typed
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0705e0c..cc8503b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -167,11 +167,16 @@
     private boolean mOneShot;
     private boolean mWithBuffer;
     private boolean mFaceDetectionRunning = false;
-    private Object mAutoFocusCallbackLock = new Object();
+    private final Object mAutoFocusCallbackLock = new Object();
 
     private static final int NO_ERROR = 0;
     private static final int EACCESS = -13;
     private static final int ENODEV = -19;
+    private static final int EBUSY = -16;
+    private static final int EINVAL = -22;
+    private static final int ENOSYS = -38;
+    private static final int EUSERS = -87;
+    private static final int EOPNOTSUPP = -95;
 
     /**
      * Broadcast Action:  A new picture is taken by the camera, and the entry of
@@ -190,6 +195,22 @@
     public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
 
     /**
+     * Camera HAL device API version 1.0
+     * @hide
+     */
+    public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+
+    /**
+     * A constant meaning the normal camera connect/open will be used.
+     */
+    private static final int CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2;
+
+    /**
+     * Used to indicate HAL version un-specified.
+     */
+    private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
+
+    /**
      * Hardware face detection. It does not use much CPU.
      */
     private static final int CAMERA_FACE_DETECTION_HW = 0;
@@ -331,14 +352,82 @@
         return null;
     }
 
-    Camera(int cameraId) {
-        int err = cameraInit(cameraId);
+    /**
+     * Creates a new Camera object to access a particular hardware camera with
+     * given hal API version. If the same camera is opened by other applications
+     * or the hal API version is not supported by this device, this will throw a
+     * RuntimeException.
+     * <p>
+     * You must call {@link #release()} when you are done using the camera,
+     * otherwise it will remain locked and be unavailable to other applications.
+     * <p>
+     * Your application should only have one Camera object active at a time for
+     * a particular hardware camera.
+     * <p>
+     * Callbacks from other methods are delivered to the event loop of the
+     * thread which called open(). If this thread has no event loop, then
+     * callbacks are delivered to the main application event loop. If there is
+     * no main application event loop, callbacks are not delivered.
+     * <p class="caution">
+     * <b>Caution:</b> On some devices, this method may take a long time to
+     * complete. It is best to call this method from a worker thread (possibly
+     * using {@link android.os.AsyncTask}) to avoid blocking the main
+     * application UI thread.
+     *
+     * @param cameraId The hardware camera to access, between 0 and
+     * {@link #getNumberOfCameras()}-1.
+     * @param halVersion The HAL API version this camera device to be opened as.
+     * @return a new Camera object, connected, locked and ready for use.
+     *
+     * @throws IllegalArgumentException if the {@code halVersion} is invalid
+     *
+     * @throws RuntimeException if opening the camera fails (for example, if the
+     * camera is in use by another process or device policy manager has disabled
+     * the camera).
+     *
+     * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
+     * @see #CAMERA_HAL_API_VERSION_1_0
+     *
+     * @hide
+     */
+    public static Camera openLegacy(int cameraId, int halVersion) {
+        if (halVersion < CAMERA_HAL_API_VERSION_1_0) {
+            throw new IllegalArgumentException("Invalid HAL version " + halVersion);
+        }
+
+        return new Camera(cameraId, halVersion);
+    }
+
+    /**
+     * Create a legacy camera object.
+     *
+     * @param cameraId The hardware camera to access, between 0 and
+     * {@link #getNumberOfCameras()}-1.
+     * @param halVersion The HAL API version this camera device to be opened as.
+     */
+    private Camera(int cameraId, int halVersion) {
+        int err = cameraInitVersion(cameraId, halVersion);
         if (checkInitErrors(err)) {
             switch(err) {
                 case EACCESS:
                     throw new RuntimeException("Fail to connect to camera service");
                 case ENODEV:
                     throw new RuntimeException("Camera initialization failed");
+                case ENOSYS:
+                    throw new RuntimeException("Camera initialization failed because some methods"
+                            + " are not implemented");
+                case EOPNOTSUPP:
+                    throw new RuntimeException("Camera initialization failed because the hal"
+                            + " version is not supported by this device");
+                case EINVAL:
+                    throw new RuntimeException("Camera initialization failed because the input"
+                            + " arugments are invalid");
+                case EBUSY:
+                    throw new RuntimeException("Camera initialization failed because the camera"
+                            + " device was already opened");
+                case EUSERS:
+                    throw new RuntimeException("Camera initialization failed because the max"
+                            + " number of camera devices were already opened");
                 default:
                     // Should never hit this.
                     throw new RuntimeException("Unknown camera error");
@@ -346,10 +435,7 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public int cameraInit(int cameraId) {
+    private int cameraInitVersion(int cameraId, int halVersion) {
         mShutterCallback = null;
         mRawImageCallback = null;
         mJpegCallback = null;
@@ -369,9 +455,48 @@
 
         String packageName = ActivityThread.currentPackageName();
 
-        return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+        return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName);
     }
 
+    private int cameraInitNormal(int cameraId) {
+        return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_NORMAL_CONNECT);
+    }
+
+    /**
+     * Connect to the camera service using #connectLegacy
+     *
+     * <p>
+     * This acts the same as normal except that it will return
+     * the detailed error code if open fails instead of
+     * converting everything into {@code NO_INIT}.</p>
+     *
+     * <p>Intended to use by the camera2 shim only, do <i>not</i> use this for other code.</p>
+     *
+     * @return a detailed errno error code, or {@code NO_ERROR} on success
+     *
+     * @hide
+     */
+    public int cameraInitUnspecified(int cameraId) {
+        return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_UNSPECIFIED);
+    }
+
+    /** used by Camera#open, Camera#open(int) */
+    Camera(int cameraId) {
+        int err = cameraInitNormal(cameraId);
+        if (checkInitErrors(err)) {
+            switch(err) {
+                case EACCESS:
+                    throw new RuntimeException("Fail to connect to camera service");
+                case ENODEV:
+                    throw new RuntimeException("Camera initialization failed");
+                default:
+                    // Should never hit this.
+                    throw new RuntimeException("Unknown camera error");
+            }
+        }
+    }
+
+
     /**
      * @hide
      */
@@ -392,11 +517,12 @@
     Camera() {
     }
 
+    @Override
     protected void finalize() {
         release();
     }
 
-    private native final int native_setup(Object camera_this, int cameraId,
+    private native final int native_setup(Object camera_this, int cameraId, int halVersion,
                                            String packageName);
 
     private native final void native_release();
@@ -929,7 +1055,7 @@
 
     private class EventHandler extends Handler
     {
-        private Camera mCamera;
+        private final Camera mCamera;
 
         public EventHandler(Camera c, Looper looper) {
             super(looper);
@@ -2210,6 +2336,7 @@
          * @hide
          * @deprecated
          */
+        @Deprecated
         public void dump() {
             Log.e(TAG, "dump: size=" + mMap.size());
             for (String k : mMap.keySet()) {
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index 31896f5..2bc3dd4 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -74,4 +74,11 @@
     int getLegacyParameters(int cameraId, out String[] parameters);
     // Determines if a particular API version is supported; see ICameraService.h for version defines
     int supportsCameraApi(int cameraId, int apiVersion);
+
+    int connectLegacy(ICameraClient client, int cameraId,
+                    int halVersion,
+                    String clientPackageName,
+                    int clientUid,
+                    // Container for an ICamera object
+                    out BinderHolder device);
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 4a87680..e2f88eb 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -579,6 +579,7 @@
      * of the lens that can be focused correctly.</p>
      * <p>If the lens is fixed-focus, this should be
      * 0.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      */
     public static final Key<Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE =
             new Key<Float>("android.lens.info.minimumFocusDistance", float.class);
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 73188ff..9a3d806 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -278,14 +278,19 @@
                 ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
                 int id = Integer.parseInt(cameraId);
                 try {
-                    mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
-                            USE_CALLING_UID, holder);
-                    cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
-                } catch (CameraRuntimeException e) {
-                    if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+                    if (supportsCamera2Api(cameraId)) {
+                        // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
+                        mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
+                                USE_CALLING_UID, holder);
+                        cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+                    } else {
                         // Use legacy camera implementation for HAL1 devices
                         Log.i(TAG, "Using legacy camera HAL.");
                         cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+                    }
+                } catch (CameraRuntimeException e) {
+                    if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+                        throw new AssertionError("Should've gone down the shim path");
                     } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
                             e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
                             e.getReason() == CameraAccessException.CAMERA_DISABLED ||
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 5a02435..dad1854 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -248,7 +248,6 @@
      * <li>Manual frame duration control<ul>
      * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li>
      * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
-     * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li>
      * </ul>
      * </li>
      * <li>Manual exposure control<ul>
@@ -279,6 +278,9 @@
      * result.</p>
      * <p>A given camera device may also support additional manual sensor controls,
      * but this capability only covers the above list of controls.</p>
+     * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will
+     * additionally return a min frame duration that is greater than
+     * zero for each supported size-format combination.</p>
      *
      * @see CaptureRequest#BLACK_LEVEL_LOCK
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 2cfb611..5bc59dc 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CameraCharacteristics.Key;
 import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.HashCodeHelpers;
 import android.hardware.camera2.utils.TypeReference;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -280,7 +281,7 @@
 
     @Override
     public int hashCode() {
-        return mSettings.hashCode();
+        return HashCodeHelpers.hashCode(mSettings, mSurfaceSet, mUserTag);
     }
 
     public static final Parcelable.Creator<CaptureRequest> CREATOR =
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d9f3af4..2e59eee 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -28,6 +28,8 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
+import android.hardware.camera2.utils.CloseableLock;
+import android.hardware.camera2.utils.CloseableLock.ScopedLock;
 import android.hardware.camera2.utils.LongParcelable;
 import android.os.Handler;
 import android.os.IBinder;
@@ -57,13 +59,14 @@
     // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
     private ICameraDeviceUser mRemoteDevice;
 
-    private final Object mLock = new Object();
+    private final CloseableLock mCloseLock;
     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
 
     private final StateListener mDeviceListener;
     private volatile StateListener mSessionStateListener;
     private final Handler mDeviceHandler;
 
+    private volatile boolean mClosing = false;
     private boolean mInError = false;
     private boolean mIdle = true;
 
@@ -100,7 +103,9 @@
     private final Runnable mCallOnOpened = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onOpened(CameraDeviceImpl.this);
@@ -113,7 +118,9 @@
     private final Runnable mCallOnUnconfigured = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onUnconfigured(CameraDeviceImpl.this);
@@ -126,7 +133,9 @@
     private final Runnable mCallOnActive = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onActive(CameraDeviceImpl.this);
@@ -139,7 +148,9 @@
     private final Runnable mCallOnBusy = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onBusy(CameraDeviceImpl.this);
@@ -150,20 +161,29 @@
     };
 
     private final Runnable mCallOnClosed = new Runnable() {
+        private boolean mClosedOnce = false;
+
         @Override
         public void run() {
+            if (mClosedOnce) {
+                throw new AssertionError("Don't post #onClosed more than once");
+            }
+
             StateListener sessionListener = mSessionStateListener;
             if (sessionListener != null) {
                 sessionListener.onClosed(CameraDeviceImpl.this);
             }
             mDeviceListener.onClosed(CameraDeviceImpl.this);
+            mClosedOnce = true;
         }
     };
 
     private final Runnable mCallOnIdle = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onIdle(CameraDeviceImpl.this);
@@ -176,7 +196,9 @@
     private final Runnable mCallOnDisconnected = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onDisconnected(CameraDeviceImpl.this);
@@ -195,6 +217,7 @@
         mDeviceListener = listener;
         mDeviceHandler = handler;
         mCharacteristics = characteristics;
+        mCloseLock = new CloseableLock(/*name*/"CD-" + mCameraId);
 
         final int MAX_TAG_LEN = 23;
         String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -210,8 +233,8 @@
     }
 
     public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
+        try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
         // TODO: Move from decorator to direct binder-mediated exceptions
-        synchronized(mLock) {
             // If setRemoteFailure already called, do nothing
             if (mInError) return;
 
@@ -254,7 +277,9 @@
         }
         final int code = failureCode;
         final boolean isError = failureIsError;
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+            if (scopedLock == null) return; // Camera already closed, can't go to error state
+
             mInError = true;
             mDeviceHandler.post(new Runnable() {
                 @Override
@@ -280,7 +305,7 @@
         if (outputs == null) {
             outputs = new ArrayList<Surface>();
         }
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
 
             HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
@@ -344,7 +369,7 @@
     public void createCaptureSession(List<Surface> outputs,
             CameraCaptureSession.StateListener listener, Handler handler)
             throws CameraAccessException {
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             if (DEBUG) {
                 Log.d(TAG, "createCaptureSession");
             }
@@ -389,7 +414,7 @@
     @Override
     public CaptureRequest.Builder createCaptureRequest(int templateType)
             throws CameraAccessException {
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
 
             CameraMetadataNative templatedRequest = new CameraMetadataNative();
@@ -435,7 +460,7 @@
      * starting and stopping repeating request and flushing.
      *
      * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
-     * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered.
+     * sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
      * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
      * is added to the list mFrameNumberRequestPairs.</p>
      *
@@ -446,7 +471,7 @@
     private void checkEarlyTriggerSequenceComplete(
             final int requestId, final long lastFrameNumber) {
         // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
-        // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately.
+        // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
         if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
             final CaptureListenerHolder holder;
             int index = mCaptureListenerMap.indexOfKey(requestId);
@@ -463,7 +488,7 @@
 
             if (holder != null) {
                 if (DEBUG) {
-                    Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because"
+                    Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
                             + " request did not reach HAL");
                 }
 
@@ -480,10 +505,9 @@
                                     || lastFrameNumber > Integer.MAX_VALUE) {
                                 throw new AssertionError(lastFrameNumber + " cannot be cast to int");
                             }
-                            holder.getListener().onCaptureSequenceCompleted(
+                            holder.getListener().onCaptureSequenceAborted(
                                     CameraDeviceImpl.this,
-                                    requestId,
-                                    lastFrameNumber);
+                                    requestId);
                         }
                     }
                 };
@@ -509,7 +533,7 @@
             handler = checkHandler(handler);
         }
 
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
             int requestId;
 
@@ -581,7 +605,7 @@
     @Override
     public void stopRepeating() throws CameraAccessException {
 
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
 
@@ -612,7 +636,7 @@
 
     private void waitUntilIdle() throws CameraAccessException {
 
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
                 throw new IllegalStateException("Active repeating request ongoing");
@@ -633,7 +657,7 @@
 
     @Override
     public void flush() throws CameraAccessException {
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
 
             mDeviceHandler.post(mCallOnBusy);
@@ -656,8 +680,15 @@
 
     @Override
     public void close() {
-        synchronized (mLock) {
+        mClosing = true;
+        // Acquire exclusive lock, close, release (idempotent)
+        mCloseLock.close();
 
+        /*
+         *  The rest of this is safe, since no other methods will be able to execute
+         *  (they will throw ISE instead; the callbacks will get dropped)
+         */
+        {
             try {
                 if (mRemoteDevice != null) {
                     mRemoteDevice.disconnect();
@@ -805,7 +836,12 @@
                 // remove request from mCaptureListenerMap
                 final int requestId = frameNumberRequestPair.getValue();
                 final CaptureListenerHolder holder;
-                synchronized (mLock) {
+                try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                    if (scopedLock == null) {
+                        Log.w(TAG, "Camera closed while checking sequences");
+                        return;
+                    }
+
                     int index = mCaptureListenerMap.indexOfKey(requestId);
                     holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
                             : null;
@@ -890,9 +926,12 @@
         @Override
         public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
             Runnable r = null;
-            if (isClosed()) return;
 
-            synchronized(mLock) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) {
+                    return; // Camera already closed
+                }
+
                 mInError = true;
                 switch (errorCode) {
                     case ERROR_CAMERA_DISCONNECTED:
@@ -914,25 +953,24 @@
                         break;
                 }
                 CameraDeviceImpl.this.mDeviceHandler.post(r);
-            }
 
-            // Fire onCaptureSequenceCompleted
-            if (DEBUG) {
-                Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+                // Fire onCaptureSequenceCompleted
+                if (DEBUG) {
+                    Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+                }
+                mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+                checkAndFireSequenceComplete();
             }
-            mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
-            checkAndFireSequenceComplete();
-
         }
 
         @Override
         public void onCameraIdle() {
-            if (isClosed()) return;
-
             if (DEBUG) {
                 Log.d(TAG, "Camera now idle");
             }
-            synchronized (mLock) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 if (!CameraDeviceImpl.this.mIdle) {
                     CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
                 }
@@ -948,30 +986,33 @@
             }
             final CaptureListenerHolder holder;
 
-            // Get the listener for this frame ID, if there is one
-            synchronized (mLock) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
+                // Get the listener for this frame ID, if there is one
                 holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
-            }
 
-            if (holder == null) {
-                return;
-            }
+                if (holder == null) {
+                    return;
+                }
 
-            if (isClosed()) return;
+                if (isClosed()) return;
 
-            // Dispatch capture start notice
-            holder.getHandler().post(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()) {
-                            holder.getListener().onCaptureStarted(
-                                CameraDeviceImpl.this,
-                                holder.getRequest(resultExtras.getSubsequenceId()),
-                                timestamp);
+                // Dispatch capture start notice
+                holder.getHandler().post(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!CameraDeviceImpl.this.isClosed()) {
+                                holder.getListener().onCaptureStarted(
+                                    CameraDeviceImpl.this,
+                                    holder.getRequest(resultExtras.getSubsequenceId()),
+                                    timestamp);
+                            }
                         }
-                    }
-                });
+                    });
+
+            }
         }
 
         @Override
@@ -984,88 +1025,91 @@
                         + requestId);
             }
 
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
 
-            // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
-            result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
-                    getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+                // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+                result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+                        getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
 
-            final CaptureListenerHolder holder;
-            synchronized (mLock) {
-                holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
-            }
+                final CaptureListenerHolder holder =
+                        CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
 
-            Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
-            boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
+                Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
+                boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
 
-            // Update tracker (increment counter) when it's not a partial result.
-            if (!quirkIsPartialResult) {
-                mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
-            }
-
-            // Check if we have a listener for this
-            if (holder == null) {
-                if (DEBUG) {
-                    Log.d(TAG,
-                            "holder is null, early return at frame "
-                                    + resultExtras.getFrameNumber());
+                // Update tracker (increment counter) when it's not a partial result.
+                if (!quirkIsPartialResult) {
+                    mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(),
+                            /*error*/false);
                 }
-                return;
-            }
 
-            if (isClosed()) {
-                if (DEBUG) {
-                    Log.d(TAG,
-                            "camera is closed, early return at frame "
-                                    + resultExtras.getFrameNumber());
+                // Check if we have a listener for this
+                if (holder == null) {
+                    if (DEBUG) {
+                        Log.d(TAG,
+                                "holder is null, early return at frame "
+                                        + resultExtras.getFrameNumber());
+                    }
+                    return;
                 }
-                return;
-            }
 
-            final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
-
-
-            Runnable resultDispatch = null;
-
-            // Either send a partial result or the final capture completed result
-            if (quirkIsPartialResult) {
-                final CaptureResult resultAsCapture =
-                        new CaptureResult(result, request, requestId);
-
-                // Partial result
-                resultDispatch = new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()){
-                            holder.getListener().onCapturePartial(
-                                CameraDeviceImpl.this,
-                                request,
-                                resultAsCapture);
-                        }
+                if (isClosed()) {
+                    if (DEBUG) {
+                        Log.d(TAG,
+                                "camera is closed, early return at frame "
+                                        + resultExtras.getFrameNumber());
                     }
-                };
-            } else {
-                final TotalCaptureResult resultAsCapture =
-                        new TotalCaptureResult(result, request, requestId);
+                    return;
+                }
 
-                // Final capture result
-                resultDispatch = new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()){
-                            holder.getListener().onCaptureCompleted(
-                                CameraDeviceImpl.this,
-                                request,
-                                resultAsCapture);
+                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+
+
+                Runnable resultDispatch = null;
+
+                // Either send a partial result or the final capture completed result
+                if (quirkIsPartialResult) {
+                    final CaptureResult resultAsCapture =
+                            new CaptureResult(result, request, requestId);
+
+                    // Partial result
+                    resultDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!CameraDeviceImpl.this.isClosed()){
+                                holder.getListener().onCapturePartial(
+                                    CameraDeviceImpl.this,
+                                    request,
+                                    resultAsCapture);
+                            }
                         }
-                    }
-                };
-            }
+                    };
+                } else {
+                    final TotalCaptureResult resultAsCapture =
+                            new TotalCaptureResult(result, request, requestId);
 
-            holder.getHandler().post(resultDispatch);
+                    // Final capture result
+                    resultDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!CameraDeviceImpl.this.isClosed()){
+                                holder.getListener().onCaptureCompleted(
+                                    CameraDeviceImpl.this,
+                                    request,
+                                    resultAsCapture);
+                            }
+                        }
+                    };
+                }
 
-            // Fire onCaptureSequenceCompleted
-            if (!quirkIsPartialResult) {
-                checkAndFireSequenceComplete();
+                holder.getHandler().post(resultDispatch);
+
+                // Fire onCaptureSequenceCompleted
+                if (!quirkIsPartialResult) {
+                    checkAndFireSequenceComplete();
+                }
+
             }
         }
 
@@ -1101,10 +1145,9 @@
         }
     }
 
+    /** Whether the camera device has started to close (may not yet have finished) */
     private boolean isClosed() {
-        synchronized(mLock) {
-            return (mRemoteDevice == null);
-        }
+        return mClosing;
     }
 
     private CameraCharacteristics getCharacteristics() {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 54d9c3c..abd69c1 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -76,13 +76,11 @@
         }
         // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
         Camera legacyCamera = Camera.openUninitialized();
-        int initErrors = legacyCamera.cameraInit(cameraId);
+        int initErrors = legacyCamera.cameraInitUnspecified(cameraId);
+
         // Check errors old HAL initialization
-        if (Camera.checkInitErrors(initErrors)) {
-            // TODO: Map over old camera error codes.  This likely involves improving the error
-            // reporting in the HAL1 connect path.
-            throw new CameraRuntimeException(CameraAccessException.CAMERA_DISCONNECTED);
-        }
+        CameraBinderDecorator.throwOnError(initErrors);
+
         LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
         return new CameraDeviceUserShim(cameraId, device);
     }
@@ -169,7 +167,7 @@
             }
             int numSurfaces = mSurfaces.size();
             if (numSurfaces > 0) {
-                surfaces = new ArrayList<Surface>();
+                surfaces = new ArrayList<>();
                 for (int i = 0; i < numSurfaces; ++i) {
                     surfaces.add(mSurfaces.valueAt(i));
                 }
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index 0382557..5d44fd2 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -100,6 +100,7 @@
                     break;
                 case MSG_ALLOW_FRAMES:
                     mDroppingFrames = false;
+                    break;
                 default:
                     Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
                     break;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index c34a34d..50515a2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,7 +23,6 @@
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.utils.LongParcelable;
 import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -34,7 +33,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.hardware.camera2.utils.CameraBinderDecorator.*;
 
 /**
  * This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -50,9 +50,11 @@
     public static final String DEBUG_PROP = "HAL1ShimLogging";
     private final String TAG;
 
+    private static final boolean DEBUG = false;
     private final int mCameraId;
     private final ICameraDeviceCallbacks mDeviceCallbacks;
     private final CameraDeviceState mDeviceState = new CameraDeviceState();
+    private List<Surface> mConfiguredSurfaces;
 
     private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
 
@@ -84,6 +86,9 @@
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onError callback.");
+                    }
                     try {
                         mDeviceCallbacks.onCameraError(errorCode, extras);
                     } catch (RemoteException e) {
@@ -92,13 +97,14 @@
                     }
                 }
             });
-
-
         }
 
         @Override
         public void onConfiguring() {
             // Do nothing
+            if (DEBUG) {
+                Log.d(TAG, "doing onConfiguring callback.");
+            }
         }
 
         @Override
@@ -108,6 +114,9 @@
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onIdle callback.");
+                    }
                     try {
                         mDeviceCallbacks.onCameraIdle();
                     } catch (RemoteException e) {
@@ -126,6 +135,9 @@
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onCaptureStarted callback.");
+                    }
                     try {
                         // TODO: Don't fake timestamp
                         mDeviceCallbacks.onCaptureStarted(extras, timestamp);
@@ -135,7 +147,6 @@
                     }
                 }
             });
-
         }
 
         @Override
@@ -145,6 +156,9 @@
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onCaptureResult callback.");
+                    }
                     try {
                         // TODO: Don't fake metadata
                         mDeviceCallbacks.onResultReceived(result, extras);
@@ -200,16 +214,35 @@
     /**
      * Configure the device with a set of output surfaces.
      *
+     * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
+     *
+     * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
+     *
      * @param outputs a list of surfaces to set.
-     * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
+     * @return an error code for this binder operation, or {@link NO_ERROR}
      *          on success.
      */
     public int configureOutputs(List<Surface> outputs) {
+        if (outputs != null) {
+            for (Surface output : outputs) {
+                if (output == null) {
+                    Log.e(TAG, "configureOutputs - null outputs are not allowed");
+                    return BAD_VALUE;
+                }
+            }
+        }
+
         int error = mDeviceState.setConfiguring();
-        if (error == CameraBinderDecorator.NO_ERROR) {
+        if (error == NO_ERROR) {
             mRequestThreadManager.configure(outputs);
             error = mDeviceState.setIdle();
         }
+
+        // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
+        if (error == NO_ERROR) {
+            mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
+        }
+
         return error;
     }
 
@@ -226,7 +259,35 @@
      */
     public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
             /*out*/LongParcelable frameNumber) {
-        // TODO: validate request here
+        if (requestList == null || requestList.isEmpty()) {
+            Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
+            return BAD_VALUE;
+        }
+
+        // Make sure that there all requests have at least 1 surface; all surfaces are non-null
+        for (CaptureRequest request : requestList) {
+            if (request.getTargets().isEmpty()) {
+                Log.e(TAG, "submitRequestList - "
+                        + "Each request must have at least one Surface target");
+                return BAD_VALUE;
+            }
+
+            for (Surface surface : request.getTargets()) {
+                if (surface == null) {
+                    Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
+                    return BAD_VALUE;
+                } else if (mConfiguredSurfaces == null) {
+                    Log.e(TAG, "submitRequestList - must configure " +
+                            " device with valid surfaces before submitting requests");
+                    return INVALID_OPERATION;
+                } else if (!mConfiguredSurfaces.contains(surface)) {
+                    Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
+                    return BAD_VALUE;
+                }
+            }
+        }
+
+        // TODO: further validation of request here
         mIdle.close();
         return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
                 frameNumber);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 8bb066f..048878c 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -21,12 +21,16 @@
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Size;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.StreamConfiguration;
 import android.hardware.camera2.params.StreamConfigurationDuration;
 import android.util.Log;
+import android.util.Range;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import static com.android.internal.util.Preconditions.*;
@@ -44,6 +48,14 @@
     private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
     private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
 
+    // for metadata
+    private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
+
+    private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // ms
+    private static final long APPROXIMATE_SENSOR_AREA = (1 << 20); // 8mp
+    private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms
+    private static final long NS_PER_MS = 1000000;
+
     /**
      * Create characteristics for a legacy device by mapping the {@code parameters}
      * and {@code info}
@@ -85,15 +97,16 @@
     }
 
     private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) {
+        m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
         mapStreamConfigs(m, p);
-
+        mapAeConfig(m, p);
+        mapCapabilities(m, p);
+        mapLens(m, p);
+        mapFlash(m, p);
         // TODO: map other fields
     }
 
     private static void mapStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
-        // TODO: set non-empty durations
-        m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[] {} );
-        m.set(SCALER_AVAILABLE_STALL_DURATIONS, new StreamConfigurationDuration[] {} );
 
         ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>();
         /*
@@ -122,10 +135,98 @@
                         String.format("mapStreamConfigs - Skipping non-public format %x", format));
             }
         }
+
+        List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
         appendStreamConfig(availableStreamConfigs,
                 HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
         m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
                 availableStreamConfigs.toArray(new StreamConfiguration[0]));
+
+        // No frame durations available
+        m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
+
+        StreamConfigurationDuration[] jpegStalls =
+                new StreamConfigurationDuration[jpegSizes.size()];
+        int i = 0;
+        long longestStallDuration = -1;
+        for (Camera.Size s : jpegSizes) {
+            long stallDuration =  calculateJpegStallDuration(s);
+            jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width,
+                    s.height, stallDuration);
+            if (longestStallDuration < stallDuration) {
+                longestStallDuration = stallDuration;
+            }
+        }
+        // Set stall durations for jpeg, other formats use default stall duration
+        m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
+
+        m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private static void mapAeConfig(CameraMetadataNative m, Camera.Parameters p) {
+
+        List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
+        if (fpsRanges == null) {
+            throw new AssertionError("Supported FPS ranges cannot be null.");
+        }
+        int rangesSize = fpsRanges.size();
+        if (rangesSize <= 0) {
+            throw new AssertionError("At least one FPS range must be supported.");
+        }
+        Range<Integer>[] ranges = new Range[rangesSize];
+        int i = 0;
+        for (int[] r : fpsRanges) {
+            ranges[i++] = Range.create(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                    r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+        }
+        m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
+
+        List<String> antiBandingModes = p.getSupportedAntibanding();
+        int antiBandingModesSize = antiBandingModes.size();
+        if (antiBandingModesSize > 0) {
+            int[] modes = new int[antiBandingModesSize];
+            int j = 0;
+            for (String mode : antiBandingModes) {
+                int convertedMode = convertAntiBandingMode(mode);
+                if (convertedMode == -1) {
+                    Log.w(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
+                            " not supported, skipping...");
+                } else {
+                    modes[j++] = convertedMode;
+                }
+            }
+            m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
+        }
+    }
+
+    private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) {
+        int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
+        m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
+    }
+
+    private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
+        /*
+         *  We can tell if the lens is fixed focus;
+         *  but if it's not, we can't tell the minimum focus distance, so leave it null then.
+         */
+        if (p.getFocusMode() == Camera.Parameters.FOCUS_MODE_FIXED) {
+            m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS);
+        }
+    }
+
+    private static void mapFlash(CameraMetadataNative m, Camera.Parameters p) {
+        boolean flashAvailable = false;
+        List<String> supportedFlashModes = p.getSupportedFlashModes();
+        if (supportedFlashModes != null) {
+            // If only 'OFF' is available, we don't really have flash support
+            if (!(supportedFlashModes.contains(Camera.Parameters.FLASH_MODE_OFF) &&
+                    supportedFlashModes.size() == 1)) {
+                flashAvailable = true;
+            }
+        }
+
+        m.set(FLASH_INFO_AVAILABLE, flashAvailable);
     }
 
     private static void appendStreamConfig(
@@ -136,4 +237,115 @@
             configs.add(config);
         }
     }
+
+    /**
+     * Returns -1 if the anti-banding mode string is null, or not supported.
+     */
+    private static int convertAntiBandingMode(final String mode) {
+        if (mode == null) {
+            return -1;
+        }
+        switch(mode) {
+            case Camera.Parameters.ANTIBANDING_OFF: {
+                return CONTROL_AE_ANTIBANDING_MODE_OFF;
+            }
+            case Camera.Parameters.ANTIBANDING_50HZ: {
+                return CONTROL_AE_ANTIBANDING_MODE_50HZ;
+            }
+            case Camera.Parameters.ANTIBANDING_60HZ: {
+                return CONTROL_AE_ANTIBANDING_MODE_60HZ;
+            }
+            case Camera.Parameters.ANTIBANDING_AUTO: {
+                return CONTROL_AE_ANTIBANDING_MODE_AUTO;
+            }
+            default: {
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * Returns null if the anti-banding mode enum is not supported.
+     */
+    private static String convertAntiBandingModeToLegacy(int mode) {
+        switch(mode) {
+            case CONTROL_AE_ANTIBANDING_MODE_OFF: {
+                return Camera.Parameters.ANTIBANDING_OFF;
+            }
+            case CONTROL_AE_ANTIBANDING_MODE_50HZ: {
+                return Camera.Parameters.ANTIBANDING_50HZ;
+            }
+            case CONTROL_AE_ANTIBANDING_MODE_60HZ: {
+                return Camera.Parameters.ANTIBANDING_60HZ;
+            }
+            case CONTROL_AE_ANTIBANDING_MODE_AUTO: {
+                return Camera.Parameters.ANTIBANDING_AUTO;
+            }
+            default: {
+                return null;
+            }
+        }
+    }
+
+
+    private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
+        int[] legacyFps = new int[2];
+        legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower();
+        legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper();
+        return legacyFps;
+    }
+
+    /**
+     * Return the stall duration for a given output jpeg size in nanoseconds.
+     *
+     * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p>
+     */
+    private static long calculateJpegStallDuration(Camera.Size size) {
+        long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
+        long area = size.width * (long) size.height;
+        long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME * NS_PER_MS /
+                APPROXIMATE_SENSOR_AREA; // 600ms stall for 8mp
+        return baseDuration + area * stallPerArea;
+    }
+
+    /**
+     * Generate capture result metadata from legacy camera parameters.
+     *
+     * @param params a {@link Camera.Parameters} object to generate metadata from.
+     * @param request the {@link CaptureRequest} used for this result.
+     * @param timestamp the timestamp to use for this result in nanoseconds.
+     * @return a {@link CameraMetadataNative} object containing result metadata.
+     */
+    public static CameraMetadataNative convertResultMetadata(Camera.Parameters params,
+                                                      CaptureRequest request,
+                                                      long timestamp) {
+        CameraMetadataNative result = new CameraMetadataNative();
+        result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
+        result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
+
+        // TODO: Remaining result metadata tags conversions.
+        return result;
+    }
+
+    /**
+     * Set the legacy parameters using the request metadata.
+     *
+     * @param request a {@link CaptureRequest} object to generate parameters from.
+     * @param params the a {@link Camera.Parameters} to set parameters in.
+     */
+    public static void convertRequestMetadata(CaptureRequest request,
+            /*out*/Camera.Parameters params) {
+        Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
+        if (antiBandingMode != null) {
+            String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
+            if (legacyMode != null) params.setAntibanding(legacyMode);
+        }
+
+        Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+        if (aeFpsRange != null) {
+            int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
+            params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                    legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+        }
+    }
 }
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 7b522ff..e0f3429 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -16,11 +16,9 @@
 
 package android.hardware.camera2.legacy;
 
-import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.utils.LongParcelable;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.os.ConditionVariable;
@@ -71,21 +69,26 @@
     private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
     private boolean mPreviewRunning = false;
 
+    private volatile long mLastJpegTimestamp;
+    private volatile long mLastPreviewTimestamp;
     private volatile RequestHolder mInFlightPreview;
     private volatile RequestHolder mInFlightJpeg;
 
-    private List<Surface> mPreviewOutputs = new ArrayList<Surface>();
-    private List<Surface> mCallbackOutputs = new ArrayList<Surface>();
+    private final List<Surface> mPreviewOutputs = new ArrayList<Surface>();
+    private final List<Surface> mCallbackOutputs = new ArrayList<Surface>();
     private GLThreadManager mGLThreadManager;
     private SurfaceTexture mPreviewTexture;
+    private Camera.Parameters mParams;
 
     private Size mIntermediateBufferSize;
 
     private final RequestQueue mRequestQueue = new RequestQueue();
+    private CaptureRequest mLastRequest = null;
     private SurfaceTexture mDummyTexture;
     private Surface mDummySurface;
 
     private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
+    private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
 
     /**
      * Container object for Configure messages.
@@ -209,23 +212,34 @@
         }
     };
 
+    private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
+        @Override
+        public void onShutter() {
+            mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
+        }
+    };
+
     private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
             new SurfaceTexture.OnFrameAvailableListener() {
                 @Override
                 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                    RequestHolder holder = mInFlightPreview;
+                    if (holder == null) {
+                        mGLThreadManager.queueNewFrame(null);
+                        Log.w(TAG, "Dropping preview frame.");
+                        return;
+                    }
+
                     if (DEBUG) {
                         mPrevCounter.countAndLog();
                     }
-                    RequestHolder holder = mInFlightPreview;
-                    if (holder == null) {
-                        Log.w(TAG, "Dropping preview frame.");
-                        mInFlightPreview = null;
-                        return;
-                    }
+                    mInFlightPreview = null;
+
                     if (holder.hasPreviewTargets()) {
                         mGLThreadManager.queueNewFrame(holder.getHolderTargets());
                     }
 
+                    mLastPreviewTimestamp = surfaceTexture.getTimestamp();
                     mReceivedPreview.open();
                 }
             };
@@ -252,7 +266,7 @@
         }
         mInFlightJpeg = request;
         // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
-        mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback);
+        mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
         mPreviewRunning = false;
     }
 
@@ -300,19 +314,20 @@
         mInFlightPreview = null;
         mInFlightJpeg = null;
 
-
-        for (Surface s : outputs) {
-            int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
-            switch (format) {
-                case CameraMetadataNative.NATIVE_JPEG_FORMAT:
-                    mCallbackOutputs.add(s);
-                    break;
-                default:
-                    mPreviewOutputs.add(s);
-                    break;
+        if (outputs != null) {
+            for (Surface s : outputs) {
+                int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
+                switch (format) {
+                    case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+                        mCallbackOutputs.add(s);
+                        break;
+                    default:
+                        mPreviewOutputs.add(s);
+                        break;
+                }
             }
         }
-
+        mParams = mCamera.getParameters();
         if (mPreviewOutputs.size() > 0) {
             List<Size> outputSizes = new ArrayList<>(outputs.size());
             for (Surface s : mPreviewOutputs) {
@@ -323,13 +338,11 @@
 
             Size largestOutput = findLargestByArea(outputSizes);
 
-            Camera.Parameters params = mCamera.getParameters();
-
             // Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
-            List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
+            List<Size> supportedJpegSizes = convertSizeList(mParams.getSupportedPictureSizes());
             Size largestJpegDimen = findLargestByArea(supportedJpegSizes);
 
-            List<Size> supportedPreviewSizes = convertSizeList(params.getSupportedPreviewSizes());
+            List<Size> supportedPreviewSizes = convertSizeList(mParams.getSupportedPreviewSizes());
 
             // Use smallest preview dimension with same aspect ratio as sensor that is >= than all
             // of the configured output dimensions.  If none exists, fall back to using the largest
@@ -357,8 +370,6 @@
             }
         }
 
-
-
         // TODO: Detect and optimize single-output paths here to skip stream teeing.
         if (mGLThreadManager == null) {
             mGLThreadManager = new GLThreadManager(mCameraId);
@@ -419,7 +430,6 @@
 
     private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
         private boolean mCleanup = false;
-        private List<RequestHolder> mRepeating = null;
 
         @SuppressWarnings("unchecked")
         @Override
@@ -428,10 +438,14 @@
                 return true;
             }
 
+            if (DEBUG) {
+                Log.d(TAG, "Request thread handling message:" + msg.what);
+            }
             switch (msg.what) {
                 case MSG_CONFIGURE_OUTPUTS:
                     ConfigureHolder config = (ConfigureHolder) msg.obj;
-                    Log.i(TAG, "Configure outputs: " + config.surfaces.size() +
+                    int sizes = config.surfaces != null ? config.surfaces.size() : 0;
+                    Log.i(TAG, "Configure outputs: " + sizes +
                             " surfaces configured.");
                     try {
                         configureOutputs(config.surfaces);
@@ -459,7 +473,15 @@
                     List<RequestHolder> requests =
                             nextBurst.first.produceRequestHolders(nextBurst.second);
                     for (RequestHolder holder : requests) {
+                        CaptureRequest request = holder.getRequest();
+                        if (mLastRequest == null || mLastRequest != request) {
+                            mLastRequest = request;
+                            LegacyMetadataMapper.convertRequestMetadata(mLastRequest,
+                                /*out*/mParams);
+                            mCamera.setParameters(mParams);
+                        }
                         mDeviceState.setCaptureStart(holder);
+                        long timestamp = 0;
                         try {
                             if (holder.hasPreviewTargets()) {
                                 mReceivedPreview.close();
@@ -468,6 +490,7 @@
                                     // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for preview callback!");
                                 }
+                                timestamp = mLastPreviewTimestamp;
                             }
                             if (holder.hasJpegTargets()) {
                                 mReceivedJpeg.close();
@@ -478,16 +501,19 @@
                                     Log.e(TAG, "Hit timeout for jpeg callback!");
                                 }
                                 mInFlightJpeg = null;
+                                timestamp = mLastJpegTimestamp;
                             }
                         } catch (IOException e) {
                             // TODO: err handling
                             throw new IOError(e);
                         }
-                        Camera.Parameters params = mCamera.getParameters();
-                        CameraMetadataNative result = convertResultMetadata(params,
-                                holder.getRequest());
+                        CameraMetadataNative result = LegacyMetadataMapper.convertResultMetadata(mParams,
+                                request, timestamp);
                         mDeviceState.setCaptureResult(holder, result);
                     }
+                    if (DEBUG) {
+                        mRequestCounter.countAndLog();
+                    }
                     break;
                 case MSG_CLEANUP:
                     mCleanup = true;
@@ -506,15 +532,6 @@
         }
     };
 
-    private CameraMetadataNative convertResultMetadata(Camera.Parameters params,
-                                                       CaptureRequest request) {
-        CameraMetadataNative result = new CameraMetadataNative();
-        result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
-
-        // TODO: Remaining result metadata tags conversions.
-        return result;
-    }
-
     /**
      * Create a new RequestThreadManager.
      *
@@ -597,12 +614,14 @@
 
 
     /**
-     * Configure with the current output Surfaces.
+     * Configure with the current list of output Surfaces.
      *
      * <p>
      * This operation blocks until the configuration is complete.
      * </p>
      *
+     * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
+     *
      * @param outputs a {@link java.util.Collection} of outputs to configure.
      */
     public void configure(Collection<Surface> outputs) {
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 49f419f..e9d32f0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -486,6 +486,7 @@
         }
         checkGlError("before updateTexImage");
         mSurfaceTexture.updateTexImage();
+        if (targetSurfaces == null) return;
         for (EGLSurfaceHolder holder : mSurfaces) {
             if (targetSurfaces.contains(holder.surface)) {
                 makeCurrent(holder.eglSurface);
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 3036425..fff171b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -63,6 +63,12 @@
 public final class StreamConfigurationMap {
 
     private static final String TAG = "StreamConfigurationMap";
+
+    /**
+     * Indicates that a minimum frame duration is not available for a particular configuration.
+     */
+    public static final long NO_MIN_FRAME_DURATION = 0;
+
     /**
      * Create a new {@link StreamConfigurationMap}.
      *
@@ -359,7 +365,9 @@
      *
      * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
      * @param size an output-compatible size
-     * @return a minimum frame duration {@code >=} 0 in nanoseconds
+     * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+     *          {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+     *          can only occur on limited mode devices).
      *
      * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
      * @throws NullPointerException if {@code size} was {@code null}
@@ -406,7 +414,9 @@
      *          a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
      *          non-empty array returned by {@link #getOutputSizes(Class)}
      * @param size an output-compatible size
-     * @return a minimum frame duration {@code >=} 0 in nanoseconds
+     * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+     *          {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+     *          can only occur on limited mode devices).
      *
      * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
      * @throws NullPointerException if {@code size} or {@code klass} was {@code null}
@@ -892,7 +902,7 @@
     private long getDurationDefault(int duration) {
         switch (duration) {
             case DURATION_MIN_FRAME:
-                throw new AssertionError("Minimum frame durations are required to be listed");
+                return NO_MIN_FRAME_DURATION;
             case DURATION_STALL:
                 return 0L; // OK. A lack of a stall duration implies a 0 stall duration
             default:
diff --git a/core/java/android/hardware/camera2/utils/CloseableLock.java b/core/java/android/hardware/camera2/utils/CloseableLock.java
new file mode 100644
index 0000000..af55055
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/CloseableLock.java
@@ -0,0 +1,330 @@
+/*
+ * 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.hardware.camera2.utils;
+
+import android.util.Log;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Implement a shared/exclusive lock that can be closed.
+ *
+ * <p>A shared lock can be acquired if any other shared locks are also acquired. An
+ * exclusive lock acquire will block until all shared locks have been released.</p>
+ *
+ * <p>Locks are re-entrant; trying to acquire another lock (of the same type)
+ * while a lock is already held will immediately succeed.</p>
+ *
+ * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not
+ * supported; attempting it will throw an {@link IllegalStateException}.</p>
+ *
+ * <p>If the lock is closed, all future and current acquires will immediately return {@code null}.
+ * </p>
+ */
+public class CloseableLock implements AutoCloseable {
+
+    private static final boolean VERBOSE = false;
+
+    private final String TAG = "CloseableLock";
+    private final String mName;
+
+    private volatile boolean mClosed = false;
+
+    /** If an exclusive lock is acquired by some thread. */
+    private boolean mExclusive = false;
+    /**
+     * How many shared locks are acquired by any thread:
+     *
+     * <p>Reentrant locking increments this. If an exclusive lock is held,
+     * this value will stay at 0.</p>
+     */
+    private int mSharedLocks = 0;
+
+    private final ReentrantLock mLock = new ReentrantLock();
+    /** This condition automatically releases mLock when waiting; re-acquiring it after notify */
+    private final Condition mCondition = mLock.newCondition();
+
+    /** How many times the current thread is holding the lock */
+    private final ThreadLocal<Integer> mLockCount =
+        new ThreadLocal<Integer>() {
+            @Override protected Integer initialValue() {
+                return 0;
+            }
+        };
+
+    /**
+     * Helper class to release a lock at the end of a try-with-resources statement.
+     */
+    public class ScopedLock implements AutoCloseable {
+        private ScopedLock() {}
+
+        /** Release the lock with {@link CloseableLock#releaseLock}. */
+        @Override
+        public void close() {
+            releaseLock();
+        }
+    }
+
+    /**
+     * Create a new instance; starts out with 0 locks acquired.
+     */
+    public CloseableLock() {
+        mName = "";
+    }
+
+    /**
+     * Create a new instance; starts out with 0 locks acquired.
+     *
+     * @param name set an optional name for logging functionality
+     */
+    public CloseableLock(String name) {
+        mName = name;
+    }
+
+    /**
+     * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock.
+     *
+     * <p>Marking a lock as closed will fail all further acquisition attempts;
+     * it will also immediately unblock all other threads currently trying to acquire a lock.</p>
+     *
+     * <p>This operation is idempotent; calling it more than once has no effect.</p>
+     *
+     * @throws IllegalStateException
+     *          if an attempt is made to {@code close} while this thread has a lock acquired
+     */
+    @Override
+    public void close() {
+        if (mClosed) {
+            log("close - already closed; ignoring");
+            return;
+        }
+
+        ScopedLock scoper = acquireExclusiveLock();
+        // Already closed by another thread?
+        if (scoper == null) {
+            return;
+        } else if (mLockCount.get() != 1) {
+            // Future: may want to add a #releaseAndClose to allow this.
+            throw new IllegalStateException(
+                    "Cannot close while one or more acquired locks are being held by this " +
+                     "thread; release all other locks first");
+        }
+
+        try {
+            mLock.lock();
+
+            mClosed = true;
+            mExclusive = false;
+            mSharedLocks = 0;
+            mLockCount.remove();
+
+            // Notify all threads that are waiting to unblock and return immediately
+            mCondition.signalAll();
+        } finally {
+            mLock.unlock();
+        }
+
+        log("close - completed");
+    }
+
+    /**
+     * Try to acquire the lock non-exclusively, blocking until the operation completes.
+     *
+     * <p>If the lock has already been closed, or being closed before this operation returns,
+     * the call will immediately return {@code false}.</p>
+     *
+     * <p>If other threads hold a non-exclusive lock (and the lock is not yet closed),
+     * this operation will return immediately. If another thread holds an exclusive lock,
+     * this thread will block until the exclusive lock has been released.</p>
+     *
+     * <p>This lock is re-entrant; acquiring more than one non-exclusive lock per thread is
+     * supported, and must be matched by an equal number of {@link #releaseLock} calls.</p>
+     *
+     * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+     *         was already closed.
+     *
+     * @throws IllegalStateException if this thread is already holding an exclusive lock
+     */
+    public ScopedLock acquireLock() {
+
+        int ownedLocks;
+
+        try {
+            mLock.lock();
+
+            // Lock is already closed, all further acquisitions will fail
+            if (mClosed) {
+                log("acquire lock early aborted (already closed)");
+                return null;
+            }
+
+            ownedLocks = mLockCount.get();
+
+            // This thread is already holding an exclusive lock
+            if (mExclusive && ownedLocks > 0) {
+                throw new IllegalStateException(
+                        "Cannot acquire shared lock while holding exclusive lock");
+            }
+
+            // Is another thread holding the exclusive lock? Block until we can get in.
+            while (mExclusive) {
+                mCondition.awaitUninterruptibly();
+
+                // Did another thread #close while we were waiting? Unblock immediately.
+                if (mClosed) {
+                    log("acquire lock unblocked aborted (already closed)");
+                    return null;
+                }
+            }
+
+            mSharedLocks++;
+
+            ownedLocks = mLockCount.get() + 1;
+            mLockCount.set(ownedLocks);
+        } finally {
+            mLock.unlock();
+        }
+
+        log("acquired lock (local own count = " + ownedLocks + "");
+        return new ScopedLock();
+    }
+
+    /**
+     * Try to acquire the lock exclusively, blocking until all other threads release their locks.
+     *
+     * <p>If the lock has already been closed, or being closed before this operation returns,
+     * the call will immediately return {@code false}.</p>
+     *
+     * <p>If any other threads are holding a lock, this thread will block until all
+     * other locks are released.</p>
+     *
+     * <p>This lock is re-entrant; acquiring more than one exclusive lock per thread is supported,
+     * and must be matched by an equal number of {@link #releaseLock} calls.</p>
+     *
+     * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+     *         was already closed.
+     *
+     * @throws IllegalStateException
+     *          if an attempt is made to acquire an exclusive lock while already holding a lock
+     */
+    public ScopedLock acquireExclusiveLock() {
+
+        int ownedLocks;
+
+        try {
+            mLock.lock();
+
+            // Lock is already closed, all further acquisitions will fail
+            if (mClosed) {
+                log("acquire exclusive lock early aborted (already closed)");
+                return null;
+            }
+
+            ownedLocks = mLockCount.get();
+
+            // This thread is already holding a shared lock
+            if (!mExclusive && ownedLocks > 0) {
+                throw new IllegalStateException(
+                        "Cannot acquire exclusive lock while holding shared lock");
+            }
+
+            /*
+             * Is another thread holding the lock? Block until we can get in.
+             *
+             * If we are already holding the lock, always let it through since
+             * we are just reentering the exclusive lock.
+             */
+            while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) {
+                mCondition.awaitUninterruptibly();
+
+             // Did another thread #close while we were waiting? Unblock immediately.
+                if (mClosed) {
+                    log("acquire exclusive lock unblocked aborted (already closed)");
+                    return null;
+                }
+            }
+
+            mExclusive = true;
+
+            ownedLocks = mLockCount.get() + 1;
+            mLockCount.set(ownedLocks);
+        } finally {
+            mLock.unlock();
+        }
+
+        log("acquired exclusive lock (local own count = " + ownedLocks + "");
+        return new ScopedLock();
+    }
+
+    /**
+     * Release a single lock that was acquired.
+     *
+     * <p>Any other other that is blocked and trying to acquire a lock will get a chance
+     * to acquire the lock.</p>
+     *
+     * @throws IllegalStateException if no locks were acquired, or if the lock was already closed
+     */
+    public void releaseLock() {
+        if (mLockCount.get() <= 0) {
+            throw new IllegalStateException(
+                    "Cannot release lock that was not acquired by this thread");
+        }
+
+        int ownedLocks;
+
+        try {
+            mLock.lock();
+
+            // Lock is already closed, it couldn't have been acquired in the first place
+            if (mClosed) {
+                throw new IllegalStateException("Do not release after the lock has been closed");
+            }
+
+            if (!mExclusive) {
+                mSharedLocks--;
+            } else {
+                if (mSharedLocks != 0) {
+                    throw new AssertionError("Too many shared locks " + mSharedLocks);
+                }
+            }
+
+            ownedLocks = mLockCount.get() - 1;
+            mLockCount.set(ownedLocks);
+
+            if (ownedLocks == 0 && mExclusive) {
+                // Wake up any threads that might be waiting for the exclusive lock to be released
+                mExclusive = false;
+                mCondition.signalAll();
+            } else if (ownedLocks == 0 && mSharedLocks == 0) {
+                // Wake up any threads that might be trying to get the exclusive lock
+                mCondition.signalAll();
+            }
+        } finally {
+            mLock.unlock();
+        }
+
+        log("released lock (local lock count " + ownedLocks + ")");
+    }
+
+    private void log(String what) {
+        if (VERBOSE) {
+            Log.v(TAG + "[" + mName + "]", what);
+        }
+    }
+
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 9218c11..53e87a6 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@
 import android.nfc.INfcAdapterExtras;
 import android.nfc.INfcTag;
 import android.nfc.INfcCardEmulation;
+import android.nfc.INfcLockscreenDispatch;
 import android.os.Bundle;
 
 /**
@@ -52,4 +53,6 @@
 
     void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
     void setP2pModes(int initatorModes, int targetModes);
+
+    void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList);
 }
diff --git a/core/java/android/nfc/INfcLockscreenDispatch.aidl b/core/java/android/nfc/INfcLockscreenDispatch.aidl
new file mode 100644
index 0000000..27e9a8c
--- /dev/null
+++ b/core/java/android/nfc/INfcLockscreenDispatch.aidl
@@ -0,0 +1,12 @@
+package android.nfc;
+
+import android.nfc.Tag;
+
+/**
+ * @hide
+ */
+interface INfcLockscreenDispatch {
+
+    boolean onTagDetected(in Tag tag);
+
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index dd8e41c..8991066 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -380,6 +380,16 @@
         public Uri[] createBeamUris(NfcEvent event);
     }
 
+
+    /**
+     * A callback to be invoked when an application has registered for receiving
+     * tags at the lockscreen.
+     */
+    public interface NfcLockscreenDispatch {
+        public boolean onTagDetected(Tag tag);
+    }
+
+
     /**
      * Helper to check if this device has FEATURE_NFC, but without using
      * a context.
@@ -1417,6 +1427,23 @@
         }
     }
 
+    public boolean registerLockscreenDispatch(final NfcLockscreenDispatch lockscreenDispatch,
+                                           int[] techList) {
+        try {
+            sService.registerLockscreenDispatch(new INfcLockscreenDispatch.Stub() {
+                @Override
+                public boolean onTagDetected(Tag tag) throws RemoteException {
+                    return lockscreenDispatch.onTagDetected(tag);
+                }
+            }, techList);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 1e7d7f1..8038a38 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1694,7 +1694,7 @@
          */
         public static final class Entity implements BaseColumns, ContactsColumns,
                 ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
-                StatusColumns, ContactOptionsColumns, ContactStatusColumns {
+                StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns {
             /**
              * no public constructor since this is a utility class
              */
@@ -7813,6 +7813,25 @@
                 "pinned_position_update");
 
         /**
+         * <p>
+         * The method to invoke in order to undemote a formerly demoted contact. The contact id of
+         * the contact must be provided as an argument. If the contact was not previously demoted,
+         * nothing will be done.
+         * </p>
+         *
+         * <p>
+         * Example:
+         * <pre>
+         * final long contactId = 10;
+         * resolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
+         *         String.valueOf(contactId), null);
+         * </pre>
+         *
+         * </p>
+         */
+        public static final String UNDEMOTE_METHOD = "undemote";
+
+        /**
          * Default value for the pinned position of an unpinned contact. Also equal to
          * {@link Integer#MAX_VALUE}.
          */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 06c05ee..9b6f0ea 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2460,6 +2460,18 @@
         public static final String POINTER_SPEED = "pointer_speed";
 
         /**
+         * Whether lock-to-app will be triggered by long-press on recents.
+         * @hide
+         */
+        public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
+
+        /**
+         * Whether lock-to-app will lock the keyguard when exiting.
+         * @hide
+         */
+        public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked";
+
+        /**
          * I am the lolrus.
          * <p>
          * Nonzero values indicate that the user has a bukkit.
@@ -4537,12 +4549,6 @@
         public static final String SMS_DEFAULT_APPLICATION = "sms_default_application";
 
         /**
-         * Specifies the package name currently configured to be the primary phone application
-         * @hide
-         */
-        public static final String PHONE_DEFAULT_APPLICATION = "phone_default_application";
-
-        /**
          * Name of a package that the current user has explicitly allowed to see all of that
          * user's notifications.
          *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 89c2f37..d25ef16 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14240,6 +14240,7 @@
      * @return True if the view is attached to a window and the window is
      *         hardware accelerated; false in any other case.
      */
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isHardwareAccelerated() {
         return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
     }
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index af1de78..3f72b4c 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -52,7 +52,7 @@
      * The View whose properties are being animated by this class. This is set at
      * construction time.
      */
-    private final View mView;
+    final View mView;
 
     /**
      * The duration of the underlying Animator object. By default, we don't set the duration
@@ -253,10 +253,9 @@
     ViewPropertyAnimator(View view) {
         mView = view;
         view.ensureTransformationInfo();
-        // TODO: Disabled because of b/15287046
-        //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
-        //    mRTBackend = new ViewPropertyAnimatorRT(view);
-        //}
+        if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+            mRTBackend = new ViewPropertyAnimatorRT(view);
+        }
     }
 
     /**
@@ -434,6 +433,9 @@
         }
         mPendingAnimations.clear();
         mView.removeCallbacks(mAnimationStarter);
+        if (mRTBackend != null) {
+            mRTBackend.cancelAll();
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java
index 709efdb..8b4277a 100644
--- a/core/java/android/view/ViewPropertyAnimatorRT.java
+++ b/core/java/android/view/ViewPropertyAnimatorRT.java
@@ -50,6 +50,15 @@
         return true;
     }
 
+    public void cancelAll() {
+        for (int i = 0; i < mAnimators.length; i++) {
+            if (mAnimators[i] != null) {
+                mAnimators[i].cancel();
+                mAnimators[i] = null;
+            }
+        }
+    }
+
     private void doStartAnimation(ViewPropertyAnimator parent) {
         int size = parent.mPendingAnimations.size();
 
@@ -63,12 +72,22 @@
             NameValuesHolder holder = parent.mPendingAnimations.get(i);
             int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
 
-            RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue);
+            final float finalValue = holder.mFromValue + holder.mDeltaValue;
+            RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);
             animator.setStartDelay(startDelay);
             animator.setDuration(duration);
             animator.setInterpolator(interpolator);
             animator.setTarget(mView);
             animator.start();
+
+            // Alpha is a special snowflake that has the canonical value stored
+            // in mTransformationInfo instead of in RenderNode, so we need to update
+            // it with the final value here.
+            if (property == RenderNodeAnimator.ALPHA) {
+                // Don't need null check because ViewPropertyAnimator's
+                // ctor calls ensureTransformationInfo()
+                parent.mView.mTransformationInfo.mAlpha = finalValue;
+            }
         }
 
         parent.mPendingAnimations.clear();
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 04b18c1..93810b3 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1326,12 +1326,28 @@
             if (sel != null) {
                positionSelector(INVALID_POSITION, sel);
                mSelectedTop = sel.getTop();
-            } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
-                View child = getChildAt(mMotionPosition - mFirstPosition);
-                if (child != null) positionSelector(mMotionPosition, child);
             } else {
-                mSelectedTop = 0;
-                mSelectorRect.setEmpty();
+                final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN
+                        && mTouchMode < TOUCH_MODE_SCROLL;
+                if (inTouchMode) {
+                    // If the user's finger is down, select the motion position.
+                    final View child = getChildAt(mMotionPosition - mFirstPosition);
+                    if (child != null) {
+                        positionSelector(mMotionPosition, child);
+                    }
+                } else if (mSelectedPosition != INVALID_POSITION) {
+                    // If we had previously positioned the selector somewhere,
+                    // put it back there. It might not match up with the data,
+                    // but it's transitioning out so it's not a big deal.
+                    final View child = getChildAt(mSelectorPosition - mFirstPosition);
+                    if (child != null) {
+                        positionSelector(mSelectorPosition, child);
+                    }
+                } else {
+                    // Otherwise, clear selection.
+                    mSelectedTop = 0;
+                    mSelectorRect.setEmpty();
+                }
             }
 
             // Attempt to restore accessibility focus, if necessary.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index eeb8015..1baeca8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1718,14 +1718,24 @@
                 }
                 mSelectedTop = sel.getTop();
             } else {
-                // If the user's finger is down, select the motion position.
-                // Otherwise, clear selection.
-                if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
+                final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
+                        || mTouchMode == TOUCH_MODE_DONE_WAITING;
+                if (inTouchMode) {
+                    // If the user's finger is down, select the motion position.
                     final View child = getChildAt(mMotionPosition - mFirstPosition);
-                    if (child != null)  {
+                    if (child != null) {
                         positionSelector(mMotionPosition, child);
                     }
+                } else if (mSelectorPosition != INVALID_POSITION) {
+                    // If we had previously positioned the selector somewhere,
+                    // put it back there. It might not match up with the data,
+                    // but it's transitioning out so it's not a big deal.
+                    final View child = getChildAt(mSelectorPosition - mFirstPosition);
+                    if (child != null) {
+                        positionSelector(mSelectorPosition, child);
+                    }
                 } else {
+                    // Otherwise, clear selection.
                     mSelectedTop = 0;
                     mSelectorRect.setEmpty();
                 }
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index cca29cf..9a8380d 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -90,6 +90,7 @@
     private boolean mSplitTrack;
     private CharSequence mTextOn;
     private CharSequence mTextOff;
+    private boolean mShowText;
 
     private int mTouchMode;
     private int mTouchSlop;
@@ -188,6 +189,7 @@
         mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
         mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
         mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
+        mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
         mThumbTextPadding = a.getDimensionPixelSize(
                 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
         mSwitchMinWidth = a.getDimensionPixelSize(
@@ -533,20 +535,43 @@
         requestLayout();
     }
 
+    /**
+     * Sets whether the on/off text should be displayed.
+     *
+     * @param showText {@code true} to display on/off text
+     * @hide
+     */
+    public void setShowText(boolean showText) {
+        if (mShowText != showText) {
+            mShowText = showText;
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return whether the on/off text should be displayed
+     * @hide
+     */
+    public boolean getShowText() {
+        return mShowText;
+    }
+
     @Override
     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mOnLayout == null) {
-            mOnLayout = makeLayout(mTextOn);
-        }
+        if (mShowText) {
+            if (mOnLayout == null) {
+                mOnLayout = makeLayout(mTextOn);
+            }
 
-        if (mOffLayout == null) {
-            mOffLayout = makeLayout(mTextOff);
+            if (mOffLayout == null) {
+                mOffLayout = makeLayout(mTextOff);
+            }
         }
 
         mTrackDrawable.getPadding(mTempRect);
 
-        final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
-                + mThumbTextPadding * 2;
+        final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+                + mThumbTextPadding * 2 : 0;
         mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
 
         final int switchWidth = Math.max(mSwitchMinWidth,
@@ -568,9 +593,10 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(event);
-        Layout layout =  isChecked() ? mOnLayout : mOffLayout;
-        if (layout != null && !TextUtils.isEmpty(layout.getText())) {
-            event.getText().add(layout.getText());
+
+        final CharSequence text = isChecked() ? mTextOn : mTextOff;
+        if (text != null) {
+            event.getText().add(text);
         }
     }
 
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 7e11850..4995ea1d1 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -28,7 +28,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
-import android.webkit.WebViewFactory;
 
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -55,6 +54,11 @@
     // that is done.
     public static long COMMIT_PERIOD = 3*60*60*1000;  // Commit current stats every 3 hours
 
+    // Minimum uptime period before committing.  If the COMMIT_PERIOD has elapsed but
+    // the total uptime has not exceeded this amount, then the commit will be held until
+    // it is reached.
+    public static long COMMIT_UPTIME_PERIOD = 60*60*1000;  // Must have at least 1 hour elapsed
+
     public static final int STATE_NOTHING = -1;
     public static final int STATE_PERSISTENT = 0;
     public static final int STATE_TOP = 1;
@@ -81,6 +85,24 @@
     public static final int PSS_USS_MAXIMUM = 6;
     public static final int PSS_COUNT = PSS_USS_MAXIMUM+1;
 
+    public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0;
+    public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1;
+    public static final int SYS_MEM_USAGE_CACHED_AVERAGE = 2;
+    public static final int SYS_MEM_USAGE_CACHED_MAXIMUM = 3;
+    public static final int SYS_MEM_USAGE_FREE_MINIMUM = 4;
+    public static final int SYS_MEM_USAGE_FREE_AVERAGE = 5;
+    public static final int SYS_MEM_USAGE_FREE_MAXIMUM = 6;
+    public static final int SYS_MEM_USAGE_ZRAM_MINIMUM = 7;
+    public static final int SYS_MEM_USAGE_ZRAM_AVERAGE = 8;
+    public static final int SYS_MEM_USAGE_ZRAM_MAXIMUM = 9;
+    public static final int SYS_MEM_USAGE_KERNEL_MINIMUM = 10;
+    public static final int SYS_MEM_USAGE_KERNEL_AVERAGE = 11;
+    public static final int SYS_MEM_USAGE_KERNEL_MAXIMUM = 12;
+    public static final int SYS_MEM_USAGE_NATIVE_MINIMUM = 13;
+    public static final int SYS_MEM_USAGE_NATIVE_AVERAGE = 14;
+    public static final int SYS_MEM_USAGE_NATIVE_MAXIMUM = 15;
+    public static final int SYS_MEM_USAGE_COUNT = SYS_MEM_USAGE_NATIVE_MAXIMUM+1;
+
     public static final int ADJ_NOTHING = -1;
     public static final int ADJ_MEM_FACTOR_NORMAL = 0;
     public static final int ADJ_MEM_FACTOR_MODERATE = 1;
@@ -174,7 +196,7 @@
     static final String CSV_SEP = "\t";
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 14;
+    private static final int PARCEL_VERSION = 18;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535453;
 
@@ -200,11 +222,16 @@
     public int mMemFactor = STATE_NOTHING;
     public long mStartTime;
 
+    public int[] mSysMemUsageTable = null;
+    public int mSysMemUsageTableSize = 0;
+    public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
+
     public long mTimePeriodStartClock;
     public long mTimePeriodStartRealtime;
     public long mTimePeriodEndRealtime;
+    public long mTimePeriodStartUptime;
+    public long mTimePeriodEndUptime;
     String mRuntime;
-    String mWebView;
     boolean mRunning;
 
     static final int LONGS_SIZE = 4096;
@@ -304,11 +331,77 @@
             mMemFactorDurations[i] += other.mMemFactorDurations[i];
         }
 
+        for (int i=0; i<other.mSysMemUsageTableSize; i++) {
+            int ent = other.mSysMemUsageTable[i];
+            int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+            long[] longs = other.mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            addSysMemUsage(state, longs, ((ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK));
+        }
+
         if (other.mTimePeriodStartClock < mTimePeriodStartClock) {
             mTimePeriodStartClock = other.mTimePeriodStartClock;
             mTimePeriodStartClockStr = other.mTimePeriodStartClockStr;
         }
         mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
+        mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
+    }
+
+    public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem,
+            long nativeMem) {
+        if (mMemFactor != STATE_NOTHING) {
+            int state = mMemFactor * STATE_COUNT;
+            mSysMemUsageArgs[SYS_MEM_USAGE_SAMPLE_COUNT] = 1;
+            for (int i=0; i<3; i++) {
+                mSysMemUsageArgs[SYS_MEM_USAGE_CACHED_MINIMUM + i] = cachedMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_FREE_MINIMUM + i] = freeMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_ZRAM_MINIMUM + i] = zramMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_KERNEL_MINIMUM + i] = kernelMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_NATIVE_MINIMUM + i] = nativeMem;
+            }
+            addSysMemUsage(state, mSysMemUsageArgs, 0);
+        }
+    }
+
+    void addSysMemUsage(int state, long[] data, int dataOff) {
+        int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+        int off;
+        if (idx >= 0) {
+            off = mSysMemUsageTable[idx];
+        } else {
+            mAddLongTable = mSysMemUsageTable;
+            mAddLongTableSize = mSysMemUsageTableSize;
+            off = addLongData(~idx, state, SYS_MEM_USAGE_COUNT);
+            mSysMemUsageTable = mAddLongTable;
+            mSysMemUsageTableSize = mAddLongTableSize;
+        }
+        long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+        idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK;
+        addSysMemUsage(longs, idx, data, dataOff);
+    }
+
+    static void addSysMemUsage(long[] dstData, int dstOff, long[] addData, int addOff) {
+        final long dstCount = dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+        final long addCount = addData[addOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+        if (dstCount == 0) {
+            dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = addCount;
+            for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i++) {
+                dstData[dstOff+i] = addData[addOff+i];
+            }
+        } else if (addCount > 0) {
+            dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = dstCount + addCount;
+            for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i+=3) {
+                if (dstData[dstOff+i] > addData[addOff+i]) {
+                    dstData[dstOff+i] = addData[addOff+i];
+                }
+                dstData[dstOff+i+1] = (long)(
+                        ((dstData[dstOff+i+1]*(double)dstCount)
+                                + (addData[addOff+i+1]*(double)addCount))
+                                / (dstCount+addCount) );
+                if (dstData[dstOff+i+2] < addData[addOff+i+2]) {
+                    dstData[dstOff+i+2] = addData[addOff+i+2];
+                }
+            }
+        }
     }
 
     public static final Parcelable.Creator<ProcessStats> CREATOR
@@ -564,6 +657,164 @@
         return totalTime;
     }
 
+    static class PssAggr {
+        long pss = 0;
+        long samples = 0;
+
+        void add(long newPss, long newSamples) {
+            pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) )
+                    / (samples+newSamples);
+            samples += newSamples;
+        }
+    }
+
+    public void computeTotalMemoryUse(TotalMemoryUseCollection data, long now) {
+        data.totalTime = 0;
+        for (int i=0; i<STATE_COUNT; i++) {
+            data.processStateWeight[i] = 0;
+            data.processStatePss[i] = 0;
+            data.processStateTime[i] = 0;
+            data.processStateSamples[i] = 0;
+        }
+        for (int i=0; i<SYS_MEM_USAGE_COUNT; i++) {
+            data.sysMemUsage[i] = 0;
+        }
+        data.sysMemCachedWeight = 0;
+        data.sysMemFreeWeight = 0;
+        data.sysMemZRamWeight = 0;
+        data.sysMemKernelWeight = 0;
+        data.sysMemNativeWeight = 0;
+        data.sysMemSamples = 0;
+        long[] totalMemUsage = new long[SYS_MEM_USAGE_COUNT];
+        for (int i=0; i<mSysMemUsageTableSize; i++) {
+            int ent = mSysMemUsageTable[i];
+            long[] longs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            int idx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+            addSysMemUsage(totalMemUsage, 0, longs, idx);
+        }
+        for (int is=0; is<data.screenStates.length; is++) {
+            for (int im=0; im<data.memStates.length; im++) {
+                int memBucket = data.screenStates[is] + data.memStates[im];
+                int stateBucket = memBucket * STATE_COUNT;
+                long memTime = mMemFactorDurations[memBucket];
+                if (mMemFactor == memBucket) {
+                    memTime += now - mStartTime;
+                }
+                data.totalTime += memTime;
+                int sysIdx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, stateBucket);
+                long[] longs = totalMemUsage;
+                int idx = 0;
+                if (sysIdx >= 0) {
+                    int ent = mSysMemUsageTable[sysIdx];
+                    long[] tmpLongs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+                    int tmpIdx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+                    if (tmpLongs[tmpIdx+SYS_MEM_USAGE_SAMPLE_COUNT] >= 3) {
+                        addSysMemUsage(data.sysMemUsage, 0, longs, idx);
+                        longs = tmpLongs;
+                        idx = tmpIdx;
+                    }
+                }
+                data.sysMemCachedWeight += longs[idx+SYS_MEM_USAGE_CACHED_AVERAGE]
+                        * (double)memTime;
+                data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE]
+                        * (double)memTime;
+                data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE]
+                        * (double)memTime;
+                data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE]
+                        * (double)memTime;
+                data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE]
+                        * (double)memTime;
+                data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT];
+             }
+        }
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int iproc=0; iproc<procMap.size(); iproc++) {
+            SparseArray<ProcessState> uids = procMap.valueAt(iproc);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final ProcessState proc = uids.valueAt(iu);
+                final PssAggr fgPss = new PssAggr();
+                final PssAggr bgPss = new PssAggr();
+                final PssAggr cachedPss = new PssAggr();
+                boolean havePss = false;
+                for (int i=0; i<proc.mDurationsTableSize; i++) {
+                    int off = proc.mDurationsTable[i];
+                    int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                    int procState = type % STATE_COUNT;
+                    long samples = proc.getPssSampleCount(type);
+                    if (samples > 0) {
+                        long avg = proc.getPssAverage(type);
+                        havePss = true;
+                        if (procState <= STATE_IMPORTANT_FOREGROUND) {
+                            fgPss.add(avg, samples);
+                        } else if (procState <= STATE_RECEIVER) {
+                            bgPss.add(avg, samples);
+                        } else {
+                            cachedPss.add(avg, samples);
+                        }
+                    }
+                }
+                if (!havePss) {
+                    continue;
+                }
+                boolean fgHasBg = false;
+                boolean fgHasCached = false;
+                boolean bgHasCached = false;
+                if (fgPss.samples < 3 && bgPss.samples > 0) {
+                    fgHasBg = true;
+                    fgPss.add(bgPss.pss, bgPss.samples);
+                }
+                if (fgPss.samples < 3 && cachedPss.samples > 0) {
+                    fgHasCached = true;
+                    fgPss.add(cachedPss.pss, cachedPss.samples);
+                }
+                if (bgPss.samples < 3 && cachedPss.samples > 0) {
+                    bgHasCached = true;
+                    bgPss.add(cachedPss.pss, cachedPss.samples);
+                }
+                if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) {
+                    bgPss.add(fgPss.pss, fgPss.samples);
+                }
+                if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) {
+                    cachedPss.add(bgPss.pss, bgPss.samples);
+                }
+                if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) {
+                    cachedPss.add(fgPss.pss, fgPss.samples);
+                }
+                for (int i=0; i<proc.mDurationsTableSize; i++) {
+                    final int off = proc.mDurationsTable[i];
+                    final int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                    long time = getLong(off, 0);
+                    if (proc.mCurState == type) {
+                        time += now - proc.mStartTime;
+                    }
+                    final int procState = type % STATE_COUNT;
+                    data.processStateTime[procState] += time;
+                    long samples = proc.getPssSampleCount(type);
+                    long avg;
+                    if (samples > 0) {
+                        avg = proc.getPssAverage(type);
+                    } else if (procState <= STATE_IMPORTANT_FOREGROUND) {
+                        samples = fgPss.samples;
+                        avg = fgPss.pss;
+                    } else if (procState <= STATE_RECEIVER) {
+                        samples = bgPss.samples;
+                        avg = bgPss.pss;
+                    } else {
+                        samples = cachedPss.samples;
+                        avg = cachedPss.pss;
+                    }
+                    double newAvg = ( (data.processStatePss[procState]
+                            * (double)data.processStateSamples[procState])
+                                + (avg*(double)samples)
+                            ) / (data.processStateSamples[procState]+samples);
+                    data.processStatePss[procState] = (long)newAvg;
+                    data.processStateSamples[procState] += samples;
+                    data.processStateWeight[procState] += avg * (double)time;
+                }
+            }
+        }
+    }
+
     static void dumpProcessState(PrintWriter pw, String prefix, ProcessState proc,
             int[] screenStates, int[] memStates, int[] procStates, long now) {
         long totalTime = 0;
@@ -679,6 +930,62 @@
         }
     }
 
+    long getSysMemUsageValue(int state, int index) {
+        int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+        return idx >= 0 ? getLong(mSysMemUsageTable[idx], index) : 0;
+    }
+
+    void dumpSysMemUsageCategory(PrintWriter pw, String prefix, String label,
+            int bucket, int index) {
+        pw.print(prefix); pw.print(label);
+        pw.print(": ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+        pw.print(" min, ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+        pw.print(" avg, ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+        pw.println(" max");
+    }
+
+    void dumpSysMemUsage(PrintWriter pw, String prefix, int[] screenStates,
+            int[] memStates) {
+        int printedScreen = -1;
+        for (int is=0; is<screenStates.length; is++) {
+            int printedMem = -1;
+            for (int im=0; im<memStates.length; im++) {
+                final int iscreen = screenStates[is];
+                final int imem = memStates[im];
+                final int bucket = ((iscreen + imem) * STATE_COUNT);
+                long count = getSysMemUsageValue(bucket, SYS_MEM_USAGE_SAMPLE_COUNT);
+                if (count > 0) {
+                    pw.print(prefix);
+                    if (screenStates.length > 1) {
+                        printScreenLabel(pw, printedScreen != iscreen
+                                ? iscreen : STATE_NOTHING);
+                        printedScreen = iscreen;
+                    }
+                    if (memStates.length > 1) {
+                        printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, '\0');
+                        printedMem = imem;
+                    }
+                    pw.print(": ");
+                    pw.print(count);
+                    pw.println(" samples:");
+                    dumpSysMemUsageCategory(pw, prefix, "  Cached", bucket,
+                            SYS_MEM_USAGE_CACHED_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Free", bucket,
+                            SYS_MEM_USAGE_FREE_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  ZRam", bucket,
+                            SYS_MEM_USAGE_ZRAM_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Kernel", bucket,
+                            SYS_MEM_USAGE_KERNEL_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Native", bucket,
+                            SYS_MEM_USAGE_NATIVE_MINIMUM);
+                }
+            }
+        }
+    }
+
     static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates,
             int[] memStates, int[] procStates) {
         final int NS = screenStates != null ? screenStates.length : 1;
@@ -1088,10 +1395,13 @@
         mTimePeriodStartClock = System.currentTimeMillis();
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+        mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis();
         mLongs.clear();
         mLongs.add(new long[LONGS_SIZE]);
         mNextLong = 0;
         Arrays.fill(mMemFactorDurations, 0);
+        mSysMemUsageTable = null;
+        mSysMemUsageTableSize = 0;
         mStartTime = 0;
         mReadError = null;
         mFlags = 0;
@@ -1220,12 +1530,17 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        long now = SystemClock.uptimeMillis();
+        writeToParcel(out, SystemClock.uptimeMillis(), flags);
+    }
+
+    /** @hide */
+    public void writeToParcel(Parcel out, long now, int flags) {
         out.writeInt(MAGIC);
         out.writeInt(PARCEL_VERSION);
         out.writeInt(STATE_COUNT);
         out.writeInt(ADJ_COUNT);
         out.writeInt(PSS_COUNT);
+        out.writeInt(SYS_MEM_USAGE_COUNT);
         out.writeInt(LONGS_SIZE);
 
         mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
@@ -1268,8 +1583,9 @@
         out.writeLong(mTimePeriodStartClock);
         out.writeLong(mTimePeriodStartRealtime);
         out.writeLong(mTimePeriodEndRealtime);
+        out.writeLong(mTimePeriodStartUptime);
+        out.writeLong(mTimePeriodEndUptime);
         out.writeString(mRuntime);
-        out.writeString(mWebView);
         out.writeInt(mFlags);
 
         out.writeInt(mLongs.size());
@@ -1287,6 +1603,13 @@
         }
         writeCompactedLongArray(out, mMemFactorDurations, mMemFactorDurations.length);
 
+        out.writeInt(mSysMemUsageTableSize);
+        for (int i=0; i<mSysMemUsageTableSize; i++) {
+            if (DEBUG_PARCEL) Slog.i(TAG, "Writing sys mem usage #" + i + ": "
+                    + printLongOffset(mSysMemUsageTable[i]));
+            out.writeInt(mSysMemUsageTable[i]);
+        }
+
         out.writeInt(NPROC);
         for (int ip=0; ip<NPROC; ip++) {
             writeCommonString(out, procMap.keyAt(ip));
@@ -1417,6 +1740,9 @@
         if (!readCheckedInt(in, PSS_COUNT, "pss count")) {
             return;
         }
+        if (!readCheckedInt(in, SYS_MEM_USAGE_COUNT, "sys mem usage count")) {
+            return;
+        }
         if (!readCheckedInt(in, LONGS_SIZE, "longs size")) {
             return;
         }
@@ -1427,8 +1753,9 @@
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = in.readLong();
         mTimePeriodEndRealtime = in.readLong();
+        mTimePeriodStartUptime = in.readLong();
+        mTimePeriodEndUptime = in.readLong();
         mRuntime = in.readString();
-        mWebView = in.readString();
         mFlags = in.readInt();
 
         final int NLONGS = in.readInt();
@@ -1447,6 +1774,12 @@
 
         readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length);
 
+        mSysMemUsageTable = readTableFromParcel(in, TAG, "sys mem usage");
+        if (mSysMemUsageTable == BAD_TABLE) {
+            return;
+        }
+        mSysMemUsageTableSize = mSysMemUsageTable != null ? mSysMemUsageTable.length : 0;
+
         int NPROC = in.readInt();
         if (NPROC < 0) {
             mReadError = "bad process count: " + NPROC;
@@ -1826,6 +2159,10 @@
             boolean dumpAll, boolean activeOnly) {
         long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
                 mStartTime, now);
+        if (mSysMemUsageTable != null) {
+            pw.println("System memory usage:");
+            dumpSysMemUsage(pw, "  ", ALL_SCREEN_ADJ, ALL_MEM_ADJ);
+        }
         ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
         boolean printedHeader = false;
         boolean sepNeeded = false;
@@ -2089,10 +2426,57 @@
         dumpTotalsLocked(pw, now);
     }
 
+    long printMemoryCategory(PrintWriter pw, String prefix, String label, double memWeight,
+            long totalTime, long curTotalMem, int samples) {
+        if (memWeight != 0) {
+            long mem = (long)(memWeight * 1024 / totalTime);
+            pw.print(prefix);
+            pw.print(label);
+            pw.print(": ");
+            printSizeValue(pw, mem);
+            pw.print(" (");
+            pw.print(samples);
+            pw.print(" samples)");
+            pw.println();
+            return curTotalMem + mem;
+        }
+        return curTotalMem;
+    }
+
     void dumpTotalsLocked(PrintWriter pw, long now) {
         pw.println("Run time Stats:");
         dumpSingleTime(pw, "  ", mMemFactorDurations, mMemFactor, mStartTime, now);
         pw.println();
+        pw.println("Memory usage:");
+        TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+                ALL_MEM_ADJ);
+        computeTotalMemoryUse(totalMem, now);
+        long totalPss = 0;
+        totalPss = printMemoryCategory(pw, "  ", "Kernel ", totalMem.sysMemKernelWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Native ", totalMem.sysMemNativeWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        for (int i=0; i<STATE_COUNT; i++) {
+            // Skip restarting service state -- that is not actually a running process.
+            if (i != STATE_SERVICE_RESTARTING) {
+                totalPss = printMemoryCategory(pw, "  ", STATE_NAMES[i],
+                        totalMem.processStateWeight[i], totalMem.totalTime, totalPss,
+                        totalMem.processStateSamples[i]);
+            }
+        }
+        totalPss = printMemoryCategory(pw, "  ", "Cached ", totalMem.sysMemCachedWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Free   ", totalMem.sysMemFreeWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Z-Ram   ", totalMem.sysMemZRamWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        pw.print("  TOTAL  : ");
+        printSizeValue(pw, totalPss);
+        pw.println();
+        printMemoryCategory(pw, "  ", STATE_NAMES[STATE_SERVICE_RESTARTING],
+                totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
+                totalMem.processStateSamples[STATE_SERVICE_RESTARTING]);
+        pw.println();
         pw.print("          Start time: ");
         pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
         pw.println();
@@ -2118,8 +2502,6 @@
         }
         pw.print(' ');
         pw.print(mRuntime);
-        pw.print(' ');
-        pw.print(mWebView);
         pw.println();
     }
 
@@ -2208,7 +2590,7 @@
     public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
         final long now = SystemClock.uptimeMillis();
         final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
-        pw.println("vers,4");
+        pw.println("vers,5");
         pw.print("period,"); pw.print(mTimePeriodStartClockStr);
         pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
         pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2229,7 +2611,7 @@
             pw.print(",partial");
         }
         pw.println();
-        pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
+        pw.print("config,"); pw.println(mRuntime);
         for (int ip=0; ip<pkgMap.size(); ip++) {
             final String pkgName = pkgMap.keyAt(ip);
             if (reqPackage != null && !reqPackage.equals(pkgName)) {
@@ -2362,6 +2744,53 @@
         pw.print("total");
         dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor,
                 mStartTime, now);
+        if (mSysMemUsageTable != null) {
+            pw.print("sysmemusage");
+            for (int i=0; i<mSysMemUsageTableSize; i++) {
+                int off = mSysMemUsageTable[i];
+                int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                pw.print(",");
+                printProcStateTag(pw, type);
+                for (int j=SYS_MEM_USAGE_SAMPLE_COUNT; j<SYS_MEM_USAGE_COUNT; j++) {
+                    if (j > SYS_MEM_USAGE_CACHED_MINIMUM) {
+                        pw.print(":");
+                    }
+                    pw.print(getLong(off, j));
+                }
+            }
+        }
+        pw.println();
+        TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+                ALL_MEM_ADJ);
+        computeTotalMemoryUse(totalMem, now);
+        pw.print("weights,");
+        pw.print(totalMem.totalTime);
+        pw.print(",");
+        pw.print(totalMem.sysMemCachedWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemFreeWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemZRamWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemKernelWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemNativeWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        for (int i=0; i<STATE_COUNT; i++) {
+            pw.print(",");
+            pw.print(totalMem.processStateWeight[i]);
+            pw.print(":");
+            pw.print(totalMem.processStateSamples[i]);
+        }
         pw.println();
     }
 
@@ -2452,6 +2881,15 @@
         }
     }
 
+    final public static class ProcessStateHolder {
+        public final int appVersion;
+        public ProcessStats.ProcessState state;
+
+        public ProcessStateHolder(int _appVersion) {
+            appVersion = _appVersion;
+        }
+    }
+
     public static final class ProcessState extends DurationsTable {
         public ProcessState mCommonProcess;
         public final String mPackage;
@@ -2660,7 +3098,7 @@
          * @param pkgList Processes to update.
          */
         public void setState(int state, int memFactor, long now,
-                ArrayMap<String, ProcessState> pkgList) {
+                ArrayMap<String, ProcessStateHolder> pkgList) {
             if (state < 0) {
                 state = mNumStartedServices > 0
                         ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
@@ -2770,7 +3208,7 @@
         }
 
         public void addPss(long pss, long uss, boolean always,
-                ArrayMap<String, ProcessState> pkgList) {
+                ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             if (!always) {
                 if (mLastPssState == mCurState && SystemClock.uptimeMillis()
@@ -2845,7 +3283,7 @@
             }
         }
 
-        public void reportExcessiveWake(ArrayMap<String, ProcessState> pkgList) {
+        public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             mCommonProcess.mNumExcessiveWake++;
             if (!mCommonProcess.mMultiPackage) {
@@ -2857,7 +3295,7 @@
             }
         }
 
-        public void reportExcessiveCpu(ArrayMap<String, ProcessState> pkgList) {
+        public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             mCommonProcess.mNumExcessiveCpu++;
             if (!mCommonProcess.mMultiPackage) {
@@ -2888,7 +3326,7 @@
             }
         }
 
-        public void reportCachedKill(ArrayMap<String, ProcessState> pkgList, long pss) {
+        public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
             ensureNotDead();
             mCommonProcess.addCachedKill(1, pss, pss, pss);
             if (!mCommonProcess.mMultiPackage) {
@@ -2925,8 +3363,10 @@
             return this;
         }
 
-        private ProcessState pullFixedProc(ArrayMap<String, ProcessState> pkgList, int index) {
-            ProcessState proc = pkgList.valueAt(index);
+        private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList,
+                int index) {
+            ProcessStateHolder holder = pkgList.valueAt(index);
+            ProcessState proc = holder.state;
             if (mDead && proc.mCommonProcess != proc) {
                 // Somehow we are contining to use a process state that is dead, because
                 // it was not being told it was active during the last commit.  We can recover
@@ -2959,7 +3399,7 @@
                     throw new IllegalStateException("Didn't create per-package process "
                             + proc.mName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
                 }
-                pkgList.setValueAt(index, proc);
+                holder.state = proc;
             }
             return proc;
         }
@@ -3351,4 +3791,27 @@
             }
         }
     }
+
+    public static class TotalMemoryUseCollection {
+        final int[] screenStates;
+        final int[] memStates;
+
+        public TotalMemoryUseCollection(int[] _screenStates, int[] _memStates) {
+            screenStates = _screenStates;
+            memStates = _memStates;
+        }
+
+        public long totalTime;
+        public long[] processStatePss = new long[STATE_COUNT];
+        public double[] processStateWeight = new double[STATE_COUNT];
+        public long[] processStateTime = new long[STATE_COUNT];
+        public int[] processStateSamples = new int[STATE_COUNT];
+        public long[] sysMemUsage = new long[SYS_MEM_USAGE_COUNT];
+        public double sysMemCachedWeight;
+        public double sysMemFreeWeight;
+        public double sysMemZRamWeight;
+        public double sysMemKernelWeight;
+        public double sysMemNativeWeight;
+        public int sysMemSamples;
+    }
 }
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index ff0ee65..b098de8 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -93,6 +93,9 @@
 
     private File mFullRestoreSetDir;
     private HashSet<String> mFullRestorePackages;
+    private FileInputStream mCurFullRestoreStream;
+    private FileOutputStream mFullRestoreSocketStream;
+    private byte[] mFullRestoreBuffer;
 
     public LocalTransport(Context context) {
         mContext = context;
@@ -104,34 +107,41 @@
         }
     }
 
+    @Override
     public String name() {
         return new ComponentName(mContext, this.getClass()).flattenToShortString();
     }
 
+    @Override
     public Intent configurationIntent() {
         // The local transport is not user-configurable
         return null;
     }
 
+    @Override
     public String currentDestinationString() {
         return TRANSPORT_DESTINATION_STRING;
     }
 
+    @Override
     public String transportDirName() {
         return TRANSPORT_DIR_NAME;
     }
 
+    @Override
     public long requestBackupTime() {
         // any time is a good time for local backup
         return 0;
     }
 
+    @Override
     public int initializeDevice() {
         if (DEBUG) Log.v(TAG, "wiping all data");
         deleteContents(mCurrentSetDir);
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
         if (DEBUG) {
             try {
@@ -191,7 +201,7 @@
                         entity.write(buf, 0, dataSize);
                     } catch (IOException e) {
                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
-                        return BackupTransport.TRANSPORT_ERROR;
+                        return TRANSPORT_ERROR;
                     } finally {
                         entity.close();
                     }
@@ -199,11 +209,11 @@
                     entityFile.delete();
                 }
             }
-            return BackupTransport.TRANSPORT_OK;
+            return TRANSPORT_OK;
         } catch (IOException e) {
             // oops, something went wrong.  abort the operation and return error.
             Log.v(TAG, "Exception reading backup input:", e);
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
     }
 
@@ -222,6 +232,7 @@
         }
     }
 
+    @Override
     public int clearBackupData(PackageInfo packageInfo) {
         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
 
@@ -243,9 +254,10 @@
             packageDir.delete();
         }
 
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int finishBackup() {
         if (DEBUG) Log.v(TAG, "finishBackup()");
         if (mSocket != null) {
@@ -259,24 +271,27 @@
                 mFullTargetPackage = null;
                 mSocket.close();
             } catch (IOException e) {
-                return BackupTransport.TRANSPORT_ERROR;
+                return TRANSPORT_ERROR;
             } finally {
                 mSocket = null;
             }
         }
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
     // ------------------------------------------------------------------------------------
     // Full backup handling
+
+    @Override
     public long requestFullBackupTime() {
         return 0;
     }
 
+    @Override
     public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
         if (mSocket != null) {
             Log.e(TAG, "Attempt to initiate full backup while one is in progress");
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         if (DEBUG) {
@@ -291,7 +306,7 @@
             mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
         } catch (IOException e) {
             Log.e(TAG, "Unable to process socket for full backup");
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         mFullTargetPackage = targetPackage.packageName;
@@ -300,18 +315,19 @@
             File tarball = new File(mCurrentSetFullDir, mFullTargetPackage);
             tarstream = new FileOutputStream(tarball);
         } catch (FileNotFoundException e) {
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
         mFullBackupOutputStream = new BufferedOutputStream(tarstream);
         mFullBackupBuffer = new byte[4096];
 
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int sendBackupData(int numBytes) {
         if (mFullBackupBuffer == null) {
             Log.w(TAG, "Attempted sendBackupData before performFullBackup");
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         if (numBytes > mFullBackupBuffer.length) {
@@ -323,21 +339,23 @@
             if (nRead < 0) {
                 // Something went wrong if we expect data but saw EOD
                 Log.w(TAG, "Unexpected EOD; failing backup");
-                return BackupTransport.TRANSPORT_ERROR;
+                return TRANSPORT_ERROR;
             }
             mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
             numBytes -= nRead;
             } catch (IOException e) {
                 Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
-                return BackupTransport.TRANSPORT_ERROR;
+                return TRANSPORT_ERROR;
             }
         }
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
     // ------------------------------------------------------------------------------------
     // Restore handling
     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; 
+
+    @Override
     public RestoreSet[] getAvailableRestoreSets() {
         long[] existing = new long[POSSIBLE_SETS.length + 1];
         int num = 0;
@@ -358,11 +376,13 @@
         return available;
     }
 
+    @Override
     public long getCurrentRestoreSet() {
         // The current restore set always has the same token
         return CURRENT_SET_TOKEN;
     }
 
+    @Override
     public int startRestore(long token, PackageInfo[] packages) {
         if (DEBUG) Log.v(TAG, "start restore " + token);
         mRestorePackages = packages;
@@ -371,7 +391,7 @@
         mRestoreSetDir = new File(mDataDir, Long.toString(token));
         mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
         mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
     @Override
@@ -397,6 +417,7 @@
                 if (maybeFullData.length() > 0) {
                     if (DEBUG) Log.v(TAG, "  nextRestorePackage(TYPE_FULL_STREAM) = " + name);
                     mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
+                    mCurFullRestoreStream = null;   // ensure starting from the ground state
                     found = true;
                 }
             }
@@ -410,6 +431,7 @@
         return RestoreDescription.NO_MORE_PACKAGES;
     }
 
+    @Override
     public int getRestoreData(ParcelFileDescriptor outFd) {
         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
@@ -426,7 +448,7 @@
         ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
             Log.e(TAG, "No keys for package: " + packageDir);
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         // We expect at least some data if the directory exists in the first place
@@ -447,10 +469,10 @@
                     in.close();
                 }
             }
-            return BackupTransport.TRANSPORT_OK;
+            return TRANSPORT_OK;
         } catch (IOException e) {
             Log.e(TAG, "Unable to read backup records", e);
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
     }
 
@@ -487,38 +509,27 @@
         return contents;
     }
 
+    @Override
     public void finishRestore() {
         if (DEBUG) Log.v(TAG, "finishRestore()");
+        if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
+            resetFullRestoreState();
+        }
+        mRestoreType = 0;
     }
 
     // ------------------------------------------------------------------------------------
     // Full restore handling
 
-    public int prepareFullRestore(long token, String[] targetPackages) {
-        mRestoreSetDir = new File(mDataDir, Long.toString(token));
-        mFullRestoreSetDir = new File(mRestoreSetDir, FULL_DATA_DIR);
-        mFullRestorePackages = new HashSet<String>();
-        if (mFullRestoreSetDir.exists()) {
-            List<String> pkgs = Arrays.asList(mFullRestoreSetDir.list());
-            HashSet<String> available = new HashSet<String>(pkgs);
-
-            for (int i = 0; i < targetPackages.length; i++) {
-                if (available.contains(targetPackages[i])) {
-                    mFullRestorePackages.add(targetPackages[i]);
-                }
-            }
+    private void resetFullRestoreState() {
+        try {
+        mCurFullRestoreStream.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to close full restore input stream");
         }
-        return BackupTransport.TRANSPORT_OK;
-    }
-
-    /**
-     * Ask the transport what package's full data will be restored next.  When all apps'
-     * data has been delivered, the transport should return {@code null} here.
-     * @return The package name of the next application whose data will be restored, or
-     *    {@code null} if all available package has been delivered.
-     */
-    public String getNextFullRestorePackage() {
-        return null;
+        mCurFullRestoreStream = null;
+        mFullRestoreSocketStream = null;
+        mFullRestoreBuffer = null;
     }
 
     /**
@@ -543,7 +554,79 @@
      *    indicating a fatal error condition that precludes further restore operations
      *    on the current dataset.
      */
+    @Override
     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
-        return 0;
+        if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+            throw new IllegalStateException("Asked for full restore data for non-stream package");
+        }
+
+        // first chunk?
+        if (mCurFullRestoreStream == null) {
+            final String name = mRestorePackages[mRestorePackage].packageName;
+            if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
+            File dataset = new File(mRestoreSetFullDir, name);
+            try {
+                mCurFullRestoreStream = new FileInputStream(dataset);
+            } catch (IOException e) {
+                // If we can't open the target package's tarball, we return the single-package
+                // error code and let the caller go on to the next package.
+                Log.e(TAG, "Unable to read archive for " + name);
+                return TRANSPORT_PACKAGE_REJECTED;
+            }
+            mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
+            mFullRestoreBuffer = new byte[32*1024];
+        }
+
+        int nRead;
+        try {
+            nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
+            if (nRead < 0) {
+                // EOF: tell the caller we're done
+                nRead = NO_MORE_DATA;
+            } else if (nRead == 0) {
+                // This shouldn't happen when reading a FileInputStream; we should always
+                // get either a positive nonzero byte count or -1.  Log the situation and
+                // treat it as EOF.
+                Log.w(TAG, "read() of archive file returned 0; treating as EOF");
+                nRead = NO_MORE_DATA;
+            } else {
+                if (DEBUG) {
+                    Log.i(TAG, "   delivering restore chunk: " + nRead);
+                }
+                mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
+            }
+        } catch (IOException e) {
+            return TRANSPORT_ERROR;  // Hard error accessing the file; shouldn't happen
+        } finally {
+            // Most transports will need to explicitly close 'socket' here, but this transport
+            // is in the same process as the caller so it can leave it up to the backup manager
+            // to manage both socket fds.
+        }
+
+        return nRead;
     }
+
+    /**
+     * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+     * data for restore, it will invoke this method to tell the transport that it should
+     * abandon the data download for the current package.  The OS will then either call
+     * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+     * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+     * operation.
+     *
+     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+     *    transport-level failure.  If the transport reports an error here, the entire restore
+     *    operation will immediately be finished with no further attempts to restore app data.
+     */
+    @Override
+    public int abortFullRestore() {
+        if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+            throw new IllegalStateException("abortFullRestore() but not currently restoring");
+        }
+        resetFullRestoreState();
+        mRestoreType = 0;
+        return TRANSPORT_OK;
+    }
+
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 240d520..1ccaa0f3 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -291,6 +291,7 @@
     final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
 
     int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+    long mMobileRadioActiveStartTime;
     StopwatchTimer mMobileRadioActiveTimer;
     StopwatchTimer mMobileRadioActivePerAppTimer;
     LongSamplingCounter mMobileRadioActiveAdjustedTime;
@@ -1425,10 +1426,6 @@
             return 0;
         }
 
-        long getLastUpdateTimeMs() {
-            return mUpdateTime;
-        }
-
         void stopRunningLocked(long elapsedRealtimeMs) {
             // Ignore attempt to stop a timer that isn't running
             if (mNesting == 0) {
@@ -2790,11 +2787,11 @@
                     powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
                             || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
             if (active) {
-                realElapsedRealtimeMs = elapsedRealtime;
+                mMobileRadioActiveStartTime = realElapsedRealtimeMs = elapsedRealtime;
                 mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
             } else {
                 realElapsedRealtimeMs = timestampNs / (1000*1000);
-                long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs();
+                long lastUpdateTimeMs = mMobileRadioActiveStartTime;
                 if (realElapsedRealtimeMs < lastUpdateTimeMs) {
                     Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
                             + " is before start time " + lastUpdateTimeMs);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 9998995..c139c9d 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -258,16 +258,16 @@
 
 // can return NULL
 static ToColorProc ChooseToColorProc(const SkBitmap& src, bool isPremultiplied) {
-    switch (src.config()) {
-        case SkBitmap::kARGB_8888_Config:
+    switch (src.colorType()) {
+        case kN32_SkColorType:
             if (src.isOpaque()) return ToColor_S32_Opaque;
             return isPremultiplied ? ToColor_S32_Alpha : ToColor_S32_Raw;
-        case SkBitmap::kARGB_4444_Config:
+        case kARGB_4444_SkColorType:
             if (src.isOpaque()) return ToColor_S4444_Opaque;
             return isPremultiplied ? ToColor_S4444_Alpha : ToColor_S4444_Raw;
-        case SkBitmap::kRGB_565_Config:
+        case kRGB_565_SkColorType:
             return ToColor_S565;
-        case SkBitmap::kIndex8_Config:
+        case kIndex_8_SkColorType:
             if (src.getColorTable() == NULL) {
                 return NULL;
             }
@@ -291,7 +291,7 @@
 static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
                               jint offset, jint stride, jint width, jint height,
                               jint configHandle, jboolean isMutable) {
-    SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle);
+    SkColorType colorType = SkBitmapConfigToColorType(static_cast<SkBitmap::Config>(configHandle));
     if (NULL != jColors) {
         size_t n = env->GetArrayLength(jColors);
         if (n < SkAbs32(stride) * (size_t)height) {
@@ -301,12 +301,12 @@
     }
 
     // ARGB_4444 is a deprecated format, convert automatically to 8888
-    if (config == SkBitmap::kARGB_4444_Config) {
-        config = SkBitmap::kARGB_8888_Config;
+    if (colorType == kARGB_4444_SkColorType) {
+        colorType = kN32_SkColorType;
     }
 
     SkBitmap bitmap;
-    bitmap.setConfig(config, width, height);
+    bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
 
     jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
     if (NULL == buff) {
@@ -515,28 +515,29 @@
 
     android::Parcel* p = android::parcelForJavaObject(env, parcel);
 
-    const bool              isMutable = p->readInt32() != 0;
-    const SkBitmap::Config  config = (SkBitmap::Config)p->readInt32();
-    const int               width = p->readInt32();
-    const int               height = p->readInt32();
-    const int               rowBytes = p->readInt32();
-    const int               density = p->readInt32();
+    const bool        isMutable = p->readInt32() != 0;
+    const SkColorType colorType = (SkColorType)p->readInt32();
+    const SkAlphaType alphaType = (SkAlphaType)p->readInt32();
+    const int         width = p->readInt32();
+    const int         height = p->readInt32();
+    const int         rowBytes = p->readInt32();
+    const int         density = p->readInt32();
 
-    if (SkBitmap::kARGB_8888_Config != config &&
-            SkBitmap::kRGB_565_Config != config &&
-            SkBitmap::kARGB_4444_Config != config &&
-            SkBitmap::kIndex8_Config != config &&
-            SkBitmap::kA8_Config != config) {
-        SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config);
+    if (kN32_SkColorType != colorType &&
+            kRGB_565_SkColorType != colorType &&
+            kARGB_4444_SkColorType != colorType &&
+            kIndex_8_SkColorType != colorType &&
+            kAlpha_8_SkColorType != colorType) {
+        SkDebugf("Bitmap_createFromParcel unknown colortype: %d\n", colorType);
         return NULL;
     }
 
     SkBitmap* bitmap = new SkBitmap;
 
-    bitmap->setConfig(config, width, height, rowBytes);
+    bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes);
 
     SkColorTable* ctable = NULL;
-    if (config == SkBitmap::kIndex8_Config) {
+    if (colorType == kIndex_8_SkColorType) {
         int count = p->readInt32();
         if (count > 0) {
             size_t size = count * sizeof(SkPMColor);
@@ -587,13 +588,14 @@
     android::Parcel* p = android::parcelForJavaObject(env, parcel);
 
     p->writeInt32(isMutable);
-    p->writeInt32(bitmap->config());
+    p->writeInt32(bitmap->colorType());
+    p->writeInt32(bitmap->alphaType());
     p->writeInt32(bitmap->width());
     p->writeInt32(bitmap->height());
     p->writeInt32(bitmap->rowBytes());
     p->writeInt32(density);
 
-    if (bitmap->config() == SkBitmap::kIndex8_Config) {
+    if (bitmap->colorType() == kIndex_8_SkColorType) {
         SkColorTable* ctable = bitmap->getColorTable();
         if (ctable != NULL) {
             int count = ctable->count();
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 5106f0d..86ed677 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -386,7 +386,7 @@
         // FIXME: If the alphaType is kUnpremul and the image has alpha, the
         // colors may not be correct, since Skia does not yet support drawing
         // to/from unpremultiplied bitmaps.
-        outputBitmap->setConfig(SkImageInfo::Make(scaledWidth, scaledHeight,
+        outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
                 colorType, decodingBitmap.alphaType()));
         if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
             return nullObjectReturn("allocation failed for scaled bitmap");
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 935e3a0..9e09280 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -703,10 +703,11 @@
                                 jboolean hasAlpha, jlong paintHandle) {
         SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        SkImageInfo info = SkImageInfo::Make(width, height,
+                               hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+                               kPremul_SkAlphaType);
         SkBitmap    bitmap;
-        bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config :
-                         SkBitmap::kRGB_565_Config, width, height);
-        if (!bitmap.allocPixels()) {
+        if (!bitmap.allocPixels(info)) {
             return;
         }
 
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 3a53331..f8bab24 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -36,6 +36,11 @@
 
 using namespace android;
 
+enum {
+    // Keep up to date with Camera.java
+    CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2,
+};
+
 struct fields_t {
     jfieldID    context;
     jfieldID    facing;
@@ -466,7 +471,7 @@
 
 // connect to camera service
 static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
-    jobject weak_this, jint cameraId, jstring clientPackageName)
+    jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName)
 {
     // Convert jstring to String16
     const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL);
@@ -474,8 +479,18 @@
     String16 clientName(rawClientName, rawClientNameLen);
     env->ReleaseStringChars(clientPackageName, rawClientName);
 
-    sp<Camera> camera = Camera::connect(cameraId, clientName,
-            Camera::USE_CALLING_UID);
+    sp<Camera> camera;
+    if (halVersion == CAMERA_HAL_API_VERSION_NORMAL_CONNECT) {
+        // Default path: hal version is don't care, do normal camera connect.
+        camera = Camera::connect(cameraId, clientName,
+                Camera::USE_CALLING_UID);
+    } else {
+        jint status = Camera::connectLegacy(cameraId, halVersion, clientName,
+                Camera::USE_CALLING_UID, camera);
+        if (status != NO_ERROR) {
+            return status;
+        }
+    }
 
     if (camera == NULL) {
         return -EACCES;
@@ -510,7 +525,6 @@
 // finalizer is invoked later.
 static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
 {
-    // TODO: Change to ALOGV
     ALOGV("release camera");
     JNICameraContext* context = NULL;
     sp<Camera> camera;
@@ -891,7 +905,7 @@
     "(ILandroid/hardware/Camera$CameraInfo;)V",
     (void*)android_hardware_Camera_getCameraInfo },
   { "native_setup",
-    "(Ljava/lang/Object;ILjava/lang/String;)I",
+    "(Ljava/lang/Object;IILjava/lang/String;)I",
     (void*)android_hardware_Camera_native_setup },
   { "native_release",
     "()V",
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index ee4c619..849531c 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -287,6 +287,8 @@
     env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz,
                               "errorCallbackFromNative","(I)V"),
                               check_AudioSystem_Command(err));
+
+    env->DeleteLocalRef(clazz);
 }
 
 static jint
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index dc1ea06..4362018 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -388,11 +388,11 @@
 static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jintArray colors, jint offset, jint stride,
         jfloat left, jfloat top, jint width, jint height, jboolean hasAlpha, jlong paintPtr) {
+    const SkImageInfo info = SkImageInfo::Make(width, height,
+                               hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+                               kPremul_SkAlphaType);
     SkBitmap* bitmap = new SkBitmap;
-    bitmap->setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : SkBitmap::kRGB_565_Config,
-            width, height);
-
-    if (!bitmap->allocPixels()) {
+    if (!bitmap->allocPixels(info)) {
         delete bitmap;
         return;
     }
diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp
index 0210bd9..5ebed9c 100644
--- a/core/jni/android_view_GraphicBuffer.cpp
+++ b/core/jni/android_view_GraphicBuffer.cpp
@@ -142,16 +142,16 @@
 // Canvas management
 // ----------------------------------------------------------------------------
 
-static inline SkBitmap::Config convertPixelFormat(int32_t format) {
+static inline SkColorType convertPixelFormat(int32_t format) {
     switch (format) {
         case PIXEL_FORMAT_RGBA_8888:
-            return SkBitmap::kARGB_8888_Config;
+            return kN32_SkColorType;
         case PIXEL_FORMAT_RGBX_8888:
-            return SkBitmap::kARGB_8888_Config;
+            return kN32_SkColorType;
         case PIXEL_FORMAT_RGB_565:
-            return SkBitmap::kRGB_565_Config;
+            return kRGB_565_SkColorType;
         default:
-            return SkBitmap::kNo_Config;
+            return kUnknown_SkColorType;
     }
 }
 
@@ -188,8 +188,10 @@
     ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
 
     SkBitmap bitmap;
-    bitmap.setConfig(convertPixelFormat(buffer->getPixelFormat()),
-            buffer->getWidth(), buffer->getHeight(), bytesCount);
+    bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
+                                     convertPixelFormat(buffer->getPixelFormat()),
+                                     kPremul_SkAlphaType),
+                   bytesCount);
 
     if (buffer->getWidth() > 0 && buffer->getHeight() > 0) {
         bitmap.setPixels(bits);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 3d14aaf..7018751 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -173,17 +173,17 @@
     return value;
 }
 
-static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
+static inline SkColorType convertPixelFormat(PixelFormat format) {
     /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then
-        we can map to SkBitmap::kARGB_8888_Config, and optionally call
+        we can map to kN32_SkColorType, and optionally call
         bitmap.setAlphaType(kOpaque_SkAlphaType) on the resulting SkBitmap
         (as an accelerator)
     */
     switch (format) {
-    case PIXEL_FORMAT_RGBX_8888:    return SkBitmap::kARGB_8888_Config;
-    case PIXEL_FORMAT_RGBA_8888:    return SkBitmap::kARGB_8888_Config;
-    case PIXEL_FORMAT_RGB_565:      return SkBitmap::kRGB_565_Config;
-    default:                        return SkBitmap::kNo_Config;
+    case PIXEL_FORMAT_RGBX_8888:    return kN32_SkColorType;
+    case PIXEL_FORMAT_RGBA_8888:    return kN32_SkColorType;
+    case PIXEL_FORMAT_RGB_565:      return kRGB_565_SkColorType;
+    default:                        return kUnknown_SkColorType;
     }
 }
 
@@ -220,12 +220,16 @@
     // Associate a SkCanvas object to this surface
     env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, outBuffer.format);
 
+    SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
+                                         convertPixelFormat(outBuffer.format),
+                                         kPremul_SkAlphaType);
+    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
+        info.fAlphaType = kOpaque_SkAlphaType;
+    }
+
     SkBitmap bitmap;
     ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
-    bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
-    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
-        bitmap.setAlphaType(kOpaque_SkAlphaType);
-    }
+    bitmap.setInfo(info, bpr);
     if (outBuffer.width > 0 && outBuffer.height > 0) {
         bitmap.setPixels(outBuffer.bits);
     } else {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index cfc8eb8..9783e91 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -175,7 +175,7 @@
             screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
 
     SkBitmap* bitmap = new SkBitmap();
-    bitmap->setConfig(screenshotInfo, (size_t)rowBytes);
+    bitmap->setInfo(screenshotInfo, (size_t)rowBytes);
     if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) {
         // takes ownership of ScreenshotClient
         SkMallocPixelRef* pixels = SkMallocPixelRef::NewWithProc(screenshotInfo,
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index c1ab515..5c04a78 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -73,17 +73,28 @@
 // Native layer
 // ----------------------------------------------------------------------------
 
-static inline SkBitmap::Config convertPixelFormat(int32_t format) {
-    switch (format) {
+// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp)
+static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) {
+    SkImageInfo info;
+    info.fWidth = buffer.width;
+    info.fHeight = buffer.height;
+    switch (buffer.format) {
         case WINDOW_FORMAT_RGBA_8888:
-            return SkBitmap::kARGB_8888_Config;
+            info.fColorType = kN32_SkColorType;
+            info.fAlphaType = kPremul_SkAlphaType;
+            break;
         case WINDOW_FORMAT_RGBX_8888:
-            return SkBitmap::kARGB_8888_Config;
+            info.fColorType = kN32_SkColorType;
+            info.fAlphaType = kOpaque_SkAlphaType;
         case WINDOW_FORMAT_RGB_565:
-            return SkBitmap::kRGB_565_Config;
+            info.fColorType = kRGB_565_SkColorType;
+            info.fAlphaType = kOpaque_SkAlphaType;
         default:
-            return SkBitmap::kNo_Config;
+            info.fColorType = kUnknown_SkColorType;
+            info.fAlphaType = kIgnore_SkAlphaType;
+            break;
     }
+    return info;
 }
 
 /**
@@ -148,11 +159,7 @@
     ssize_t bytesCount = buffer.stride * bytesPerPixel(buffer.format);
 
     SkBitmap bitmap;
-    bitmap.setConfig(convertPixelFormat(buffer.format), buffer.width, buffer.height, bytesCount);
-
-    if (buffer.format == WINDOW_FORMAT_RGBX_8888) {
-        bitmap.setAlphaType(kOpaque_SkAlphaType);
-    }
+    bitmap.setInfo(convertPixelFormat(buffer), bytesCount);
 
     if (buffer.width > 0 && buffer.height > 0) {
         bitmap.setPixels(buffer.bits);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8b5dff0..fa1a563 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2742,7 +2742,8 @@
                 android:finishOnCloseSystemDialogs="true"
                 android:excludeFromRecents="true"
                 android:multiprocess="true"
-                android:documentLaunchMode="never">
+                android:documentLaunchMode="never"
+                android:relinquishTaskIdentity="true">
             <intent-filter>
                 <action android:name="android.intent.action.CHOOSER" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 7e212be..ace17ae 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -18,7 +18,7 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60">
+    android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="100">
     <alpha
         android:fromAlpha="0.0" android:toAlpha="1.0"
         android:fillEnabled="true" android:fillBefore="true"
diff --git a/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..4d97045
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..46fb463
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_recent.png b/core/res/res/drawable-hdpi/ic_recent.png
new file mode 100644
index 0000000..8866539
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..163f4a0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..bbfb83c7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_recent.png b/core/res/res/drawable-mdpi/ic_recent.png
new file mode 100644
index 0000000..2b607df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..21d4d53
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..2aeb9a2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_recent.png b/core/res/res/drawable-xhdpi/ic_recent.png
new file mode 100644
index 0000000..86316db
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..1b11b59
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..ae0d655
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_recent.png b/core/res/res/drawable-xxhdpi/ic_recent.png
new file mode 100644
index 0000000..e6bd125
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..8774412
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..1375acc
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/drawable/lock_task_notify_bg.xml
new file mode 100644
index 0000000..3a8fab5
--- /dev/null
+++ b/core/res/res/drawable/lock_task_notify_bg.xml
@@ -0,0 +1,22 @@
+<?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"
+    android:shape="rectangle" >
+
+    <solid android:color="@android:color/black" />
+    <corners
+        android:radius="8dip" />
+</shape>
diff --git a/core/res/res/layout/lock_to_app_enter.xml b/core/res/res/layout/lock_to_app_enter.xml
new file mode 100644
index 0000000..c034536
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_enter.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="20dp"
+    android:paddingRight="20dp"
+    android:paddingBottom="12dp"
+    android:alpha=".8"
+    android:background="@drawable/lock_task_notify_bg" >
+
+    <ImageView
+        android:id="@+id/lock_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="20dp"
+        android:alpha=".8"
+        android:src="@drawable/ic_lock_outline_wht_24dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/lock_icon"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="2dp"
+        android:textColor="@android:color/white"
+        android:alpha=".8"
+        android:text="@string/lock_to_app_start" />
+</RelativeLayout>
diff --git a/core/res/res/layout/lock_to_app_exit.xml b/core/res/res/layout/lock_to_app_exit.xml
new file mode 100644
index 0000000..4a60c80
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_exit.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="20dp"
+    android:paddingRight="20dp"
+    android:paddingBottom="12dp"
+    android:alpha=".8"
+    android:background="@drawable/lock_task_notify_bg" >
+
+    <ImageView
+        android:id="@+id/lock_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="20dp"
+        android:alpha=".8"
+        android:src="@drawable/ic_lock_open_wht_24dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/lock_icon"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="2dp"
+        android:textColor="@android:color/white"
+        android:alpha=".8"
+        android:text="@string/lock_to_app_exit" />
+</RelativeLayout>
diff --git a/core/res/res/transition/explode.xml b/core/res/res/transition/explode.xml
new file mode 100644
index 0000000..fe22284
--- /dev/null
+++ b/core/res/res/transition/explode.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<explode xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/core/res/res/transition/slide_bottom.xml b/core/res/res/transition/slide_bottom.xml
new file mode 100644
index 0000000..46dc0d6
--- /dev/null
+++ b/core/res/res/transition/slide_bottom.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="bottom"/>
diff --git a/core/res/res/transition/slide_left.xml b/core/res/res/transition/slide_left.xml
new file mode 100644
index 0000000..997bd97
--- /dev/null
+++ b/core/res/res/transition/slide_left.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="left"/>
diff --git a/core/res/res/transition/slide_right.xml b/core/res/res/transition/slide_right.xml
new file mode 100644
index 0000000..98f8f6a
--- /dev/null
+++ b/core/res/res/transition/slide_right.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="right"/>
diff --git a/core/res/res/transition/slide_top.xml b/core/res/res/transition/slide_top.xml
new file mode 100644
index 0000000..07ab945
--- /dev/null
+++ b/core/res/res/transition/slide_top.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="top"/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 198333f..4a6311a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4978,6 +4978,16 @@
     <!-- ========================== -->
     <eat-comment />
 
+    <!-- Drawable used to draw vector paths. -->
+    <declare-styleable name="VectorDrawable">
+        <!-- If set, specifies the color to apply to the drawable as a tint. By default,
+             no tint is applied. May be a color state list. -->
+        <attr name="tint" />
+        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The
+             default value is src_in, which treats the drawable as an alpha mask. -->
+        <attr name="tintMode" />
+    </declare-styleable>
+
     <!-- Define the virtual size of the drawing surface paths will draw to. -->
     <declare-styleable name="VectorDrawableViewport">
         <!-- The width of the canvas the drawing is on. -->
@@ -6750,6 +6760,8 @@
         <attr name="switchPadding" format="dimension" />
         <!-- Whether to split the track and leave a gap for the thumb drawable. -->
         <attr name="splitTrack" />
+        <!-- Whether to draw on/off text. -->
+        <attr name="showText" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="Pointer">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 4e06d9a..fc1d0df 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -985,6 +985,14 @@
          Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} -->
     <attr name="autoRemoveFromRecents" format="boolean" />
 
+    <!-- Tasks whose root has this attribute set to true will replace baseIntent with that of the
+         next activity in the task. If the next activity also has this attribute set to true then
+         it will yield the baseIntent to any activity that it launches in the same task. This
+         continues until an activity is encountered which has this attribute set to false. False
+         is the default. This attribute set to true also permits activity's use of the
+         TaskDescription to change labels, colors and icons in the recent task list. -->
+    <attr name="relinquishTaskIdentity" format="boolean" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -1653,6 +1661,7 @@
         <attr name="documentLaunchMode" />
         <attr name="maxRecents" />
         <attr name="autoRemoveFromRecents" />
+        <attr name="relinquishTaskIdentity" />
     </declare-styleable>
     
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 471eece..d350ef2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1597,5 +1597,12 @@
         <item>"1,sim,0,modem"</item>
     </string-array>
 
+    <!-- This string array can be overriden to add an additional DRM support for WebView EME. -->
+    <!-- Array of "[keySystemName],[UuidOfMediaDrm]" -->
+    <string-array name="config_keySystemUuidMapping" translatable="false">
+        <!-- Example:
+        <item>"x-com.microsoft.playready,9A04F079-9840-4286-AB92-E65BE0885F95"</item>
+        -->
+    </string-array>
 
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f73c958..c3e4d94 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2209,6 +2209,7 @@
   <public type="attr" name="fullBackupOnly" />
   <public type="attr" name="propertyXName" />
   <public type="attr" name="propertyYName" />
+  <public type="attr" name="relinquishTaskIdentity" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
@@ -2460,5 +2461,26 @@
   <!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
   <public type="interpolator" name="fast_out_linear_in" />
 
+  <!-- Used for Activity Transitions, this transition indicates that no Transition
+       should be used. -->
   <public type="transition" name="no_transition" id="0x010f0000"/>
+  <!-- A transition that moves and resizes a view -->
+  <public type="transition" name="move"/>
+  <!-- A transition that fades views in and out. -->
+  <public type="transition" name="fade"/>
+  <!-- A transition that moves views in or out of the scene to or from the edges when
+       a view visibility changes. -->
+  <public type="transition" name="explode"/>
+  <!-- A transition that moves views in or out of the scene to or from the bottom edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_bottom"/>
+  <!-- A transition that moves views in or out of the scene to or from the top edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_top"/>
+  <!-- A transition that moves views in or out of the scene to or from the right edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_right"/>
+  <!-- A transition that moves views in or out of the scene to or from the left edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_left"/>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c8c0d23..e017f53 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4746,4 +4746,17 @@
     <!-- DO NOT TRANSLATE -->
     <string name="day_of_week_label_typeface">sans-serif</string>
 
+    <!-- Lock-to-app dialog title. -->
+    <string name="lock_to_app_title">Use lock-to-app?</string>
+    <!-- Lock-to-app dialog description. The $ is not actually shown or translated, it is a marker of where the recents icon shows up. -->
+    <string name="lock_to_app_description">Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button  $</string>
+    <!-- Lock-to-app negative response. -->
+    <string name="lock_to_app_negative">NO</string>
+    <!-- Lock-to-app positive response. -->
+    <string name="lock_to_app_positive">START</string>
+    <!-- Starting lock-to-app indication. -->
+    <string name="lock_to_app_start">Start Lock-to-app</string>
+    <!-- Exting lock-to-app indication. -->
+    <string name="lock_to_app_exit">Exit Lock-to-app</string>
+
 </resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 25307b9..75f905c 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -493,11 +493,12 @@
         <item name="thumb">@drawable/switch_thumb_material_anim</item>
         <item name="splitTrack">true</item>
         <item name="switchTextAppearance">@style/TextAppearance.Material.Widget.Switch</item>
-        <item name="textOn"></item>
-        <item name="textOff"></item>
+        <item name="textOn">@string/capital_on</item>
+        <item name="textOff">@string/capital_off</item>
         <item name="switchMinWidth">4dip</item>
         <item name="switchPadding">4dip</item>
         <item name="background">?attr/selectableItemBackgroundBorderless</item>
+        <item name="showText">false</item>
     </style>
 
     <style name="Widget.Material.EditText" parent="Widget.EditText"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1547bbd..3e82d08 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -604,6 +604,14 @@
   <java-symbol type="string" name="kilobyteShort" />
   <java-symbol type="string" name="last_month" />
   <java-symbol type="string" name="launchBrowserDefault" />
+  <java-symbol type="string" name="lock_to_app_title" />
+  <java-symbol type="string" name="lock_to_app_description" />
+  <java-symbol type="string" name="lock_to_app_negative" />
+  <java-symbol type="string" name="lock_to_app_positive" />
+  <java-symbol type="drawable" name="ic_recent" />
+  <java-symbol type="layout" name="lock_to_app_enter" />
+  <java-symbol type="layout" name="lock_to_app_exit" />
+  <java-symbol type="drawable" name="lock_task_notify_bg" />
   <java-symbol type="string" name="lockscreen_access_pattern_cell_added" />
   <java-symbol type="string" name="lockscreen_access_pattern_cleared" />
   <java-symbol type="string" name="lockscreen_access_pattern_detected" />
@@ -1002,6 +1010,7 @@
   <java-symbol type="array" name="config_callBarringMMI" />
   <java-symbol type="array" name="config_globalActionsList" />
   <java-symbol type="array" name="config_telephonyHardware" />
+  <java-symbol type="array" name="config_keySystemUuidMapping" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="indicator_input_error" />
diff --git a/docs/html/preview/images/l-dev-prev.png b/docs/html/preview/images/l-dev-prev.png
index 95bad8c..eae6ede 100644
--- a/docs/html/preview/images/l-dev-prev.png
+++ b/docs/html/preview/images/l-dev-prev.png
Binary files differ
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index 368db84..d4610b5 100644
--- a/docs/html/preview/index.html
+++ b/docs/html/preview/index.html
@@ -163,8 +163,8 @@
               platform officially launches.
             </div>
 
-            <img src="/preview/images/l-dev-prev.png" style=" margin:10px 0 0 100px" width="700px"/>
-            <div class="col-6" style="margin-left:630px; margin-top:-40px">
+            <img src="/preview/images/l-dev-prev.png" style=" margin:0px 0 0 40px" width="860px"/>
+            <div class="col-6" style="margin-left:660px; margin-top:-105px">
    <a href="/preview/setup-sdk.html" class="landing-button landing-secondary" style="position:absolute;z-index:100;float:right;margin-top: 0px; background-color:#09c">Get Started</a><!--
             <p>Set up your environment and check out all the docs to get up and running.</p>-->
              
@@ -176,7 +176,7 @@
 
 
 
-<div class="landing-section landing-gray-background" style="margin-top:-80px; padding-bottom:20px">
+<div class="landing-section landing-gray-background" style="margin-top:-135px; padding-bottom:20px">
         <div class="wrap">
           <div class="cols">
 <div class="landing-body" style="margin-top:-80px" >
@@ -186,14 +186,14 @@
                 <p>A New UI Design</p>
                 <p class="landing-small">
                   Create a consistent experience across mobile and the web with
-                   material design, the new Google-wide standard.
+                   <b>material design</b>, the new Google-wide standard.
                 </p>
                 <p class="landing-small">
                   <a href="/preview/material/index.html">Learn about material</a>
                 </p>
               </div>
               <div class="col-4">
-                <p>A Rehauled Runtime</p>
+                <p>A New Runtime</p>
                 <p class="landing-small">
                   Test your apps and get them ready for <b>ART</b> (<b>A</b>ndroid <b>R</b>un<b>t</b>ime),
                   the default runtime in the next release.
@@ -205,21 +205,21 @@
               <div class="col-4">
                 <p style="width:230px">Enhanced Notifications</p>
                 <p class="landing-small">
-                   Get more control over where notifications appear,
-                   how they look, and automatic syncing to non-handheld devices.
+                   Get control over where notifications appear,
+                   how they look, and how they sync to non-handheld devices.
                 </p>
                 <p class="landing-small">
-                  <a href="/preview/api-overview.html#UI">Learn more</a>
+                  <a href="/preview/api-overview.html#UI">Learn about notifications</a>
                 </p>
               </div>
               <div class="col-4">
-                <p>Project Volta</p>
+                <p>Increased Efficiency</p>
                 <p class="landing-small">
-                  We've tuned the platform to be more energy efficient and
+                  <b>Project Volta</b> is our effort to make the platform energy efficient and
                   to give you more control over resource usage.
                 </p>
                 <p class="landing-small">
-                  <a href="/preview/api-overview.html#Power">Learn more</a>
+                  <a href="/preview/api-overview.html#Power">Learn about Project Volta</a>
                 </p>
               </div>
             </div>
diff --git a/docs/html/tools/help/adb.jd b/docs/html/tools/help/adb.jd
index f980042..e2dd196 100644
--- a/docs/html/tools/help/adb.jd
+++ b/docs/html/tools/help/adb.jd
@@ -8,6 +8,7 @@
 <div id="qv">
   <h2>In this document</h2>
 <ol>
+  <li><a href="#Enabling">Enabling adb Debugging</a></li>
   <li><a href="#issuingcommands">Syntax</a></li>
   <li><a href="#commandsummary">Commands</a></li>
   <li><a href="#devicestatus">Querying for Emulator/Device Instances</a></li>
@@ -72,6 +73,19 @@
 instance from any client (or from a script).</p>
 
 
+<h2 id="Enabling">Enabling adb Debugging</h2>
+
+<p>In order to use adb with a device connected over USB, you must enable
+<strong>USB debugging</strong> in the device system settings, under <strong>
+Developer options</strong>.</p>
+
+<p>On Android 4.2 and higher, the Developer options screen is
+hidden by default. To make it visible, go to
+<b>Settings &gt; About phone</b> and tap <b>Build number</b> seven times. Return to the previous
+screen to find <strong>Developer options</strong> at the bottom.</p>
+
+<p>On some devices, the Developer options screen may be located or named differently.</p>
+
 <p class="note"><strong>Note:</strong> When you connect a device running Android 4.2.2 or higher
 to your computer, the system shows a dialog asking whether to accept an RSA key that allows
 debugging through this computer. This security mechanism protects user devices because it ensures
@@ -80,6 +94,11 @@
 SDK Platform-tools r16.0.1 and higher) in order to debug on a device running Android 4.2.2 or
 higher.</p>
 
+<p>For more information about connecting to a device over USB, read
+<a href="{@docRoot}tools/device.html">Using Hardware Devices</a>.</p>
+
+
+
 
 <h2 id="issuingcommands">Syntax</h2>
 
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 005b8ef..28cd869 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -135,7 +135,6 @@
     private Paint mStrokePaint;   // optional, set by the caller
     private ColorFilter mColorFilter;   // optional, set by the caller
     private int mAlpha = 0xFF;  // modified by the caller
-    private boolean mDither;
 
     private final Path mPath = new Path();
     private final RectF mRect = new RectF();
@@ -543,7 +542,7 @@
             if (mLayerPaint == null) {
                 mLayerPaint = new Paint();
             }
-            mLayerPaint.setDither(mDither);
+            mLayerPaint.setDither(st.mDither);
             mLayerPaint.setAlpha(mAlpha);
             mLayerPaint.setColorFilter(mColorFilter);
 
@@ -561,14 +560,14 @@
                 individual paints
             */
             mFillPaint.setAlpha(currFillAlpha);
-            mFillPaint.setDither(mDither);
+            mFillPaint.setDither(st.mDither);
             mFillPaint.setColorFilter(mColorFilter);
-            if (mColorFilter != null && mGradientState.mColorStateList == null) {
+            if (mColorFilter != null && st.mColorStateList == null) {
                 mFillPaint.setColor(mAlpha << 24);
             }
             if (haveStroke) {
                 mStrokePaint.setAlpha(currStrokeAlpha);
-                mStrokePaint.setDither(mDither);
+                mStrokePaint.setDither(st.mDither);
                 mStrokePaint.setColorFilter(mColorFilter);
             }
         }
@@ -804,8 +803,8 @@
 
     @Override
     public void setDither(boolean dither) {
-        if (dither != mDither) {
-            mDither = dither;
+        if (dither != mGradientState.mDither) {
+            mGradientState.mDither = dither;
             invalidateSelf();
         }
     }
@@ -1015,7 +1014,7 @@
         state.mThemeAttrs = a.extractThemeAttrs();
 
         state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
-        mDither = a.getBoolean(R.styleable.GradientDrawable_dither, mDither);
+        state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
 
         if (state.mShape == RING) {
             state.mInnerRadius = a.getDimensionPixelSize(
@@ -1459,6 +1458,8 @@
         public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
         public int mInnerRadius = -1;
         public int mThickness = -1;
+        public boolean mDither = false;
+
         private float mCenterX = 0.5f;
         private float mCenterY = 0.5f;
         private float mGradientRadius = 0.5f;
@@ -1510,6 +1511,7 @@
             mThicknessRatio = state.mThicknessRatio;
             mInnerRadius = state.mInnerRadius;
             mThickness = state.mThickness;
+            mDither = state.mDither;
             mCenterX = state.mCenterX;
             mCenterY = state.mCenterY;
             mGradientRadius = state.mGradientRadius;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0512ecc..f2e75a5 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -645,25 +645,29 @@
 
     @Override
     public Rect getDirtyBounds() {
-        final Rect drawingBounds = mDrawingBounds;
-        final Rect dirtyBounds = mDirtyBounds;
-        dirtyBounds.set(drawingBounds);
-        drawingBounds.setEmpty();
+        if (isProjected()) {
+            final Rect drawingBounds = mDrawingBounds;
+            final Rect dirtyBounds = mDirtyBounds;
+            dirtyBounds.set(drawingBounds);
+            drawingBounds.setEmpty();
 
-        final int cX = (int) mHotspotBounds.exactCenterX();
-        final int cY = (int) mHotspotBounds.exactCenterY();
-        final Rect rippleBounds = mTempRect;
-        final Ripple[] activeRipples = mAnimatingRipples;
-        final int N = mAnimatingRipplesCount;
-        for (int i = 0; i < N; i++) {
-            activeRipples[i].getBounds(rippleBounds);
-            rippleBounds.offset(cX, cY);
-            drawingBounds.union(rippleBounds);
+            final int cX = (int) mHotspotBounds.exactCenterX();
+            final int cY = (int) mHotspotBounds.exactCenterY();
+            final Rect rippleBounds = mTempRect;
+            final Ripple[] activeRipples = mAnimatingRipples;
+            final int N = mAnimatingRipplesCount;
+            for (int i = 0; i < N; i++) {
+                activeRipples[i].getBounds(rippleBounds);
+                rippleBounds.offset(cX, cY);
+                drawingBounds.union(rippleBounds);
+            }
+
+            dirtyBounds.union(drawingBounds);
+            dirtyBounds.union(super.getDirtyBounds());
+            return dirtyBounds;
+        } else {
+            return getBounds();
         }
-
-        dirtyBounds.union(drawingBounds);
-        dirtyBounds.union(super.getDirtyBounds());
-        return dirtyBounds;
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index beb300d..369bb59 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -416,6 +416,9 @@
         final ShapeState state = mShapeState;
         final Paint paint = state.mPaint;
 
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
         int color = paint.getColor();
         color = a.getColor(R.styleable.ShapeDrawable_color, color);
         paint.setColor(color);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 7c6c67d..3773a9b 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -286,13 +286,6 @@
     }
 
     @Override
-    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
-            throws XmlPullParserException, IOException {
-        final VPathRenderer p = inflateInternal(res, parser, attrs, theme);
-        setPathRenderer(p);
-    }
-
-    @Override
     public boolean canApplyTheme() {
         return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
     }
@@ -335,13 +328,44 @@
         return color;
     }
 
-    private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
-            Theme theme) throws XmlPullParserException, IOException {
+
+    @Override
+    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(res, theme,  attrs,R.styleable.VectorDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        final VectorDrawableState state = mVectorState;
+        state.mVPathRenderer = inflateInternal(res, parser, attrs, theme);
+
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+        state.mVPathRenderer.setColorFilter(mTintFilter);
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        final VectorDrawableState state = mVectorState;
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+    }
+
+    private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
         final VPathRenderer pathRenderer = new VPathRenderer();
 
         boolean noSizeTag = true;
         boolean noViewportTag = true;
-        boolean noGroupTag = true;
         boolean noPathTag = true;
 
         // Use a stack to help to build the group tree.
@@ -377,7 +401,6 @@
                     if (newChildGroup.getGroupName() != null) {
                         mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup);
                     }
-                    noGroupTag = false;
                 }
             } else if (eventType == XmlPullParser.END_TAG) {
                 final String tagName = parser.getName();
@@ -435,11 +458,8 @@
         }
     }
 
-    private void setPathRenderer(VPathRenderer pathRenderer) {
-        mVectorState.mVPathRenderer = pathRenderer;
-    }
-
     private static class VectorDrawableState extends ConstantState {
+        int[] mThemeAttrs;
         int mChangingConfigurations;
         VPathRenderer mVPathRenderer;
         Rect mPadding;
@@ -448,6 +468,7 @@
 
         public VectorDrawableState(VectorDrawableState copy) {
             if (copy != null) {
+                mThemeAttrs = copy.mThemeAttrs;
                 mChangingConfigurations = copy.mChangingConfigurations;
                 // TODO: Make sure the constant state are handled correctly.
                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index eff3011..dc6d852 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -160,6 +160,10 @@
     (target->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
 }
 
+uint32_t RenderPropertyAnimator::dirtyMask() {
+    return mPropertyAccess->dirtyMask;
+}
+
 float RenderPropertyAnimator::getValue(RenderNode* target) const {
     return (target->properties().*mPropertyAccess->getter)();
 }
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 203cdff..6cb72c4c 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -61,6 +61,8 @@
     bool isFinished() { return mPlayState == FINISHED; }
     float finalValue() { return mFinalValue; }
 
+    ANDROID_API virtual uint32_t dirtyMask() { return 0; }
+
 protected:
     BaseRenderNodeAnimator(float finalValue);
     virtual ~BaseRenderNodeAnimator();
@@ -112,6 +114,8 @@
 
     ANDROID_API virtual void onAttached(RenderNode* target);
 
+    ANDROID_API virtual uint32_t dirtyMask();
+
 protected:
     virtual float getValue(RenderNode* target) const;
     virtual void setValue(RenderNode* target, float value);
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
index 1f84b86..fc0e8a0 100644
--- a/libs/hwui/Interpolator.cpp
+++ b/libs/hwui/Interpolator.cpp
@@ -112,6 +112,10 @@
     int i1 = (int) ipart;
     int i2 = MathUtils::min(i1 + 1, mSize - 1);
 
+    LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!"
+            " i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f",
+            i1, i2, input, lutpos, mSize, mValues, ipart, weight);
+
     float v1 = mValues[i1];
     float v2 = mValues[i2];
 
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 9dd5aa5..97eb583 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -104,8 +104,7 @@
 }
 
 static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
-    bitmap.setConfig(SkBitmap::kA8_Config, width, height);
-    bitmap.allocPixels();
+    bitmap.allocPixels(SkImageInfo::MakeA8(width, height));
     bitmap.eraseColor(0);
 }
 
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b2fe849..2d6d409 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -189,6 +189,8 @@
     // UI thread only!
     ANDROID_API void removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
         mStagingAnimators.erase(animator);
+        // Force a sync of the staging property value
+        mDirtyPropertyFields |= animator->dirtyMask();
         mNeedsAnimatorsSync = true;
     }
 
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 1001cae0..9212d0a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -333,8 +333,7 @@
 void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap,
         uint32_t width, uint32_t height) {
     SkBitmap rgbaBitmap;
-    rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType());
-    rgbaBitmap.allocPixels();
+    rgbaBitmap.allocPixels(SkImageInfo::MakeN32(width, height, bitmap->alphaType()));
     rgbaBitmap.eraseColor(0);
 
     SkCanvas canvas(rgbaBitmap);
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 3f6ccc9..063383b 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -206,9 +206,8 @@
             } else {
                 SkBitmap surfaceBitmap;
                 ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
-                surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
-                        outBuffer.width, outBuffer.height, bpr);
-                surfaceBitmap.setPixels(outBuffer.bits);
+                surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height),
+                                            outBuffer.bits, bpr);
 
                 SkCanvas surfaceCanvas(surfaceBitmap);
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d35225a..fb19242 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1254,11 +1254,6 @@
      * call {@link #stopBluetoothSco()} to clear the request and turn down the bluetooth connection.
      * <p>Even if a SCO connection is established, the following restrictions apply on audio
      * output streams so that they can be routed to SCO headset:
-     * <p>NOTE: up to and including API version
-     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
-     * voice call to the bluetooth headset.
-     * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
-     * connection is established.
      * <ul>
      *   <li> the stream type must be {@link #STREAM_VOICE_CALL} </li>
      *   <li> the format must be mono </li>
@@ -1274,6 +1269,11 @@
      * it will be ignored. Similarly, if a call is received or sent while an application
      * is using the SCO connection, the connection will be lost for the application and NOT
      * returned automatically when the call ends.
+     * <p>NOTE: up to and including API version
+     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
+     * voice call to the bluetooth headset.
+     * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
+     * connection is established.
      * @see #stopBluetoothSco()
      * @see #ACTION_SCO_AUDIO_STATE_UPDATED
      */
@@ -1287,13 +1287,38 @@
     }
 
     /**
+     * Start bluetooth SCO audio connection in virtual call mode.
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+     * <p>Similar to {@link #startBluetoothSco()} with explicit selection of virtual call mode.
+     * Telephony and communication applications (VoIP, Video Chat) should preferably select
+     * virtual call mode.
+     * Applications using voice input for search or commands should first try raw audio connection
+     * with {@link #startBluetoothSco()} and fall back to startBluetoothScoVirtualCall() in case of
+     * failure.
+     * @see #startBluetoothSco()
+     * @see #stopBluetoothSco()
+     * @see #ACTION_SCO_AUDIO_STATE_UPDATED
+     */
+    public void startBluetoothScoVirtualCall() {
+        IAudioService service = getService();
+        try {
+            service.startBluetoothScoVirtualCall(mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in startBluetoothScoVirtualCall", e);
+        }
+    }
+
+    /**
      * Stop bluetooth SCO audio connection.
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
      * <p>This method must be called by applications having requested the use of
-     * bluetooth SCO audio with {@link #startBluetoothSco()}
-     * when finished with the SCO connection or if connection fails.
+     * bluetooth SCO audio with {@link #startBluetoothSco()} or
+     * {@link #startBluetoothScoVirtualCall()} when finished with the SCO connection or
+     * if connection fails.
      * @see #startBluetoothSco()
+     * @see #startBluetoothScoVirtualCall()
      */
     public void stopBluetoothSco(){
         IAudioService service = getService();
@@ -2169,48 +2194,8 @@
             Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
             return;
         }
-        IAudioService service = getService();
-        try {
-            // pi != null, this is currently still needed to support across
-            // reboot launching of the last app.
-            service.registerMediaButtonIntent(pi, eventReceiver,
-                    eventReceiver == null ? mToken : null);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
-        }
         MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
-        helper.addMediaButtonListener(pi, mContext);
-    }
-
-    /**
-     * @hide
-     * Used internally by telephony package to register an intent receiver for ACTION_MEDIA_BUTTON.
-     * @param eventReceiver the component that will receive the media button key events,
-     *          no-op if eventReceiver is null
-     */
-    public void registerMediaButtonEventReceiverForCalls(ComponentName eventReceiver) {
-        if (eventReceiver == null) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            // eventReceiver != null
-            service.registerMediaButtonEventReceiverForCalls(eventReceiver);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerMediaButtonEventReceiverForCalls", e);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void unregisterMediaButtonEventReceiverForCalls() {
-        IAudioService service = getService();
-        try {
-            service.unregisterMediaButtonEventReceiverForCalls();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiverForCalls", e);
-        }
+        helper.addMediaButtonListener(pi, eventReceiver, mContext);
     }
 
     /**
@@ -2247,12 +2232,6 @@
      * @hide
      */
     public void unregisterMediaButtonIntent(PendingIntent pi) {
-        IAudioService service = getService();
-        try {
-            service.unregisterMediaButtonIntent(pi);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
-        }
         MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
         helper.removeMediaButtonListener(pi);
     }
@@ -2418,46 +2397,6 @@
     }
 
     /**
-     * @hide
-     * Request the user of a RemoteControlClient to seek to the given playback position.
-     * @param generationId the RemoteControlClient generation counter for which this request is
-     *         issued. Requests for an older generation than current one will be ignored.
-     * @param timeMs the time in ms to seek to, must be positive.
-     */
-    public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        if (timeMs < 0) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.setRemoteControlClientPlaybackPosition(generationId, timeMs);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in setRccPlaybackPosition("+ generationId + ", "
-                    + timeMs + ")", e);
-        }
-    }
-
-    /**
-     * @hide
-     * Notify the user of a RemoteControlClient that it should update its metadata with the
-     * new value for the given key.
-     * @param generationId the RemoteControlClient generation counter for which this request is
-     *         issued. Requests for an older generation than current one will be ignored.
-     * @param key the metadata key for which a new value exists
-     * @param value the new metadata value
-     */
-    public void updateRemoteControlClientMetadata(int generationId, int key,
-            Rating value) {
-        IAudioService service = getService();
-        try {
-            service.updateRemoteControlClientMetadata(generationId, key, value);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in updateRemoteControlClientMetadata("+ generationId + ", "
-                    + key +", " + value + ")", e);
-        }
-    }
-
-    /**
      *  @hide
      *  Reload audio settings. This method is called by Settings backup
      *  agent when audio settings are restored and causes the AudioService
@@ -2873,12 +2812,9 @@
      * @hide
      */
     public int getRemoteStreamVolume() {
-        try {
-            return getService().getRemoteStreamVolume();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error getting remote stream volume", e);
-            return 0;
-        }
+        // TODO STOPSHIP switch callers to use media sessions instead
+        Log.e(TAG, "Need to implement new Remote Volume!");
+        return 0;
     }
 
     /**
@@ -2886,12 +2822,9 @@
      * @hide
      */
     public int getRemoteStreamMaxVolume() {
-        try {
-            return getService().getRemoteStreamMaxVolume();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error getting remote stream max volume", e);
-            return 0;
-        }
+        // TODO STOPSHIP switch callers to use media sessions instead
+        Log.e(TAG, "Need to implement new Remote Volume!");
+        return 0;
     }
 
     /**
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 48479a4..0c224a6 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -2056,7 +2056,19 @@
     }
 
     /** @see AudioManager#startBluetoothSco() */
-    public void startBluetoothSco(IBinder cb, int targetSdkVersion){
+    public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+        int scoAudioMode =
+                (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
+                        SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+        startBluetoothScoInt(cb, scoAudioMode);
+    }
+
+    /** @see AudioManager#startBluetoothScoVirtualCall() */
+    public void startBluetoothScoVirtualCall(IBinder cb) {
+        startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+    }
+
+    void startBluetoothScoInt(IBinder cb, int scoAudioMode){
         if (!checkAudioSettingsPermission("startBluetoothSco()") ||
                 !mSystemReady) {
             return;
@@ -2068,7 +2080,7 @@
         // The caller identity must be cleared after getScoClient() because it is needed if a new
         // client is created.
         final long ident = Binder.clearCallingIdentity();
-        client.incCount(targetSdkVersion);
+        client.incCount(scoAudioMode);
         Binder.restoreCallingIdentity(ident);
     }
 
@@ -2114,9 +2126,9 @@
             }
         }
 
-        public void incCount(int targetSdkVersion) {
+        public void incCount(int scoAudioMode) {
             synchronized(mScoClients) {
-                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, targetSdkVersion);
+                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
                 if (mStartcount == 0) {
                     try {
                         mCb.linkToDeath(this, 0);
@@ -2186,7 +2198,7 @@
             }
         }
 
-        private void requestScoState(int state, int targetSdkVersion) {
+        private void requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
             if (totalCount() == 0) {
                 if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
@@ -2201,9 +2213,7 @@
                                 (mScoAudioState == SCO_STATE_INACTIVE ||
                                  mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
                             if (mScoAudioState == SCO_STATE_INACTIVE) {
-                                mScoAudioMode =
-                                        (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
-                                                SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+                                mScoAudioMode = scoAudioMode;
                                 if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
                                     boolean status;
                                     if (mScoAudioMode == SCO_MODE_RAW) {
@@ -4402,68 +4412,12 @@
         mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
     }
 
-    public void registerMediaButtonEventReceiverForCalls(ComponentName c) {
-        mMediaFocusControl.registerMediaButtonEventReceiverForCalls(c);
-    }
-
-    public void unregisterMediaButtonEventReceiverForCalls() {
-        mMediaFocusControl.unregisterMediaButtonEventReceiverForCalls();
-    }
-
-    public void registerMediaButtonIntent(PendingIntent pi, ComponentName c, IBinder token) {
-        mMediaFocusControl.registerMediaButtonIntent(pi, c, token);
-    }
-
-    public void unregisterMediaButtonIntent(PendingIntent pi) {
-        mMediaFocusControl.unregisterMediaButtonIntent(pi);
-    }
-
-    public int registerRemoteControlClient(PendingIntent mediaIntent,
-            IRemoteControlClient rcClient, String callingPckg) {
-        return mMediaFocusControl.registerRemoteControlClient(mediaIntent, rcClient, callingPckg);
-    }
-
-    public void unregisterRemoteControlClient(PendingIntent mediaIntent,
-            IRemoteControlClient rcClient) {
-        mMediaFocusControl.unregisterRemoteControlClient(mediaIntent, rcClient);
-    }
-
-    public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        mMediaFocusControl.setRemoteControlClientPlaybackPosition(generationId, timeMs);
-    }
-
-    public void updateRemoteControlClientMetadata(int generationId, int key, Rating value) {
-        mMediaFocusControl.updateRemoteControlClientMetadata(generationId, key, value);
-    }
-
-    public void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
-        mMediaFocusControl.registerRemoteVolumeObserverForRcc(rccId, rvo);
-    }
-
-    @Override
-    public int getRemoteStreamVolume() {
-        return mMediaFocusControl.getRemoteStreamVolume();
-    }
-
-    @Override
-    public int getRemoteStreamMaxVolume() {
-        return mMediaFocusControl.getRemoteStreamMaxVolume();
-    }
-
     @Override
     public void setRemoteStreamVolume(int index) {
         enforceSelfOrSystemUI("set the remote stream volume");
         mMediaFocusControl.setRemoteStreamVolume(index);
     }
 
-    public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
-        mMediaFocusControl.setPlaybackStateForRcc(rccId, state, timeMs, speed);
-    }
-
-    public void setPlaybackInfoForRcc(int rccId, int what, int value) {
-        mMediaFocusControl.setPlaybackInfoForRcc(rccId, what, value);
-    }
-
     //==========================================================================================
     // Audio Focus
     //==========================================================================================
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 169631e..4dcdd19 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -122,12 +122,6 @@
 
     int getCurrentAudioFocus();
 
-           void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c, IBinder token);
-    oneway void unregisterMediaButtonIntent(in PendingIntent pi);
-
-    oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
-    oneway void unregisterMediaButtonEventReceiverForCalls();
-
     /**
      * Register an IRemoteControlDisplay.
      * Success of registration is subject to a check on
@@ -180,43 +174,9 @@
      */
     oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd,
             boolean wantsSync);
-    /**
-     * Request the user of a RemoteControlClient to seek to the given playback position.
-     * @param generationId the RemoteControlClient generation counter for which this request is
-     *         issued. Requests for an older generation than current one will be ignored.
-     * @param timeMs the time in ms to seek to, must be positive.
-     */
-     void setRemoteControlClientPlaybackPosition(int generationId, long timeMs);
-     /**
-      * Notify the user of a RemoteControlClient that it should update its metadata with the
-      * new value for the given key.
-      * @param generationId the RemoteControlClient generation counter for which this request is
-      *         issued. Requests for an older generation than current one will be ignored.
-      * @param key the metadata key for which a new value exists
-      * @param value the new metadata value
-      */
-     void updateRemoteControlClientMetadata(int generationId, int key, in Rating value);
-
-    /**
-     * Do not use directly, use instead
-     *     {@link android.media.AudioManager#registerRemoteControlClient(RemoteControlClient)}
-     */
-    int registerRemoteControlClient(in PendingIntent mediaIntent,
-            in IRemoteControlClient rcClient, in String callingPackageName);
-    /**
-     * Do not use directly, use instead
-     *     {@link android.media.AudioManager#unregisterRemoteControlClient(RemoteControlClient)}
-     */
-    oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
-            in IRemoteControlClient rcClient);
-
-    oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
-    void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed);
-           int  getRemoteStreamMaxVolume();
-           int  getRemoteStreamVolume();
-    oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo);
 
     void startBluetoothSco(IBinder cb, int targetSdkVersion);
+    void startBluetoothScoVirtualCall(IBinder cb);
     void stopBluetoothSco(IBinder cb);
 
     void forceVolumeControlStream(int streamType, IBinder cb);
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 440653a..6559bc5 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -181,6 +181,27 @@
     }
 
     /**
+     * Thrown when an unrecoverable failure occurs during a MediaDrm operation.
+     * Extends java.lang.IllegalStateException with the addition of an error
+     * code that may be useful in diagnosing the failure.
+     */
+    public static final class MediaDrmStateException extends java.lang.IllegalStateException {
+        private final int mErrorCode;
+
+        public MediaDrmStateException(int errorCode, String detailMessage) {
+            super(detailMessage);
+            mErrorCode = errorCode;
+        }
+
+        /**
+         * Retrieve the associated error code
+         */
+        public int getErrorCode() {
+            return mErrorCode;
+        }
+    }
+
+    /**
      * Register a callback to be invoked when an event occurs
      *
      * @param listener the callback that will be run
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 1c73c05..a4a7c4e 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -379,32 +379,11 @@
                     onReevaluateRemote();
                     break;
 
-                case MSG_RCC_NEW_PLAYBACK_INFO:
-                    onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */,
-                            ((Integer)msg.obj).intValue() /* value */);
-                    break;
-
                 case MSG_RCC_NEW_VOLUME_OBS:
                     onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
                             (IRemoteVolumeObserver)msg.obj /* rvo */);
                     break;
 
-                case MSG_RCC_NEW_PLAYBACK_STATE:
-                    onNewPlaybackStateForRcc(msg.arg1 /* rccId */,
-                            msg.arg2 /* state */,
-                            (PlayerRecord.RccPlaybackState)msg.obj /* newState */);
-                    break;
-
-                case MSG_RCC_SEEK_REQUEST:
-                    onSetRemoteControlClientPlaybackPosition(
-                            msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */);
-                    break;
-
-                case MSG_RCC_UPDATE_METADATA:
-                    onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/,
-                            (Rating) msg.obj /* value */);
-                    break;
-
                 case MSG_RCDISPLAY_INIT_INFO:
                     // msg.obj is guaranteed to be non null
                     onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
@@ -2003,217 +1982,6 @@
         }
     }
 
-    protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        // ignore position change requests if invalid generation ID
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if (mCurrentRcClientGen != generationId) {
-                    return;
-                }
-            }
-        }
-        // discard any unprocessed seek request in the message queue, and replace with latest
-        sendMsg(mEventHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */,
-                0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */);
-    }
-
-    private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId +
-                ", timeMs=" + timeMs + ")");
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) {
-                    // tell the current client to seek to the requested location
-                    try {
-                        mCurrentRcClient.seekTo(generationId, timeMs);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Current valid remote client is dead: "+e);
-                        mCurrentRcClient = null;
-                    }
-                }
-            }
-        }
-    }
-
-    protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) {
-        sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE,
-                genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */);
-    }
-
-    private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) {
-        if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId +
-                ", what=" + key + ",rating=" + value + ")");
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
-                    try {
-                        switch (key) {
-                            case MediaMetadataEditor.RATING_KEY_BY_USER:
-                                mCurrentRcClient.updateMetadata(genId, key, value);
-                                break;
-                            default:
-                                Log.e(TAG, "unhandled metadata key " + key + " update for RCC "
-                                        + genId);
-                                break;
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Current valid remote client is dead", e);
-                        mCurrentRcClient = null;
-                    }
-                }
-            }
-        }
-    }
-
-    protected void setPlaybackInfoForRcc(int rccId, int what, int value) {
-        sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE,
-                rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */);
-    }
-
-    // handler for MSG_RCC_NEW_PLAYBACK_INFO
-    private void onNewPlaybackInfoForRcc(int rccId, int key, int value) {
-        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId +
-                ", what=" + key + ",val=" + value + ")");
-        synchronized(mPRStack) {
-            // iterating from top of stack as playback information changes are more likely
-            //   on entries at the top of the remote control stack
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    if (prse.getRccId() == rccId) {
-                        switch (key) {
-                            case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
-                                prse.mPlaybackType = value;
-                                postReevaluateRemote();
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_VOLUME:
-                                prse.mPlaybackVolume = value;
-                                synchronized (mMainRemote) {
-                                    if (rccId == mMainRemote.mRccId) {
-                                        mMainRemote.mVolume = value;
-                                        mVolumeController.postHasNewRemotePlaybackInfo();
-                                    }
-                                }
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
-                                prse.mPlaybackVolumeMax = value;
-                                synchronized (mMainRemote) {
-                                    if (rccId == mMainRemote.mRccId) {
-                                        mMainRemote.mVolumeMax = value;
-                                        mVolumeController.postHasNewRemotePlaybackInfo();
-                                    }
-                                }
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
-                                prse.mPlaybackVolumeHandling = value;
-                                synchronized (mMainRemote) {
-                                    if (rccId == mMainRemote.mRccId) {
-                                        mMainRemote.mVolumeHandling = value;
-                                        mVolumeController.postHasNewRemotePlaybackInfo();
-                                    }
-                                }
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
-                                prse.mPlaybackStream = value;
-                                break;
-                            default:
-                                Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
-                                break;
-                        }
-                        return;
-                    }
-                }//for
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index mPRStack on onNewPlaybackInfoForRcc, lock error? ", e);
-            }
-        }
-    }
-
-    protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
-        sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE,
-                rccId /* arg1 */, state /* arg2 */,
-                new PlayerRecord.RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
-    }
-
-    private void onNewPlaybackStateForRcc(int rccId, int state,
-            PlayerRecord.RccPlaybackState newState) {
-        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
-                + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
-        synchronized(mPRStack) {
-            if (mPRStack.empty()) {
-                return;
-            }
-            PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
-            PlayerRecord prse = null;
-            int lastPlayingIndex = mPRStack.size();
-            int inStackIndex = -1;
-            try {
-                // go through the stack from the top to figure out who's playing, and the position
-                // of this RemoteControlClient (note that it may not be in the stack)
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    prse = mPRStack.elementAt(index);
-                    if (prse.getRccId() == rccId) {
-                        inStackIndex = index;
-                        prse.mPlaybackState = newState;
-                    }
-                    if (prse.isPlaybackActive()) {
-                        lastPlayingIndex = index;
-                    }
-                }
-
-                if (inStackIndex != -1) {
-                    // is in the stack
-                    prse = mPRStack.elementAt(inStackIndex);
-                    synchronized (mMainRemote) {
-                        if (rccId == mMainRemote.mRccId) {
-                            mMainRemoteIsActive = isPlaystateActive(state);
-                            postReevaluateRemote();
-                        }
-                    }
-                    if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
-                        // remove it from its old location in the stack
-                        mPRStack.removeElementAt(inStackIndex);
-                        if (prse.isPlaybackActive()) {
-                            // and put it at the top
-                            mPRStack.push(prse);
-                        } else {
-                            // and put it after the ones with active playback
-                            if (inStackIndex > lastPlayingIndex) {
-                                mPRStack.add(lastPlayingIndex, prse);
-                            } else {
-                                mPRStack.add(lastPlayingIndex - 1, prse);
-                            }
-                        }
-                    }
-
-                    if (oldTopPrse != mPRStack.lastElement()) {
-                        // the top of the stack changed:
-                        final ComponentName target =
-                                mPRStack.lastElement().getMediaButtonReceiver();
-                        if (target != null) {
-                            // post message to persist the default media button receiver
-                            mEventHandler.sendMessage( mEventHandler.obtainMessage(
-                                    MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
-                        }
-                        // reevaluate the display
-                        checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification or bad index
-                Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
-                        + " size=" + mPRStack.size()
-                        + "accessing PlayerRecord stack in onNewPlaybackStateForRcc", e);
-            }
-        }
-    }
-
-    protected void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
-        sendMsg(mEventHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE,
-                rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */);
-    }
-
     // handler for MSG_RCC_NEW_VOLUME_OBS
     private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
         synchronized(mPRStack) {
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index ddd5b72..3336694 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -29,7 +29,6 @@
 import android.hardware.display.WifiDisplay;
 import android.hardware.display.WifiDisplayStatus;
 import android.media.session.MediaSession;
-import android.media.session.RemoteVolumeProvider;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
@@ -2204,10 +2203,10 @@
                 return;
             }
             if (mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
-                int volumeControl = RemoteVolumeProvider.VOLUME_CONTROL_FIXED;
+                int volumeControl = VolumeProvider.VOLUME_CONTROL_FIXED;
                 switch (mVolumeHandling) {
                     case RemoteControlClient.PLAYBACK_VOLUME_VARIABLE:
-                        volumeControl = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+                        volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
                         break;
                     case RemoteControlClient.PLAYBACK_VOLUME_FIXED:
                     default:
@@ -2226,7 +2225,7 @@
             }
         }
 
-        class SessionVolumeProvider extends RemoteVolumeProvider {
+        class SessionVolumeProvider extends VolumeProvider {
 
             public SessionVolumeProvider(int volumeControl, int maxVolume) {
                 super(volumeControl, maxVolume);
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 76c7299..be96398 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -361,18 +361,10 @@
         if (timeMs < 0) {
             throw new IllegalArgumentException("illegal negative time value");
         }
-        if (USE_SESSIONS) {
-            synchronized (mInfoLock) {
-                if (mCurrentSession != null) {
-                    mCurrentSession.getTransportControls().seekTo(timeMs);
-                }
+        synchronized (mInfoLock) {
+            if (mCurrentSession != null) {
+                mCurrentSession.getTransportControls().seekTo(timeMs);
             }
-        } else {
-            final int genId;
-            synchronized (mGenLock) {
-                genId = mClientGenerationIdCurrent;
-            }
-            mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
         }
         return true;
     }
@@ -534,34 +526,15 @@
             if (!mMetadataChanged) {
                 return;
             }
-            if (USE_SESSIONS) {
-                synchronized (mInfoLock) {
-                    if (mCurrentSession != null) {
-                        if (mEditorMetadata.containsKey(
-                                String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
-                            Rating rating = (Rating) getObject(
-                                    MediaMetadataEditor.RATING_KEY_BY_USER, null);
-                            if (rating != null) {
-                                mCurrentSession.getTransportControls().setRating(rating);
-                            }
-                        }
-                    }
-                }
-            } else {
-                final int genId;
-                synchronized(mGenLock) {
-                    genId = mClientGenerationIdCurrent;
-                }
-                synchronized(mInfoLock) {
+            synchronized (mInfoLock) {
+                if (mCurrentSession != null) {
                     if (mEditorMetadata.containsKey(
                             String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
                         Rating rating = (Rating) getObject(
                                 MediaMetadataEditor.RATING_KEY_BY_USER, null);
-                        mAudioManager.updateRemoteControlClientMetadata(genId,
-                              MediaMetadataEditor.RATING_KEY_BY_USER,
-                              rating);
-                    } else {
-                        Log.e(TAG, "no metadata to apply");
+                        if (rating != null) {
+                            mCurrentSession.getTransportControls().setRating(rating);
+                        }
                     }
                 }
             }
diff --git a/media/java/android/media/session/RemoteVolumeProvider.java b/media/java/android/media/VolumeProvider.java
similarity index 92%
rename from media/java/android/media/session/RemoteVolumeProvider.java
rename to media/java/android/media/VolumeProvider.java
index 606b1d7..7d93b40 100644
--- a/media/java/android/media/session/RemoteVolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.session;
+package android.media;
 
+import android.media.session.MediaSession;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -24,8 +25,8 @@
  * You can set a volume provider on a session by calling
  * {@link MediaSession#setPlaybackToRemote}.
  */
-public abstract class RemoteVolumeProvider {
-    private static final String TAG = "RemoteVolumeProvider";
+public abstract class VolumeProvider {
+    private static final String TAG = "VolumeProvider";
 
     /**
      * The volume is fixed and can not be modified. Requests to change volume
@@ -60,7 +61,7 @@
      *            this provider.
      * @param maxVolume The maximum allowed volume.
      */
-    public RemoteVolumeProvider(int volumeControl, int maxVolume) {
+    public VolumeProvider(int volumeControl, int maxVolume) {
         mControlType = volumeControl;
         mMaxVolume = maxVolume;
     }
@@ -117,7 +118,7 @@
     /**
      * @hide
      */
-    void setSession(MediaSession session) {
+    public void setSession(MediaSession session) {
         mSession = session;
     }
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 1cfc5bc..5bc0de4 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -15,6 +15,7 @@
 
 package android.media.session;
 
+import android.content.ComponentName;
 import android.media.MediaMetadata;
 import android.media.session.ISessionController;
 import android.media.session.RouteOptions;
@@ -33,6 +34,7 @@
     ISessionController getController();
     void setFlags(int flags);
     void setActive(boolean active);
+    void setMediaButtonReceiver(in ComponentName mbr);
     void destroy();
 
     // These commands are for setting up and communicating with routes
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index f0cd785..b4c11f6 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -20,6 +20,7 @@
 import android.media.Rating;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaSessionInfo;
+import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -38,6 +39,9 @@
     void showRoutePicker();
     MediaSessionInfo getSessionInfo();
     long getFlags();
+    ParcelableVolumeInfo getVolumeAttributes();
+    void adjustVolumeBy(int delta, int flags);
+    void setVolumeTo(int value, int flags);
 
     // These commands are for the TransportController
     void play();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 87a43e4..84dad25 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -18,6 +18,7 @@
 
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.VolumeProvider;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -183,6 +184,23 @@
     }
 
     /**
+     * Get the current volume info for this session.
+     *
+     * @return The current volume info or null.
+     */
+    public VolumeInfo getVolumeInfo() {
+        try {
+            ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
+            return new VolumeInfo(result.volumeType, result.audioStream, result.controlType,
+                    result.maxVolume, result.currentVolume);
+
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error calling getVolumeInfo.", e);
+        }
+        return null;
+    }
+
+    /**
      * Adds a callback to receive updates from the Session. Updates will be
      * posted on the caller's thread.
      *
@@ -509,6 +527,85 @@
         }
     }
 
+    /**
+     * Holds information about the way volume is handled for this session.
+     */
+    public static final class VolumeInfo {
+        private final int mVolumeType;
+        private final int mAudioStream;
+        private final int mVolumeControl;
+        private final int mMaxVolume;
+        private final int mCurrentVolume;
+
+        /**
+         * @hide
+         */
+        public VolumeInfo(int type, int stream, int control, int max, int current) {
+            mVolumeType = type;
+            mAudioStream = stream;
+            mVolumeControl = control;
+            mMaxVolume = max;
+            mCurrentVolume = current;
+        }
+
+        /**
+         * Get the type of volume handling, either local or remote. One of:
+         * <ul>
+         * <li>{@link MediaSession#VOLUME_TYPE_LOCAL}</li>
+         * <li>{@link MediaSession#VOLUME_TYPE_REMOTE}</li>
+         * </ul>
+         *
+         * @return The type of volume handling this session is using.
+         */
+        public int getVolumeType() {
+            return mVolumeType;
+        }
+
+        /**
+         * Get the stream this is currently controlling volume on. When the volume
+         * type is {@link MediaSession#VOLUME_TYPE_REMOTE} this value does not
+         * have meaning and should be ignored.
+         *
+         * @return The stream this session is playing on.
+         */
+        public int getAudioStream() {
+            return mAudioStream;
+        }
+
+        /**
+         * Get the type of volume control that can be used. One of:
+         * <ul>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
+         * </ul>
+         *
+         * @return The type of volume control that may be used with this
+         *         session.
+         */
+        public int getVolumeControl() {
+            return mVolumeControl;
+        }
+
+        /**
+         * Get the maximum volume that may be set for this session.
+         *
+         * @return The maximum allowed volume where this session is playing.
+         */
+        public int getMaxVolume() {
+            return mMaxVolume;
+        }
+
+        /**
+         * Get the current volume for this session.
+         *
+         * @return The current volume where this session is playing.
+         */
+        public int getCurrentVolume() {
+            return mCurrentVolume;
+        }
+    }
+
     private final static class CallbackStub extends ISessionControllerCallback.Stub {
         private final WeakReference<MediaController> mController;
 
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4ba1351..406b1c3 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,10 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.VolumeProvider;
 import android.media.session.ISessionController;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -125,18 +127,12 @@
     public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
 
     /**
-     * The session uses local playback. Used for configuring volume handling
-     * with the system.
-     *
-     * @hide
+     * The session uses local playback.
      */
     public static final int VOLUME_TYPE_LOCAL = 1;
 
     /**
-     * The session uses remote playback. Used for configuring volume handling
-     * with the system.
-     *
-     * @hide
+     * The session uses remote playback.
      */
     public static final int VOLUME_TYPE_REMOTE = 2;
 
@@ -155,7 +151,7 @@
             = new ArrayMap<String, RouteInterface.EventListener>();
 
     private Route mRoute;
-    private RemoteVolumeProvider mVolumeProvider;
+    private VolumeProvider mVolumeProvider;
 
     private boolean mActive = false;;
 
@@ -232,6 +228,21 @@
     }
 
     /**
+     * Set a media button event receiver component to use to restart playback
+     * after an app has been stopped.
+     *
+     * @param mbr The receiver component to send the media button event to.
+     * @hide
+     */
+    public void setMediaButtonReceiver(ComponentName mbr) {
+        try {
+            mBinder.setMediaButtonReceiver(mbr);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+        }
+    }
+
+    /**
      * Set any flags for the session.
      *
      * @param flags The flags to set for this session.
@@ -272,7 +283,7 @@
      * @param volumeProvider The provider that will handle volume changes. May
      *            not be null.
      */
-    public void setPlaybackToRemote(RemoteVolumeProvider volumeProvider) {
+    public void setPlaybackToRemote(VolumeProvider volumeProvider) {
         if (volumeProvider == null) {
             throw new IllegalArgumentException("volumeProvider may not be null!");
         }
@@ -524,12 +535,12 @@
     }
 
     /**
-     * Notify the system that the remove volume changed.
+     * Notify the system that the remote volume changed.
      *
      * @param provider The provider that is handling volume changes.
      * @hide
      */
-    void notifyRemoteVolumeChanged(RemoteVolumeProvider provider) {
+    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
         if (provider == null || provider != mVolumeProvider) {
             Log.w(TAG, "Received update from stale volume provider");
             return;
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 801844f..838b857 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.media.MediaMetadata;
@@ -214,7 +215,7 @@
         }
     }
 
-    public void addMediaButtonListener(PendingIntent pi,
+    public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
             Context context) {
         if (pi == null) {
             Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
@@ -238,6 +239,7 @@
 
         holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context);
         holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler);
+        holder.mSession.setMediaButtonReceiver(mbrComponent);
         if (DEBUG) {
             Log.d(TAG, "addMediaButtonListener added " + pi);
         }
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/media/java/android/media/session/ParcelableVolumeInfo.aidl
new file mode 100644
index 0000000..c4250f0
--- /dev/null
+++ b/media/java/android/media/session/ParcelableVolumeInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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.session;
+
+parcelable ParcelableVolumeInfo;
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.java b/media/java/android/media/session/ParcelableVolumeInfo.java
new file mode 100644
index 0000000..166ccd3
--- /dev/null
+++ b/media/java/android/media/session/ParcelableVolumeInfo.java
@@ -0,0 +1,78 @@
+/* Copyright 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.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Convenience class for passing information about the audio configuration of a
+ * session. The public implementation is {@link MediaController.VolumeInfo}.
+ *
+ * @hide
+ */
+public class ParcelableVolumeInfo implements Parcelable {
+    public int volumeType;
+    public int audioStream;
+    public int controlType;
+    public int maxVolume;
+    public int currentVolume;
+
+    public ParcelableVolumeInfo(int volumeType, int audioStream, int controlType, int maxVolume,
+            int currentVolume) {
+        this.volumeType = volumeType;
+        this.audioStream = audioStream;
+        this.controlType = controlType;
+        this.maxVolume = maxVolume;
+        this.currentVolume = currentVolume;
+    }
+
+    public ParcelableVolumeInfo(Parcel from) {
+        volumeType = from.readInt();
+        audioStream = from.readInt();
+        controlType = from.readInt();
+        maxVolume = from.readInt();
+        currentVolume = from.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(volumeType);
+        dest.writeInt(audioStream);
+        dest.writeInt(controlType);
+        dest.writeInt(maxVolume);
+        dest.writeInt(currentVolume);
+    }
+
+
+    public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR
+            = new Parcelable.Creator<ParcelableVolumeInfo>() {
+        @Override
+        public ParcelableVolumeInfo createFromParcel(Parcel in) {
+            return new ParcelableVolumeInfo(in);
+        }
+
+        @Override
+        public ParcelableVolumeInfo[] newArray(int size) {
+            return new ParcelableVolumeInfo[size];
+        }
+    };
+}
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 4fbd2a4..5f27b16 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -110,6 +110,11 @@
     jfieldID certificateData;
 };
 
+struct StateExceptionFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -121,6 +126,7 @@
     IteratorFields iterator;
     EntryFields entry;
     CertificateFields certificate;
+    StateExceptionFields stateException;
     jclass certificateClassId;
     jclass hashmapClassId;
     jclass arraylistClassId;
@@ -212,6 +218,14 @@
     }
 }
 
+static void throwStateException(JNIEnv *env, const char *msg, status_t err) {
+    ALOGE("Illegal state exception: %s (%d)", msg, err);
+
+    jobject exception = env->NewObject(gFields.stateException.classId,
+            gFields.stateException.init, static_cast<int>(err),
+            env->NewStringUTF(msg));
+    env->Throw(static_cast<jthrowable>(exception));
+}
 
 static bool throwExceptionAsNecessary(
         JNIEnv *env, status_t err, const char *msg = NULL) {
@@ -275,8 +289,7 @@
                 msg = errbuf.string();
             }
         }
-        ALOGE("Illegal state exception: %s", msg);
-        jniThrowException(env, "java/lang/IllegalStateException", msg);
+        throwStateException(env, msg, err);
         return true;
     }
     return false;
@@ -608,6 +621,10 @@
 
     FIND_CLASS(clazz, "java/util/ArrayList");
     gFields.arraylistClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+    FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
+    GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+    gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
 }
 
 static void android_media_MediaDrm_native_setup(
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 7dba21d..b6bb578 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -205,6 +205,37 @@
         }
     }
 
+    @SmallTest
+    public void testConnectLegacy() throws Exception {
+        final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+            ICamera cameraUser = null;
+            ICameraClient dummyCallbacks = new DummyCameraClient();
+
+            String clientPackageName = getContext().getPackageName();
+
+            BinderHolder holder = new BinderHolder();
+
+            try {
+                CameraBinderDecorator.newInstance(mUtils.getCameraService())
+                        .connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
+                        clientPackageName,
+                        CameraBinderTestUtils.USE_CALLING_UID, holder);
+                cameraUser = ICamera.Stub.asInterface(holder.getBinder());
+                assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
+
+                Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
+            } catch (RuntimeException e) {
+                // Not all camera device support openLegacy.
+                Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+            } finally {
+                if (cameraUser != null) {
+                    cameraUser.disconnect();
+                }
+            }
+        }
+    }
+
     static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
 
         /*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
new file mode 100644
index 0000000..14bbe44
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 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.mediaframeworktest.unit;
+
+import android.hardware.Camera;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * <pre>
+ * adb shell am instrument \
+ *      -e class 'com.android.mediaframeworktest.unit.CameraOpenTest' \
+ *      -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
+ * </pre>
+ */
+public class CameraOpenTest extends junit.framework.TestCase {
+    private static String TAG = "CameraOpenTest";
+
+    private Camera mCamera;
+
+    /**
+     * Test @hide android.hardware.Camera#openLegacy API that cannot be tested in CTS.
+     */
+    @SmallTest
+    public void testOpenLegacy() {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            try {
+                mCamera.openLegacy(id, Camera.CAMERA_HAL_API_VERSION_1_0);
+            } catch (RuntimeException e) {
+                Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+            } finally {
+                if (mCamera != null) {
+                    mCamera.release();
+                }
+            }
+        }
+    }
+}
diff --git a/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml b/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
index b769a0c..acb7767 100644
--- a/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
+++ b/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
@@ -125,6 +125,11 @@
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="@string/audiomanagertwo" />
+        <CheckBox
+            android:id="@+id/useVirtualCallCheckBox"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/useVirtualCallCheckText" />
             
     </LinearLayout>
 
diff --git a/media/tests/ScoAudioTest/res/values/strings.xml b/media/tests/ScoAudioTest/res/values/strings.xml
index c3ff6d5..b0284e2 100644
--- a/media/tests/ScoAudioTest/res/values/strings.xml
+++ b/media/tests/ScoAudioTest/res/values/strings.xml
@@ -10,5 +10,5 @@
     <string name="tts_speak">Speak TTS</string>
     <string name="tts_to_file">TTS to file</string>
     <string name="audiomanagertwo">Use different AudioManager for starting SCO</string>
-    
+    <string name="useVirtualCallCheckText">Use Virtual Call</string>
 </resources>
diff --git a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
index 0304640..7e21876 100644
--- a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
+++ b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
@@ -207,16 +207,25 @@
             if (mForceScoOn != isChecked) {
                 mForceScoOn = isChecked;
                 AudioManager mngr = mAudioManager;
+                boolean useVirtualCall = false;
                 CheckBox box = (CheckBox) findViewById(R.id.useSecondAudioManager);
                 if (box.isChecked()) {
                     Log.i(TAG, "Using 2nd audio manager");
                     mngr = mAudioManager2;
                 }
+                box = (CheckBox) findViewById(R.id.useVirtualCallCheckBox);
+                useVirtualCall = box.isChecked();
 
                 if (mForceScoOn) {
-                    Log.e(TAG, "startBluetoothSco() IN");
-                    mngr.startBluetoothSco();
-                    Log.e(TAG, "startBluetoothSco() OUT");
+                    if (useVirtualCall) {
+                        Log.e(TAG, "startBluetoothScoVirtualCall() IN");
+                        mngr.startBluetoothScoVirtualCall();
+                        Log.e(TAG, "startBluetoothScoVirtualCall() OUT");
+                    } else {
+                        Log.e(TAG, "startBluetoothSco() IN");
+                        mngr.startBluetoothSco();
+                        Log.e(TAG, "startBluetoothSco() OUT");
+                    }
                 } else {
                     Log.e(TAG, "stopBluetoothSco() IN");
                     mngr.stopBluetoothSco();
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
index a4d292a..b8b6c04 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
@@ -169,15 +169,10 @@
     return true;
 }
 
-void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkBitmap::Config pref,
+void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkColorType pref,
         int width, int height) {
-    bm->setConfig(getColorSpaceConfig(pref), width, height, 0, kOpaque_SkAlphaType);
-}
-
-SkBitmap::Config OmxJpegImageDecoder::getColorSpaceConfig(
-        SkBitmap::Config pref) {
-
-    // Set the color space to ARGB_8888 for now
+    // Set the color space to ARGB_8888 for now (ignoring pref)
     // because of limitation in hardware support.
-    return SkBitmap::kARGB_8888_Config;
+    bm->setInfo(SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType);
 }
+
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
index e431e72..e487245 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
@@ -49,9 +49,7 @@
     sp<MediaSource> getDecoder(OMXClient* client, const sp<MediaSource>& source);
     bool decodeSource(sp<MediaSource> decoder, const sp<MediaSource>& source,
             SkBitmap* bm);
-    void configBitmapSize(SkBitmap* bm, SkBitmap::Config pref, int width,
-            int height);
-    SkBitmap::Config getColorSpaceConfig(SkBitmap::Config pref);
+    void configBitmapSize(SkBitmap* bm, SkColorType, int width, int height);
 
     OMXClient mClient;
 };
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index d2bf30c..ab18271 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -71,6 +71,7 @@
     private AppWidgetHost mAppWidgetHost;
     private AppWidgetManager mAppWidgetManager;
     private KeyguardWidgetPager mAppWidgetContainer;
+    // TODO remove transport control references, these don't exist anymore
     private KeyguardTransportControlView mTransportControl;
     private int mAppWidgetToShow;
 
@@ -235,36 +236,6 @@
                 mKeyguardMultiUserSelectorView.finalizeActiveUserView(true);
             }
         }
-        @Override
-        public void onMusicClientIdChanged(
-                int clientGeneration, boolean clearing, android.app.PendingIntent intent) {
-            // Set transport state to invisible until we know music is playing (below)
-            if (DEBUGXPORT && (mClientGeneration != clientGeneration || clearing)) {
-                Log.v(TAG, (clearing ? "hide" : "show") + " transport, gen:" + clientGeneration);
-            }
-            mClientGeneration = clientGeneration;
-            final int newState = (clearing ? TRANSPORT_GONE
-                    : (mTransportState == TRANSPORT_VISIBLE ?
-                    TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE));
-            if (newState != mTransportState) {
-                mTransportState = newState;
-                if (DEBUGXPORT) Log.v(TAG, "update widget: transport state changed");
-                KeyguardHostView.this.post(mSwitchPageRunnable);
-            }
-        }
-        @Override
-        public void onMusicPlaybackStateChanged(int playbackState, long eventTime) {
-            if (DEBUGXPORT) Log.v(TAG, "music state changed: " + playbackState);
-            if (mTransportState != TRANSPORT_GONE) {
-                final int newState = (isMusicPlaying(playbackState) ?
-                        TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE);
-                if (newState != mTransportState) {
-                    mTransportState = newState;
-                    if (DEBUGXPORT) Log.v(TAG, "update widget: play state changed");
-                    KeyguardHostView.this.post(mSwitchPageRunnable);
-                }
-            }
-        }
     };
 
     private static final boolean isMusicPlaying(int playbackState) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index 7918755..38316ff 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -166,6 +166,11 @@
         return info;
     }
 
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
     // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
     // This is an optimization to ensure we only recompute the patterns when the inputs change.
     private static final class Patterns {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 668e1ef..bf34705 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -91,8 +91,6 @@
     private static final int MSG_KEYGUARD_VISIBILITY_CHANGED = 312;
     protected static final int MSG_BOOT_COMPLETED = 313;
     private static final int MSG_USER_SWITCH_COMPLETE = 314;
-    private static final int MSG_SET_CURRENT_CLIENT_ID = 315;
-    protected static final int MSG_SET_PLAYBACK_STATE = 316;
     protected static final int MSG_USER_INFO_CHANGED = 317;
     protected static final int MSG_REPORT_EMERGENCY_CALL_ACTION = 318;
     private static final int MSG_SCREEN_TURNED_ON = 319;
@@ -184,12 +182,6 @@
                 case MSG_BOOT_COMPLETED:
                     handleBootCompleted();
                     break;
-                case MSG_SET_CURRENT_CLIENT_ID:
-                    handleSetGenerationId(msg.arg1, msg.arg2 != 0, (PendingIntent) msg.obj);
-                    break;
-                case MSG_SET_PLAYBACK_STATE:
-                    handleSetPlaybackState(msg.arg1, msg.arg2, (Long) msg.obj);
-                    break;
                 case MSG_USER_INFO_CHANGED:
                     handleUserInfoChanged(msg.arg1);
                     break;
@@ -206,8 +198,6 @@
         }
     };
 
-    private AudioManager mAudioManager;
-
     private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
 
     @Override
@@ -257,49 +247,6 @@
 
     private DisplayClientState mDisplayClientState = new DisplayClientState();
 
-    /**
-     * This currently implements the bare minimum required to enable showing and hiding
-     * KeyguardTransportControl.  There's a lot of client state to maintain which is why
-     * KeyguardTransportControl maintains an independent connection while it's showing.
-     */
-    private final IRemoteControlDisplay.Stub mRemoteControlDisplay =
-                new IRemoteControlDisplay.Stub() {
-
-        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
-                long currentPosMs, float speed) {
-            Message msg = mHandler.obtainMessage(MSG_SET_PLAYBACK_STATE,
-                    generationId, state, stateChangeTimeMs);
-            mHandler.sendMessage(msg);
-        }
-
-        public void setMetadata(int generationId, Bundle metadata) {
-
-        }
-
-        public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
-
-        }
-
-        public void setArtwork(int generationId, Bitmap bitmap) {
-
-        }
-
-        public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
-
-        }
-
-        public void setEnabled(boolean enabled) {
-            // no-op: this RemoteControlDisplay is not subject to being disabled.
-        }
-
-        public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
-                boolean clearing) throws RemoteException {
-            Message msg = mHandler.obtainMessage(MSG_SET_CURRENT_CLIENT_ID,
-                        clientGeneration, (clearing ? 1 : 0), mediaIntent);
-            mHandler.sendMessage(msg);
-        }
-    };
-
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
 
         public void onReceive(Context context, Intent intent) {
@@ -501,38 +448,6 @@
         }
     }
 
-    protected void handleSetGenerationId(int clientGeneration, boolean clearing, PendingIntent p) {
-        mDisplayClientState.clientGeneration = clientGeneration;
-        mDisplayClientState.clearing = clearing;
-        mDisplayClientState.intent = p;
-        if (DEBUG)
-            Log.v(TAG, "handleSetGenerationId(g=" + clientGeneration + ", clear=" + clearing + ")");
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onMusicClientIdChanged(clientGeneration, clearing, p);
-            }
-        }
-    }
-
-    protected void handleSetPlaybackState(int generationId, int playbackState, long eventTime) {
-        if (DEBUG)
-            Log.v(TAG, "handleSetPlaybackState(gen=" + generationId
-                + ", state=" + playbackState + ", t=" + eventTime + ")");
-        mDisplayClientState.playbackState = playbackState;
-        mDisplayClientState.playbackEventTime = eventTime;
-        if (generationId == mDisplayClientState.clientGeneration) {
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-                if (cb != null) {
-                    cb.onMusicPlaybackStateChanged(playbackState, eventTime);
-                }
-            }
-        } else {
-            Log.w(TAG, "Ignoring generation id " + generationId + " because it's not current");
-        }
-    }
-
     private void handleUserInfoChanged(int userId) {
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -694,8 +609,6 @@
     protected void handleBootCompleted() {
         if (mBootCompleted) return;
         mBootCompleted = true;
-        mAudioManager = new AudioManager(mContext);
-        mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1013,12 +926,6 @@
         callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
         callback.onClockVisibilityChanged();
         callback.onSimStateChanged(mSimState);
-        callback.onMusicClientIdChanged(
-                mDisplayClientState.clientGeneration,
-                mDisplayClientState.clearing,
-                mDisplayClientState.intent);
-        callback.onMusicPlaybackStateChanged(mDisplayClientState.playbackState,
-                mDisplayClientState.playbackEventTime);
     }
 
     public void sendKeyguardVisibilityChanged(boolean showing) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index bcdf18f..01600d2 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -144,18 +144,6 @@
     public void onBootCompleted() { }
 
     /**
-     * Called when audio client attaches or detaches from AudioManager.
-     */
-    public void onMusicClientIdChanged(int clientGeneration, boolean clearing, PendingIntent intent) { }
-
-    /**
-     * Called when the audio playback state changes.
-     * @param playbackState
-     * @param eventTime
-     */
-    public void onMusicPlaybackStateChanged(int playbackState, long eventTime) { }
-
-    /**
      * Called when the emergency call button is pressed.
      */
     void onEmergencyCallAction() { }
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
index a7e8514..4a4c1c1 100644
--- a/packages/SystemUI/res/drawable/ic_account_circle.xml
+++ b/packages/SystemUI/res/drawable/ic_account_circle.xml
@@ -22,7 +22,13 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"/>
 
+    <group
+        android:scaleX="1.2"
+        android:scaleY="1.2"
+        android:pivotX="12.0"
+        android:pivotY="12.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.0zM12.0,5.0c1.7,0.0 3.0,1.3 3.0,3.0c0.0,1.7 -1.3,3.0 -3.0,3.0c-1.7,0.0 -3.0,-1.3 -3.0,-3.0C9.0,6.3 10.3,5.0 12.0,5.0zM12.0,19.2c-2.5,0.0 -4.7,-1.3 -6.0,-3.2c0.0,-2.0 4.0,-3.1 6.0,-3.1c2.0,0.0 6.0,1.1 6.0,3.1C16.7,17.9 14.5,19.2 12.0,19.2z"/>
+    </group>
 </vector>
diff --git a/packages/SystemUI/res/drawable/notification_scrim.xml b/packages/SystemUI/res/drawable/notification_scrim.xml
new file mode 100644
index 0000000..ff7e31f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_scrim.xml
@@ -0,0 +1,22 @@
+<?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">
+    <solid android:color="#34000000" />
+    <corners android:radius="@*android:dimen/notification_material_rounded_rect_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
new file mode 100644
index 0000000..5648065
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/keyguard_user_switcher"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:gravity="end"
+        android:visibility="gone"
+        android:paddingTop="4dp">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
new file mode 100644
index 0000000..691a80e
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -0,0 +1,40 @@
+<?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
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:layout_marginEnd="8dp"
+        android:gravity="center_vertical"
+        android:clickable="true"
+        android:background="@drawable/ripple_drawable">
+    <TextView android:id="@+id/name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="16dp"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.UserName"
+            />
+    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/picture"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:contentDescription="@null"
+            sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
+            sysui:activeFrameColor="@color/current_user_border_color" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index cde83bf..b54ba1a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -24,6 +24,7 @@
     android:id="@+id/notification_panel"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="@android:color/transparent"
     >
 
     <include
@@ -91,6 +92,14 @@
 
     <include layout="@layout/status_bar_expanded_header" />
 
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
+        android:layout_gravity="end"
+        android:layout="@layout/keyguard_user_switcher" />
+
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 353368b..2e4c0ef 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -73,9 +73,9 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/status_bar_header_height"
         android:layout_toStartOf="@id/multi_user_switch"
-        android:layout_marginEnd="2dp"
+        android:layout_alignWithParentIfMissing="true"
         android:layout_marginStart="16dp"
-        />
+        android:paddingEnd="2dp" />
 
     <TextView
         android:id="@+id/header_charging_info"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index c442f79..f0f50e1 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -49,4 +49,10 @@
         android:layout_width="120dp"
         android:layout_height="wrap_content"
         />
+
+    <com.android.systemui.statusbar.NotificationScrimView
+        android:id="@+id/scrim_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 5fabd3e..7663d54 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -48,4 +48,9 @@
         android:padding="2dp"
         />
 
+    <com.android.systemui.statusbar.NotificationScrimView
+        android:id="@+id/scrim_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </com.android.systemui.statusbar.ExpandableNotificationRow>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 47581a9..9f4c364 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -32,4 +32,7 @@
     <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
          card. -->
     <integer name="keyguard_max_notification_count">5</integer>
+
+    <!-- Set to true to enable the user switcher on the keyguard. -->
+    <bool name="config_keyguardUserSwitcher">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index c453618..8473d96 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -54,5 +54,10 @@
         <enum name="horizontal" value="0" />
         <enum name="vertical" value="1" />
     </attr>
+    <declare-styleable name="UserAvatarView">
+        <attr name="frameWidth" format="dimension" />
+        <attr name="activeFrameColor" format="color" />
+        <attr name="frameColor" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3bd8689..4e38da6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -60,6 +60,9 @@
 
     <color name="keyguard_affordance">#ffffffff</color>
 
+    <!-- The color of the circle around the primary user in the user switcher -->
+    <color name="current_user_border_color">@color/primary_color</color>
+
     <!-- Our material color palette (deep teal) -->
     <color name="primary_color">#ff7fcac3</color>
     <color name="background_color_1">#ff384248</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c64a182..48b327d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -154,5 +154,8 @@
          Notification.tickerText across the status bar for what seems like an
          eternity. -->
     <bool name="enable_ticker">false</bool>
+
+    <!-- Set to true to enable the user switcher on the keyguard. -->
+    <bool name="config_keyguardUserSwitcher">false</bool>
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bbcc9c1..36c1994 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -320,6 +320,9 @@
          device. -->
     <dimen name="unlock_move_distance">75dp</dimen>
 
+    <!-- Distance after which the scrim starts fading in when dragging down the quick settings -->
+    <dimen name="notification_scrim_wait_distance">100dp</dimen>
+
     <!-- Move distance for the unlock hint animation on the lockscreen -->
     <dimen name="hint_move_distance">75dp</dimen>
 
@@ -332,4 +335,11 @@
 
     <!-- end margin for multi user switch in expanded quick settings -->
     <dimen name="multi_user_switch_expanded_margin">8dp</dimen>
+
+    <!-- end margin for system icons if multi user switch is hidden -->
+    <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
+
+    <!-- The thickness of the colored border around the current user. -->
+    <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c117eba..e5d5b03 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -100,6 +100,13 @@
     <style name="TextAppearance.StatusBar.Expanded.Network.EmergencyOnly">
     </style>
 
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.UserName" />
+
     <style name="TextAppearance" />
     <style name="TextAppearance.QuickSettings" />
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 8d19f50..f6f78e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -119,6 +119,7 @@
 
     private NotificationBackgroundView mBackgroundNormal;
     private NotificationBackgroundView mBackgroundDimmed;
+    private NotificationScrimView mScrimView;
     private ObjectAnimator mBackgroundAnimator;
     private RectF mAppearAnimationRect = new RectF();
     private PorterDuffColorFilter mAppearAnimationFilter;
@@ -153,6 +154,7 @@
         mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
         updateBackground();
         updateBackgroundResources();
+        mScrimView = (NotificationScrimView) findViewById(R.id.scrim_view);
     }
 
     private final Runnable mTapTimeoutRunnable = new Runnable() {
@@ -379,6 +381,7 @@
         setPivotY(actualHeight / 2);
         mBackgroundNormal.setActualHeight(actualHeight);
         mBackgroundDimmed.setActualHeight(actualHeight);
+        mScrimView.setActualHeight(actualHeight);
     }
 
     @Override
@@ -386,6 +389,7 @@
         super.setClipTopAmount(clipTopAmount);
         mBackgroundNormal.setClipTopAmount(clipTopAmount);
         mBackgroundDimmed.setClipTopAmount(clipTopAmount);
+        mScrimView.setClipTopAmount(clipTopAmount);
     }
 
     @Override
@@ -405,6 +409,11 @@
         }
     }
 
+    @Override
+    public void setScrimAmount(float scrimAmount) {
+        mScrimView.setAlpha(scrimAmount);
+    }
+
     private void startAppearAnimation(boolean isAppearing,
             float translationDirection, long delay, final Runnable onFinishedRunnable) {
         if (mAppearAnimator != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index ac2537c..4d4a8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -223,6 +223,8 @@
 
     public abstract void performAddAnimation(long delay);
 
+    public abstract void setScrimAmount(float scrimAmount);
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java
new file mode 100644
index 0000000..440b2c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.keyguard.R;
+
+/**
+ * A view that can be used for both the dimmed and normal background of an notification.
+ */
+public class NotificationScrimView extends View {
+
+    private Drawable mBackground;
+    private int mClipTopAmount;
+    private int mActualHeight;
+
+    public NotificationScrimView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mBackground = getResources().getDrawable(R.drawable.notification_scrim);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        draw(canvas, mBackground);
+    }
+
+    private void draw(Canvas canvas, Drawable drawable) {
+        if (drawable != null) {
+            drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
+            drawable.draw(canvas);
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mBackground;
+    }
+
+    public void setActualHeight(int actualHeight) {
+        mActualHeight = actualHeight;
+        invalidate();
+    }
+
+    public int getActualHeight() {
+        return mActualHeight;
+    }
+
+    public void setClipTopAmount(int clipTopAmount) {
+        mClipTopAmount = clipTopAmount;
+        invalidate();
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+
+        // Prevents this view from creating a layer when alpha is animating.
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
index f80f0fd..650abaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -121,4 +121,9 @@
     public void performAddAnimation(long delay) {
         performVisibilityAnimation(true);
     }
+
+    @Override
+    public void setScrimAmount(float scrimAmount) {
+        // We don't need to scrim the speedbumps
+    }
 }
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 97aa993..63698e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -58,6 +58,7 @@
     private ImageView mCameraImageView;
     private ImageView mPhoneImageView;
     private ImageView mLockIcon;
+    private View mIndicationText;
 
     private ActivityStarter mActivityStarter;
     private UnlockMethodCache mUnlockMethodCache;
@@ -87,6 +88,7 @@
         mCameraImageView = (ImageView) findViewById(R.id.camera_button);
         mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
         mLockIcon = (ImageView) findViewById(R.id.lock_icon);
+        mIndicationText = findViewById(R.id.keyguard_indication_text);
         watchForCameraPolicyChanges();
         watchForAccessibilityChanges();
         updateCameraVisibility();
@@ -231,6 +233,10 @@
         return mLockIcon;
     }
 
+    public View getIndicationView() {
+        return mIndicationText;
+    }
+
     @Override
     public void onMethodSecureChanged(boolean methodSecure) {
         updateTrust();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 6a83a5e..319096d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.graphics.Path;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.systemui.R;
@@ -31,6 +32,10 @@
 
     private static final float CLOCK_RUBBERBAND_FACTOR_MIN = 0.08f;
     private static final float CLOCK_RUBBERBAND_FACTOR_MAX = 0.8f;
+    private static final float CLOCK_SCALE_FADE_START = 0.95f;
+    private static final float CLOCK_SCALE_FADE_END = 0.75f;
+    private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f;
+
 
     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f;
     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f;
@@ -61,6 +66,8 @@
         sSlowDownInterpolator = new PathInterpolator(path);
     }
 
+    private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
+
     /**
      * Refreshes the dimension values.
      */
@@ -87,18 +94,29 @@
     }
 
     public void run(Result result) {
-        int y = getClockY() - mKeyguardStatusHeight/2;
+        int y = getClockY() - mKeyguardStatusHeight / 2;
         float clockAdjustment = getClockYExpansionAdjustment();
         float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier();
         result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier);
         int clockNotificationsPadding = getClockNotificationsPadding()
                 + result.stackScrollerPaddingAdjustment;
         int padding = y + clockNotificationsPadding;
-        y += clockAdjustment;
         result.clockY = y;
         result.stackScrollerPadding = mKeyguardStatusHeight + padding;
-        result.clockAlpha = getClockAlpha(result.stackScrollerPadding
-                - (y + mKeyguardStatusHeight));
+        result.clockScale = getClockScale(result.stackScrollerPadding,
+                result.clockY,
+                y + getClockNotificationsPadding() + mKeyguardStatusHeight);
+        result.clockAlpha = getClockAlpha(result.clockScale);
+    }
+
+    private float getClockScale(int notificationPadding, int clockY, int startPadding) {
+        float scaleMultiplier = getNotificationAmountT() == 0 ? 6.0f : 5.0f;
+        float scaleEnd = clockY - mKeyguardStatusHeight * scaleMultiplier;
+        float distanceToScaleEnd = notificationPadding - scaleEnd;
+        float progress = distanceToScaleEnd / (startPadding - scaleEnd);
+        progress = Math.max(0.0f, Math.min(progress, 1.0f));
+        progress = mAccelerateInterpolator.getInterpolation(progress);
+        return progress;
     }
 
     private int getClockNotificationsPadding() {
@@ -144,11 +162,12 @@
                 + t * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX;
     }
 
-    private float getClockAlpha(int clockNotificationPadding) {
-        float t = getNotificationAmountT();
-        t = (float) Math.pow(t, 0.3f);
-        float multiplier = 1 + 2 * (1 - t);
-        float alpha = 1 + (float) clockNotificationPadding * multiplier / mKeyguardStatusHeight * 3;
+    private float getClockAlpha(float scale) {
+        float fadeEnd = getNotificationAmountT() == 0.0f
+                ? CLOCK_SCALE_FADE_END_NO_NOTIFS
+                : CLOCK_SCALE_FADE_END;
+        float alpha = (scale - fadeEnd)
+                / (CLOCK_SCALE_FADE_START - fadeEnd);
         return Math.max(0, Math.min(1, alpha));
     }
 
@@ -168,6 +187,11 @@
         public int clockY;
 
         /**
+         * The scale of the Clock
+         */
+        public float clockScale;
+
+        /**
          * The alpha value of the clock.
          */
         public float clockAlpha;
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 dde95bf..a6ce5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
@@ -48,6 +49,11 @@
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
         KeyguardPageSwipeHelper.Callback {
 
+    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+    // changed.
+    private static final int CAP_HEIGHT = 1456;
+    private static final int FONT_HEIGHT = 2163;
+
     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
 
     private KeyguardPageSwipeHelper mPageSwiper;
@@ -56,7 +62,7 @@
     private View mQsPanel;
     private View mKeyguardStatusView;
     private ObservableScrollView mScrollView;
-    private View mStackScrollerContainer;
+    private TextView mClockView;
 
     private NotificationStackScrollLayout mNotificationStackScroller;
     private int mNotificationTopPadding;
@@ -105,9 +111,11 @@
             new KeyguardClockPositionAlgorithm.Result();
     private boolean mIsSwipedHorizontally;
     private boolean mIsExpanding;
-    private KeyguardBottomAreaView mKeyguardBottomArea;
+
     private boolean mBlockTouches;
     private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
+    private int mNotificationScrimWaitDistance;
+    private boolean mOnNotificationsOnDown;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -135,9 +143,9 @@
         mHeader.getBackgroundView().setOnClickListener(this);
         mHeader.setOverlayParent(this);
         mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
-        mStackScrollerContainer = findViewById(R.id.notification_container_parent);
         mQsContainer = findViewById(R.id.quick_settings_container);
         mQsPanel = findViewById(R.id.quick_settings_panel);
+        mClockView = (TextView) findViewById(R.id.clock_view);
         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
         mScrollView.setListener(this);
         mNotificationStackScroller = (NotificationStackScrollLayout)
@@ -169,12 +177,19 @@
                 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
         mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
         mClockPositionAlgorithm.loadDimens(getResources());
+        mNotificationScrimWaitDistance =
+                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
+        // Update Clock Pivot
+        mKeyguardStatusView.setPivotX(getWidth() / 2);
+        mKeyguardStatusView.setPivotY(
+                (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
+
         // Calculate quick setting heights.
         mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
         mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
@@ -197,7 +212,7 @@
      * showing.
      */
     private void positionClockAndNotifications() {
-        boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         int stackScrollerPadding;
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
             int bottom = mStackScrollerOverscrolling
@@ -215,17 +230,17 @@
                     getHeight(),
                     mKeyguardStatusView.getHeight());
             mClockPositionAlgorithm.run(mClockPositionResult);
-            if (animateClock || mClockAnimator != null) {
+            if (animate || mClockAnimator != null) {
                 startClockAnimation(mClockPositionResult.clockY);
             } else {
                 mKeyguardStatusView.setY(mClockPositionResult.clockY);
             }
-            applyClockAlpha(mClockPositionResult.clockAlpha);
+            updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
             stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
             mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
         }
         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
-        requestScrollerTopPaddingUpdate(animateClock);
+        requestScrollerTopPaddingUpdate(animate);
     }
 
     private void startClockAnimation(int y) {
@@ -258,13 +273,10 @@
         });
     }
 
-    private void applyClockAlpha(float alpha) {
-        if (alpha != 1.0f) {
-            mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null);
-        } else {
-            mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null);
-        }
+    private void updateClock(float alpha, float scale) {
         mKeyguardStatusView.setAlpha(alpha);
+        mKeyguardStatusView.setScaleX(scale);
+        mKeyguardStatusView.setScaleY(scale);
     }
 
     public void animateToFullShade() {
@@ -344,6 +356,7 @@
                 mInitialTouchX = x;
                 initVelocityTracker();
                 trackMovement(event);
+                mOnNotificationsOnDown = isOnNotifications(x, y);
                 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
                     getParent().requestDisallowInterceptTouchEvent(true);
                 }
@@ -391,6 +404,8 @@
                 if (mQsTracking) {
                     flingQsWithCurrentVelocity();
                     mQsTracking = false;
+                } else if (mQsFullyExpanded && mOnNotificationsOnDown) {
+                    flingSettings(0 /* vel */, false /* expand */);
                 }
                 mIntercepting = false;
                 break;
@@ -398,6 +413,10 @@
         return !mQsExpanded && super.onInterceptTouchEvent(event);
     }
 
+    private boolean isOnNotifications(float x, float y) {
+        return mNotificationStackScroller.getChildAtPosition(x, y) != null;
+    }
+
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
 
@@ -577,9 +596,17 @@
         mHeader.setExpansion(height - mQsPeekHeight);
         setQsTranslation(height);
         requestScrollerTopPaddingUpdate(false /* animate */);
+        updateNotificationScrim(height);
         mStatusBar.userActivity();
     }
 
+    private void updateNotificationScrim(float height) {
+        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
+        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
+        progress = Math.max(0.0f, Math.min(progress, 1.0f));
+        mNotificationStackScroller.setScrimAlpha(progress);
+    }
+
     private void setQsTranslation(float height) {
         mQsContainer.setY(height - mQsContainer.getHeight());
     }
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 1f3098d..12aa004 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -62,6 +62,7 @@
     protected int mTouchSlop;
     protected boolean mHintAnimationRunning;
     private boolean mOverExpandedBeforeFling;
+    private float mOriginalIndicationY;
 
     private ValueAnimator mHeightAnimator;
     private ObjectAnimator mPeekAnimator;
@@ -82,6 +83,7 @@
 
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mBounceInterpolator;
+    protected KeyguardBottomAreaView mKeyguardBottomArea;
 
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
@@ -652,6 +654,22 @@
         });
         animator.start();
         mHeightAnimator = animator;
+        mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY();
+        mKeyguardBottomArea.getIndicationView().animate()
+                .y(mOriginalIndicationY - mHintDistance)
+                .setDuration(250)
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mKeyguardBottomArea.getIndicationView().animate()
+                                .y(mOriginalIndicationY)
+                                .setDuration(450)
+                                .setInterpolator(mBounceInterpolator)
+                                .start();
+                    }
+                })
+                .start();
     }
 
     /**
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 1da7dab..7016c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -35,6 +35,7 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -64,6 +65,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.provider.Settings.SettingNotFoundException;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -82,7 +84,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
 import android.view.ViewStub;
-import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
@@ -123,6 +124,7 @@
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
@@ -211,6 +213,7 @@
     ZenModeController mZenModeController;
     CastControllerImpl mCastController;
     VolumeComponent mVolumeComponent;
+    KeyguardUserSwitcher mKeyguardUserSwitcher;
 
     int mNaturalBarHeight = -1;
     int mIconSize = -1;
@@ -260,6 +263,10 @@
     boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationController mKeyguardIndicationController;
 
+    private boolean mKeyguardFadingAway;
+    private long mKeyguardFadingAwayDelay;
+    private long mKeyguardFadingAwayDuration;
+
     int mKeyguardMaxNotificationCount;
     View mDateTimeView;
 
@@ -399,7 +406,9 @@
     private boolean mSettingsCancelled;
     private boolean mSettingsClosing;
     private boolean mVisible;
+    private boolean mWaitingForKeyguardExit;
 
+    private Interpolator mLinearOutSlowIn;
     private Interpolator mAlphaOut = new PathInterpolator(0f, 0.4f, 1f, 1f);
     private Interpolator mAlphaIn = new PathInterpolator(0f, 0f, 0.8f, 1f);
 
@@ -713,6 +722,8 @@
         final SignalClusterView signalCluster =
                 (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
 
+        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
+                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader);
 
         mNetworkController.addSignalCluster(signalCluster);
         signalCluster.setNetworkController(mNetworkController);
@@ -893,6 +904,14 @@
         }
     };
 
+    private View.OnLongClickListener mLockToAppClickListener = new View.OnLongClickListener() {
+        @Override
+        public boolean onLongClick(View v) {
+            toggleLockedApp();
+            return true;
+        }
+    };
+
     private int mShowSearchHoldoff = 0;
     private Runnable mShowSearchPanel = new Runnable() {
         public void run() {
@@ -936,6 +955,8 @@
 
         mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
         mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
+        mNavigationBarView.getRecentsButton().setLongClickable(true);
+        mNavigationBarView.getRecentsButton().setOnLongClickListener(mLockToAppClickListener);
         mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
         updateSearchPanel();
     }
@@ -1453,7 +1474,7 @@
     }
 
     private int adjustDisableFlags(int state) {
-        if (mExpandedVisible) {
+        if (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit) {
             state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
             state |= StatusBarManager.DISABLE_SYSTEM_INFO;
         }
@@ -1501,19 +1522,9 @@
         if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
             mSystemIconArea.animate().cancel();
             if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
-                mSystemIconArea.animate()
-                    .alpha(0f)
-                    .withLayer()
-                    .setDuration(160)
-                    .setInterpolator(mAlphaIn)
-                    .setListener(mMakeIconsInvisible);
+                animateStatusBarHide(mSystemIconArea);
             } else {
-                mSystemIconArea.setVisibility(View.VISIBLE);
-                mSystemIconArea.animate()
-                    .alpha(1f)
-                    .withLayer()
-                    .setInterpolator(mAlphaOut)
-                    .setDuration(320);
+                animateStatusBarShow(mSystemIconArea);
             }
         }
 
@@ -1546,25 +1557,48 @@
                 if (mTicking) {
                     haltTicker();
                 }
-
-                mNotificationIcons.animate()
-                    .alpha(0f)
-                    .withLayer()
-                    .setDuration(160)
-                    .setInterpolator(mAlphaIn)
-                    .setListener(mMakeIconsInvisible)
-                    .start();
+                animateStatusBarHide(mNotificationIcons);
             } else {
-                mNotificationIcons.setVisibility(View.VISIBLE);
-                mNotificationIcons.animate()
-                    .alpha(1f)
-                    .withLayer()
-                    .setInterpolator(mAlphaOut)
-                    .setDuration(320);
+                animateStatusBarShow(mNotificationIcons);
             }
         }
     }
 
+    /**
+     * Animates {@code v}, a view that is part of the status bar, out.
+     */
+    private void animateStatusBarHide(View v) {
+        v.animate()
+                .alpha(0f)
+                .withLayer()
+                .setDuration(160)
+                .setInterpolator(mAlphaIn)
+                .setStartDelay(0)
+                .setListener(mMakeIconsInvisible)
+                .start();
+    }
+
+    /**
+     * Animates {@code v}, a view that is part of the status bar, in.
+     */
+    private void animateStatusBarShow(View v) {
+        v.setVisibility(View.VISIBLE);
+        v.animate()
+                .alpha(1f)
+                .withLayer()
+                .setInterpolator(mAlphaOut)
+                .setDuration(320)
+                .setStartDelay(0);
+
+        // Synchronize the motion with the Keyguard fading if necessary.
+        if (mKeyguardFadingAway) {
+            v.animate()
+                    .setDuration(mKeyguardFadingAwayDuration)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .setStartDelay(mKeyguardFadingAwayDelay);
+        }
+    }
+
     @Override
     protected BaseStatusBar.H createHandler() {
         return new PhoneStatusBar.H();
@@ -1666,6 +1700,7 @@
         mStatusBarWindowManager.setStatusBarExpanded(true);
 
         visibilityChanged(true);
+        mWaitingForKeyguardExit = false;
         disable(mDisabledUnmodified);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
@@ -1859,8 +1894,8 @@
         }
 
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
-        disable(mDisabledUnmodified);
         showBouncer();
+        disable(mDisabledUnmodified);
     }
 
     public boolean interceptTouchEvent(MotionEvent event) {
@@ -2574,6 +2609,8 @@
         if (mQSPanel != null) mQSPanel.updateResources();
 
         loadDimens();
+        mLinearOutSlowIn = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.linear_out_slow_in);
     }
 
     protected void loadDimens() {
@@ -2901,6 +2938,27 @@
         updateKeyguardState();
     }
 
+    /**
+     * Notifies the status bar the Keyguard is fading away with the specified timings.
+     *
+     * @param delay the animation delay in miliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
+     */
+    public void setKeyguardFadingAway(long delay, long fadeoutDuration) {
+        mKeyguardFadingAway = true;
+        mKeyguardFadingAwayDelay = delay;
+        mKeyguardFadingAwayDuration = fadeoutDuration;
+        mWaitingForKeyguardExit = false;
+        disable(mDisabledUnmodified);
+    }
+
+    /**
+     * Notifies that the Keyguard fading away animation is done.
+     */
+    public void finishKeyguardFadingAway() {
+        mKeyguardFadingAway = false;
+    }
+
     private void updatePublicMode() {
         setLockscreenPublicMode(
                 (mStatusBarKeyguardViewManager.isShowing() || 
@@ -2913,9 +2971,11 @@
             mKeyguardStatusView.setVisibility(View.VISIBLE);
             mKeyguardIndicationController.setVisible(true);
             mNotificationPanel.resetViews();
+            mKeyguardUserSwitcher.setKeyguard(true);
         } else {
             mKeyguardStatusView.setVisibility(View.GONE);
             mKeyguardIndicationController.setVisible(false);
+            mKeyguardUserSwitcher.setKeyguard(false);
         }
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
@@ -2975,6 +3035,7 @@
 
     private void showBouncer() {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+            mWaitingForKeyguardExit = true;
             mStatusBarKeyguardViewManager.dismiss();
         }
     }
@@ -3125,6 +3186,12 @@
         mSystemIconArea.addView(mSystemIcons, 0);
     }
 
+    @Override
+    public void setBouncerShowing(boolean bouncerShowing) {
+        super.setBouncerShowing(bouncerShowing);
+        disable(mDisabledUnmodified);
+    }
+
     public void onScreenTurnedOff() {
         mStackScroller.setAnimationsEnabled(false);
     }
@@ -3133,6 +3200,28 @@
         mStackScroller.setAnimationsEnabled(true);
     }
 
+    public void toggleLockedApp() {
+        Log.d(TAG, "Trying to toggle lock-to-app");
+        try {
+            IActivityManager activityManager = ActivityManagerNative.getDefault();
+            if (activityManager.isInLockTaskMode()) {
+                activityManager.stopLockTaskModeOnCurrent();
+            } else {
+                try {
+                    boolean lockToAppEnabled = Settings.System.getInt(mContext.getContentResolver(),
+                            Settings.System.LOCK_TO_APP_ENABLED) != 0;
+                    if (lockToAppEnabled) {
+                        activityManager.startLockTaskModeOnCurrent();
+                    }
+                } catch (SettingNotFoundException e) {
+                    // No setting, not enabled.
+                }
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Unable to toggle Lock-to-app", e);
+        }
+    }
+
     private final Runnable mUserActivity = new Runnable() {
         @Override
         public void run() {
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 1712124..fc10a08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -63,6 +63,7 @@
 
     private boolean mShowEmergencyCallsOnly;
     private boolean mShowChargingInfo;
+    private boolean mKeyguardUserSwitcherShowing;
 
     private int mCollapsedHeight;
     private int mExpandedHeight;
@@ -72,6 +73,7 @@
     private int mNormalWidth;
     private int mPadding;
     private int mMultiUserExpandedMargin;
+    private int mSystemIconsSwitcherHiddenExpandedMargin;
 
     private ActivityStarter mActivityStarter;
     private BrightnessController mBrightnessController;
@@ -125,7 +127,8 @@
         mPadding = getResources().getDimensionPixelSize(R.dimen.notification_side_padding);
         mMultiUserExpandedMargin =
                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
-
+        mSystemIconsSwitcherHiddenExpandedMargin = getResources().getDimensionPixelSize(
+                R.dimen.system_icons_switcher_hidden_expanded_margin);
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -216,12 +219,15 @@
                 ? VISIBLE : GONE);
         mChargingInfo.setVisibility(mExpanded && !mOverscrolled && mShowChargingInfo
                 && !mShowEmergencyCallsOnly ? VISIBLE : GONE);
+        mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing
+                ? VISIBLE : GONE);
     }
 
     private void updateSystemIconsLayoutParams() {
         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsContainer.getLayoutParams();
         boolean systemIconsAboveClock = mExpanded && !mOverscrolled
                 && mShowChargingInfo && !mShowEmergencyCallsOnly;
+        lp.setMarginEnd(0);
         if (systemIconsAboveClock) {
             lp.addRule(ALIGN_PARENT_START);
             lp.removeRule(START_OF);
@@ -230,7 +236,11 @@
                     ? mSettingsButton.getId()
                     : mMultiUserSwitch.getId());
             lp.removeRule(ALIGN_PARENT_START);
+            if (mMultiUserSwitch.getVisibility() == GONE) {
+                lp.setMarginEnd(mSystemIconsSwitcherHiddenExpandedMargin);
+            }
         }
+        mSystemIconsContainer.setLayoutParams(lp);
 
         RelativeLayout.LayoutParams clockLp = (LayoutParams) mDateTime.getLayoutParams();
         if (systemIconsAboveClock) {
@@ -238,6 +248,7 @@
         } else {
             clockLp.addRule(BELOW, mEmergencyCallsOnly.getId());
         }
+        mDateTime.setLayoutParams(clockLp);
     }
 
     private void updateBrightnessControllerState() {
@@ -385,4 +396,11 @@
     public void setChargingInfo(CharSequence chargingInfo) {
         mChargingInfo.setText(chargingInfo);
     }
+
+    public void setKeyguardUserSwitcherShowing(boolean showing) {
+        // STOPSHIP: NOT CALLED PROPERLY WHEN GOING TO FULL SHADE AND RETURNING!?!
+        mKeyguardUserSwitcherShowing = showing;
+        updateVisibilities();
+        updateSystemIconsLayoutParams();
+    }
 }
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 09e4d94..a36f3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -190,19 +190,23 @@
      */
     public void hide(long startTime, long fadeoutDuration) {
         mShowing = false;
-        mPhoneStatusBar.hideKeyguard();
-        mStatusBarWindowManager.setKeyguardFadingAway(true);
-        mStatusBarWindowManager.setKeyguardShowing(false);
+
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = startTime - uptimeMillis;
         if (delay < 0) {
             delay = 0;
         }
+
+        mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
+        mPhoneStatusBar.hideKeyguard();
+        mStatusBarWindowManager.setKeyguardFadingAway(true);
+        mStatusBarWindowManager.setKeyguardShowing(false);
         mBouncer.animateHide(delay, fadeoutDuration);
         mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
             @Override
             public void run() {
                 mStatusBarWindowManager.setKeyguardFadingAway(false);
+                mPhoneStatusBar.finishKeyguardFadingAway();
             }
         });
         mViewMediatorCallback.keyguardGone();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
new file mode 100644
index 0000000..6f2642a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -0,0 +1,219 @@
+/*
+ * 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 com.android.systemui.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * A view that displays a user image cropped to a circle with a frame.
+ */
+public class UserAvatarView extends View {
+
+    private int mActiveFrameColor;
+    private int mFrameColor;
+    private float mFrameWidth;
+    private Bitmap mBitmap;
+    private Drawable mDrawable;
+
+    private final Paint mFramePaint = new Paint();
+    private final Paint mBitmapPaint = new Paint();
+    private final Matrix mDrawMatrix = new Matrix();
+
+    private float mScale = 1;
+
+    public UserAvatarView(Context context, AttributeSet attrs,
+            int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.UserAvatarView, defStyleAttr, defStyleRes);
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.UserAvatarView_frameWidth:
+                    setFrameWidth(a.getDimension(attr, 0));
+                    break;
+                case R.styleable.UserAvatarView_activeFrameColor:
+                    setActiveFrameColor(a.getColor(attr, 0));
+                    break;
+                case R.styleable.UserAvatarView_frameColor:
+                    setFrameColor(a.getColor(attr, 0));
+                    break;
+            }
+        }
+        a.recycle();
+
+        mFramePaint.setAntiAlias(true);
+        mFramePaint.setStyle(Paint.Style.STROKE);
+        mBitmapPaint.setAntiAlias(true);
+    }
+
+    public UserAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserAvatarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserAvatarView(Context context) {
+        this(context, null);
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        setDrawable(null);
+        mBitmap = bitmap;
+        if (mBitmap != null) {
+            mBitmapPaint.setShader(new BitmapShader(
+                    bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+        } else {
+            mBitmapPaint.setShader(null);
+        }
+        configureBounds();
+        invalidate();
+    }
+
+    public void setFrameColor(int frameColor) {
+        mFrameColor = frameColor;
+        invalidate();
+    }
+
+    public void setActiveFrameColor(int activeFrameColor) {
+        mActiveFrameColor = activeFrameColor;
+        invalidate();
+    }
+
+    public void setFrameWidth(float frameWidth) {
+        mFrameWidth = frameWidth;
+        invalidate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        configureBounds();
+    }
+
+    public void configureBounds() {
+        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
+        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
+
+        int dwidth;
+        int dheight;
+        if (mBitmap != null) {
+            dwidth = mBitmap.getWidth();
+            dheight = mBitmap.getHeight();
+        } else if (mDrawable != null) {
+            dwidth = mDrawable.getIntrinsicWidth();
+            dheight = mDrawable.getIntrinsicHeight();
+            mDrawable.setBounds(0, 0, dwidth, dheight);
+            vwidth -= 2 * (mFrameWidth - 1);
+            vheight -= 2 * (mFrameWidth - 1);
+        } else {
+            return;
+        }
+
+        float scale;
+        float dx;
+        float dy;
+
+        scale = Math.min((float) vwidth / (float) dwidth,
+                (float) vheight / (float) dheight);
+
+        dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
+        dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
+
+        mDrawMatrix.setScale(scale, scale);
+        mDrawMatrix.postTranslate(dx, dy);
+        mScale = scale;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int frameColor = isActivated() ? mActiveFrameColor : mFrameColor;
+        float halfW = getWidth() / 2f;
+        float halfH = getHeight() / 2f;
+        float halfSW = Math.min(halfH, halfW);
+        if (mBitmap != null && mScale > 0) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            canvas.concat(mDrawMatrix);
+            float halfBW = mBitmap.getWidth() / 2f;
+            float halfBH = mBitmap.getHeight() / 2f;
+            float halfBSW = Math.min(halfBH, halfBW);
+            canvas.drawCircle(halfBW, halfBH, halfBSW - mFrameWidth / mScale + 1, mBitmapPaint);
+            canvas.restoreToCount(saveCount);
+        } else if (mDrawable != null && mScale > 0) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            canvas.translate(mFrameWidth - 1, mFrameWidth - 1);
+            canvas.concat(mDrawMatrix);
+            mDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+        if (frameColor != 0) {
+            mFramePaint.setColor(frameColor);
+            mFramePaint.setStrokeWidth(mFrameWidth);
+            canvas.drawCircle(halfW, halfH, halfSW - mFrameWidth / 2f, mFramePaint);
+        }
+    }
+
+    public void setDrawable(Drawable d) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+            unscheduleDrawable(mDrawable);
+        }
+        mDrawable = d;
+        if (d != null) {
+            d.setCallback(this);
+            if (d.isStateful()) {
+                d.setState(getDrawableState());
+            }
+            d.setLayoutDirection(getLayoutDirection());
+            configureBounds();
+        }
+        if (d != null) {
+            mBitmap = null;
+        }
+        configureBounds();
+        invalidate();
+    }
+
+    @Override
+    public void invalidateDrawable(Drawable dr) {
+        if (dr == mDrawable) {
+            invalidate();
+        } else {
+            super.invalidateDrawable(dr);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 330b599..0134fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -63,6 +63,7 @@
                     // Just an old-fashioned ImageView
                     performLongClick();
                 }
+                setPressed(false);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
new file mode 100644
index 0000000..c90750c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.WindowManagerGlobal;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+public class KeyguardUserSwitcher implements View.OnClickListener {
+
+    private static final String TAG = "KeyguardUserSwitcher";
+
+    private final Context mContext;
+    private final ViewGroup mUserSwitcher;
+    private final UserManager mUserManager;
+    private final StatusBarHeaderView mHeader;
+
+    public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
+            StatusBarHeaderView header) {
+        mContext = context;
+        if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher)) {
+            mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+            mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+            mHeader = header;
+            refresh();
+        } else {
+            mUserSwitcher = null;
+            mUserManager = null;
+            mHeader = null;
+        }
+    }
+
+    public void setKeyguard(boolean keyguard) {
+        if (mUserSwitcher != null) {
+            // TODO: Cache showUserSwitcherOnKeyguard().
+            if (keyguard && showUserSwitcherOnKeyguard()) {
+                show();
+                refresh();
+            } else {
+                hide();
+            }
+        }
+    }
+
+    /**
+     * @return true if the user switcher should be shown on the lock screen.
+     * @see android.os.UserManager#isUserSwitcherEnabled()
+     */
+    private boolean showUserSwitcherOnKeyguard() {
+        // TODO: Set isEdu. The edu provisioning process can add settings to Settings.Global.
+        boolean isEdu = false;
+        if (isEdu) {
+            return true;
+        }
+        List<UserInfo> users = mUserManager.getUsers(true /* excludeDying */);
+        int N = users.size();
+        int switchableUsers = 0;
+        for (int i = 0; i < N; i++) {
+            if (users.get(i).supportsSwitchTo()) {
+                switchableUsers++;
+            }
+        }
+        return switchableUsers > 1;
+    }
+
+    public void show() {
+        if (mUserSwitcher != null) {
+            // TODO: animate
+            mUserSwitcher.setVisibility(View.VISIBLE);
+            mHeader.setKeyguardUserSwitcherShowing(true);
+        }
+    }
+
+    private void hide() {
+        if (mUserSwitcher != null) {
+            // TODO: animate
+            mUserSwitcher.setVisibility(View.GONE);
+            mHeader.setKeyguardUserSwitcherShowing(false);
+        }
+    }
+
+    private void refresh() {
+        if (mUserSwitcher != null) {
+            new AsyncTask<Void, Void, ArrayList<UserData>>() {
+                @Override
+                protected ArrayList<UserData> doInBackground(Void... params) {
+                    return loadUsers();
+                }
+
+                @Override
+                protected void onPostExecute(ArrayList<UserData> userInfos) {
+                    bind(userInfos);
+                }
+            }.execute((Void[]) null);
+        }
+    }
+
+    private void bind(ArrayList<UserData> userList) {
+        mUserSwitcher.removeAllViews();
+        int N = userList.size();
+        for (int i = 0; i < N; i++) {
+            mUserSwitcher.addView(inflateUser(userList.get(i)));
+        }
+        // TODO: add Guest
+        // TODO: add (+) button
+    }
+
+    private View inflateUser(UserData user) {
+        View v = LayoutInflater.from(mUserSwitcher.getContext()).inflate(
+                R.layout.keyguard_user_switcher_item, mUserSwitcher, false);
+        TextView name = (TextView) v.findViewById(R.id.name);
+        UserAvatarView picture = (UserAvatarView) v.findViewById(R.id.picture);
+        name.setText(user.userInfo.name);
+        picture.setActivated(user.isCurrent);
+        if (user.userInfo.isGuest()) {
+            picture.setDrawable(mContext.getResources().getDrawable(R.drawable.ic_account_circle));
+        } else {
+            picture.setBitmap(user.userIcon);
+        }
+        v.setOnClickListener(this);
+        v.setTag(user.userInfo);
+        // TODO: mark which user is current for accessibility.
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        switchUser(((UserInfo)v.getTag()).id);
+    }
+
+    // TODO: Factor out logic below and share with QS implementation.
+
+    private ArrayList<UserData> loadUsers() {
+        ArrayList<UserInfo> users = (ArrayList<UserInfo>) mUserManager
+                .getUsers(true /* excludeDying */);
+        int N = users.size();
+        ArrayList<UserData> result = new ArrayList<>(N);
+        int currentUser = -1;
+        try {
+            currentUser = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couln't get current user.", e);
+        }
+        for (int i = 0; i < N; i++) {
+            UserInfo user = users.get(i);
+            if (user.supportsSwitchTo()) {
+                boolean isCurrent = user.id == currentUser;
+                result.add(new UserData(user, mUserManager.getUserIcon(user.id), isCurrent));
+            }
+        }
+        return result;
+    }
+
+    private void switchUser(int userId) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null);
+            ActivityManagerNative.getDefault().switchUser(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't switch user.", e);
+        }
+    }
+
+    private static class UserData {
+        final UserInfo userInfo;
+        final Bitmap userIcon;
+        final boolean isCurrent;
+
+        UserData(UserInfo userInfo, Bitmap userIcon, boolean isCurrent) {
+            this.userInfo = userInfo;
+            this.userIcon = userIcon;
+            this.isCurrent = isCurrent;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 6d92b05..fcc951e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -32,6 +32,7 @@
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
     private int mSpeedBumpIndex = -1;
+    private float mScrimAmount;
 
     public int getScrollY() {
         return mScrollY;
@@ -85,6 +86,14 @@
         }
     }
 
+    public void setScrimAmount(float scrimAmount) {
+        mScrimAmount = scrimAmount;
+    }
+
+    public float getScrimAmount() {
+        return mScrimAmount;
+    }
+
     public float getOverScrollAmount(boolean top) {
         return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
     }
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 ccbaed3..f6e9aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -541,8 +541,9 @@
             if (slidingChild.getVisibility() == GONE) {
                 continue;
             }
-            float top = slidingChild.getTranslationY();
-            float bottom = top + slidingChild.getActualHeight();
+            float childTop = slidingChild.getTranslationY();
+            float top = childTop + slidingChild.getClipTopAmount();
+            float bottom = childTop + slidingChild.getActualHeight();
             int left = slidingChild.getLeft();
             int right = slidingChild.getRight();
 
@@ -1845,6 +1846,11 @@
         return true;
     }
 
+    public void setScrimAlpha(float progress) {
+        mAmbientState.setScrimAmount(progress);
+        requestChildrenUpdate();
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 602c22b..9a4b798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -154,6 +154,17 @@
         handleDraggedViews(ambientState, resultState, algorithmState);
         updateDimmedActivated(ambientState, resultState, algorithmState);
         updateClipping(resultState, algorithmState);
+        updateScrimAmount(resultState, algorithmState, ambientState.getScrimAmount());
+    }
+
+    private void updateScrimAmount(StackScrollState resultState,
+            StackScrollAlgorithmState algorithmState, float scrimAmount) {
+        int childCount = algorithmState.visibleChildren.size();
+        for (int i = 0; i < childCount; i++) {
+            View child = algorithmState.visibleChildren.get(i);
+            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+            childViewState.scrimAmount = scrimAmount;
+        }
     }
 
     private void updateClipping(StackScrollState resultState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 1ad4acc..02f2cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -148,6 +148,9 @@
                 // apply dimming
                 child.setDimmed(state.dimmed, false /* animate */);
 
+                // apply scrimming
+                child.setScrimAmount(state.scrimAmount);
+
                 float oldClipTopAmount = child.getClipTopAmount();
                 if (oldClipTopAmount != state.clipTopAmount) {
                     child.setClipTopAmount(state.clipTopAmount);
@@ -223,6 +226,12 @@
         boolean dimmed;
 
         /**
+         * A value between 0 and 1 indicating how much the view should be scrimmed.
+         * 1 means that the notifications will be darkened as much as possible.
+         */
+        float scrimAmount;
+
+        /**
          * The amount which the view should be clipped from the top. This is calculated to
          * perceive consistent shadows.
          */
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 225398a..0006dad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -176,6 +176,9 @@
         // start dimmed animation
         child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
 
+        // apply scrimming
+        child.setScrimAmount(viewState.scrimAmount);
+
         if (wasAdded) {
             child.performAddAnimation(delay);
         }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index bc2671011..637061d04 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -624,6 +624,7 @@
         public void onWakeUp() {
             synchronized (mLock) {
                 if (shouldEnableWakeGestureLp()) {
+                    performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
                     mPowerManager.wakeUp(SystemClock.uptimeMillis());
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 816e022..69262c8 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1328,7 +1328,7 @@
                         + " app=" + app);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(r.appInfo.packageName, mAm.mProcessStats);
+                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                     realStartServiceLocked(r, app, execInFg);
                     return null;
                 } catch (RemoteException e) {
@@ -1883,7 +1883,8 @@
 
                     mPendingServices.remove(i);
                     i--;
-                    proc.addPackage(sr.appInfo.packageName, mAm.mProcessStats);
+                    proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
+                            mAm.mProcessStats);
                     realStartServiceLocked(sr, proc, sr.createdFromFg);
                     didSomething = true;
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 34c1ecd..57b56b4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -36,6 +36,9 @@
 import android.app.IAppTask;
 import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetManager;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
 import android.graphics.Rect;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
@@ -171,8 +174,12 @@
 import android.os.UpdateLock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
 import android.text.format.DateUtils;
 import android.text.format.Time;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
 import android.util.AtomicFile;
 import android.util.EventLog;
 import android.util.Log;
@@ -186,6 +193,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.widget.TextView;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -570,6 +578,12 @@
     long mLastFullPssTime = SystemClock.uptimeMillis();
 
     /**
+     * If set, the next time we collect PSS data we should do a full collection
+     * with data from native processes and the kernel.
+     */
+    boolean mFullPssPending = false;
+
+    /**
      * This is the process holding what we currently consider to be
      * the "home" activity.
      */
@@ -1158,6 +1172,8 @@
     CompatModeDialog mCompatModeDialog;
     long mLastMemUsageReportTime = 0;
 
+    private LockToAppRequestDialog mLockToAppRequest;
+
     /**
      * Flag whether the current user is a "monkey", i.e. whether
      * the UI is driven by a UI automation tool.
@@ -1801,8 +1817,49 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
             case COLLECT_PSS_BG_MSG: {
-                int i=0, num=0;
                 long start = SystemClock.uptimeMillis();
+                MemInfoReader memInfo = null;
+                synchronized (ActivityManagerService.this) {
+                    if (mFullPssPending) {
+                        mFullPssPending = false;
+                        memInfo = new MemInfoReader();
+                    }
+                }
+                if (memInfo != null) {
+                    updateCpuStatsNow();
+                    long nativeTotalPss = 0;
+                    synchronized (mProcessCpuThread) {
+                        final int N = mProcessCpuTracker.countStats();
+                        for (int j=0; j<N; j++) {
+                            ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(j);
+                            if (st.vsize <= 0 || st.uid >= Process.FIRST_APPLICATION_UID
+                                    || st.uid == Process.SYSTEM_UID) {
+                                // This is definitely an application process; skip it.
+                                continue;
+                            }
+                            synchronized (mPidsSelfLocked) {
+                                if (mPidsSelfLocked.indexOfKey(st.pid) >= 0) {
+                                    // This is one of our own processes; skip it.
+                                    continue;
+                                }
+                            }
+                            nativeTotalPss += Debug.getPss(st.pid, null);
+                        }
+                    }
+                    memInfo.readMemInfo();
+                    synchronized (this) {
+                        if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in "
+                                + (SystemClock.uptimeMillis()-start) + "ms");
+                        mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+                                memInfo.getFreeSizeKb(),
+                                memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+                                memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()
+                                        +memInfo.getSlabSizeKb(),
+                                nativeTotalPss);
+                    }
+                }
+
+                int i=0, num=0;
                 long[] tmp = new long[1];
                 do {
                     ProcessRecord proc;
@@ -2176,6 +2233,8 @@
             }
         };
 
+        mLockToAppRequest = new LockToAppRequestDialog(mContext, this);
+
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
     }
@@ -2769,7 +2828,7 @@
                 // come up (we have a pid but not yet its thread), so keep it.
                 if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app);
                 // If this is a new package in the process, add the package to the list
-                app.addPackage(info.packageName, mProcessStats);
+                app.addPackage(info.packageName, info.versionCode, mProcessStats);
                 return app;
             }
 
@@ -2824,7 +2883,7 @@
             }
         } else {
             // If this is a new package in the process, add the package to the list
-            app.addPackage(info.packageName, mProcessStats);
+            app.addPackage(info.packageName, info.versionCode, mProcessStats);
         }
 
         // If the system is not ready yet, then hold off on starting this
@@ -3662,6 +3721,12 @@
             // Keep track of the root activity of the task before we finish it
             TaskRecord tr = r.task;
             ActivityRecord rootR = tr.getRootActivity();
+            // Do not allow task to finish in Lock Task mode.
+            if (tr == mStackSupervisor.mLockTaskModeTask) {
+                if (rootR == r) {
+                    return false;
+                }
+            }
             if (mController != null) {
                 // Find the first activity that is not finishing.
                 ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
@@ -3806,13 +3871,25 @@
     public boolean finishActivityAffinity(IBinder token) {
         synchronized(this) {
             final long origId = Binder.clearCallingIdentity();
-            ActivityRecord r = ActivityRecord.isInStackLocked(token);
-            boolean res = false;
-            if (r != null) {
-                res = r.task.stack.finishActivityAffinityLocked(r);
+            try {
+                ActivityRecord r = ActivityRecord.isInStackLocked(token);
+
+                ActivityRecord rootR = r.task.getRootActivity();
+                // Do not allow task to finish in Lock Task mode.
+                if (r.task == mStackSupervisor.mLockTaskModeTask) {
+                    if (rootR == r) {
+                        Binder.restoreCallingIdentity(origId);
+                        return false;
+                    }
+                }
+                boolean res = false;
+                if (r != null) {
+                    res = r.task.stack.finishActivityAffinityLocked(r);
+                }
+                return res;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            Binder.restoreCallingIdentity(origId);
-            return res;
         }
     }
 
@@ -6044,7 +6121,11 @@
         if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
             return false;
         }
+        return checkHoldingPermissionsInternalLocked(pm, pi, grantUri, uid, modeFlags, true);
+    }
 
+    private final boolean checkHoldingPermissionsInternalLocked(IPackageManager pm, ProviderInfo pi,
+            GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
         if (pi.applicationInfo.uid == uid) {
             return true;
         } else if (!pi.exported) {
@@ -6055,11 +6136,11 @@
         boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
         try {
             // check if target holds top-level <provider> permissions
-            if (!readMet && pi.readPermission != null
+            if (!readMet && pi.readPermission != null && considerUidPermissions
                     && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
                 readMet = true;
             }
-            if (!writeMet && pi.writePermission != null
+            if (!writeMet && pi.writePermission != null && considerUidPermissions
                     && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
                 writeMet = true;
             }
@@ -6085,7 +6166,8 @@
                                     + ": match=" + pp.match(path)
                                     + " check=" + pm.checkUidPermission(pprperm, uid));
                             if (pprperm != null) {
-                                if (pm.checkUidPermission(pprperm, uid) == PERMISSION_GRANTED) {
+                                if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
+                                        == PERMISSION_GRANTED) {
                                     readMet = true;
                                 } else {
                                     allowDefaultRead = false;
@@ -6099,7 +6181,8 @@
                                     + ": match=" + pp.match(path)
                                     + " check=" + pm.checkUidPermission(ppwperm, uid));
                             if (ppwperm != null) {
-                                if (pm.checkUidPermission(ppwperm, uid) == PERMISSION_GRANTED) {
+                                if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
+                                        == PERMISSION_GRANTED) {
                                     writeMet = true;
                                 } else {
                                     allowDefaultWrite = false;
@@ -6296,28 +6379,40 @@
             }
         }
 
+        /* There is a special cross user grant if:
+         * - The target is on another user.
+         * - Apps on the current user can access the uri without any uid permissions.
+         * In this case, we grant a uri permission, even if the ContentProvider does not normally
+         * grant uri permissions.
+         */
+        boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+                && checkHoldingPermissionsInternalLocked(pm, pi, grantUri, callingUid,
+                modeFlags, false /*without considering the uid permissions*/);
+
         // Second...  is the provider allowing granting of URI permissions?
-        if (!pi.grantUriPermissions) {
-            throw new SecurityException("Provider " + pi.packageName
-                    + "/" + pi.name
-                    + " does not allow granting of Uri permissions (uri "
-                    + grantUri + ")");
-        }
-        if (pi.uriPermissionPatterns != null) {
-            final int N = pi.uriPermissionPatterns.length;
-            boolean allowed = false;
-            for (int i=0; i<N; i++) {
-                if (pi.uriPermissionPatterns[i] != null
-                        && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
-                    allowed = true;
-                    break;
-                }
-            }
-            if (!allowed) {
+        if (!specialCrossUserGrant) {
+            if (!pi.grantUriPermissions) {
                 throw new SecurityException("Provider " + pi.packageName
                         + "/" + pi.name
-                        + " does not allow granting of permission to path of Uri "
-                        + grantUri);
+                        + " does not allow granting of Uri permissions (uri "
+                        + grantUri + ")");
+            }
+            if (pi.uriPermissionPatterns != null) {
+                final int N = pi.uriPermissionPatterns.length;
+                boolean allowed = false;
+                for (int i=0; i<N; i++) {
+                    if (pi.uriPermissionPatterns[i] != null
+                            && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
+                        allowed = true;
+                        break;
+                    }
+                }
+                if (!allowed) {
+                    throw new SecurityException("Provider " + pi.packageName
+                            + "/" + pi.name
+                            + " does not allow granting of permission to path of Uri "
+                            + grantUri);
+                }
             }
         }
 
@@ -7642,12 +7737,20 @@
         }
     }
 
-    private void startLockTaskMode(TaskRecord task) {
+    void startLockTaskMode(TaskRecord task) {
         final String pkg;
         synchronized (this) {
             pkg = task.intent.getComponent().getPackageName();
         }
-        if (!isLockTaskAuthorized(pkg)) {
+        boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID;
+        if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) {
+            final TaskRecord taskRecord = task;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mLockToAppRequest.showLockTaskPrompt(taskRecord);
+                }
+            });
             return;
         }
         long ident = Binder.clearCallingIdentity();
@@ -7659,7 +7762,7 @@
                     if ((mFocusedActivity == null) || (task != mFocusedActivity.task)) {
                         throw new IllegalArgumentException("Invalid task, not in foreground");
                     }
-                    mStackSupervisor.setLockTaskModeLocked(task);
+                    mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated);
                 }
             }
         } finally {
@@ -7704,24 +7807,55 @@
     }
 
     @Override
+    public void startLockTaskModeOnCurrent() throws RemoteException {
+        checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
+        ActivityRecord r = null;
+        synchronized (this) {
+            r = mStackSupervisor.topRunningActivityLocked();
+        }
+        startLockTaskMode(r.task);
+    }
+
+    @Override
     public void stopLockTaskMode() {
         // Verify that the user matches the package of the intent for the TaskRecord
-        // we are locked to.  This will ensure the same caller for startLockTaskMode and
-        // stopLockTaskMode.
-        try {
-            String pkg = mStackSupervisor.mLockTaskModeTask.intent.getPackage();
-            int uid = mContext.getPackageManager().getPackageUid(pkg,
-                    Binder.getCallingUserHandle().getIdentifier());
-            if (uid != Binder.getCallingUid()) {
-                throw new SecurityException("Invalid uid, expected " + uid);
+        // we are locked to or systtem.  This will ensure the same caller for startLockTaskMode
+        // and stopLockTaskMode.
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID) {
+            try {
+                String pkg =
+                        mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName();
+                int uid = mContext.getPackageManager().getPackageUid(pkg,
+                        Binder.getCallingUserHandle().getIdentifier());
+                if (uid != callingUid) {
+                    throw new SecurityException("Invalid uid, expected " + uid);
+                }
+            } catch (NameNotFoundException e) {
+                Log.d(TAG, "stopLockTaskMode " + e);
+                return;
             }
-        } catch (NameNotFoundException e) {
-            Log.d(TAG, "stopLockTaskMode " + e);
-            return;
         }
-        // Stop lock task
-        synchronized (this) {
-            mStackSupervisor.setLockTaskModeLocked(null);
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Log.d(TAG, "stopLockTaskMode");
+            // Stop lock task
+            synchronized (this) {
+                mStackSupervisor.setLockTaskModeLocked(null, false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void stopLockTaskModeOnCurrent() throws RemoteException {
+        checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
+        long ident = Binder.clearCallingIdentity();
+        try {
+            stopLockTaskMode();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -7780,7 +7914,8 @@
                     // to run in multiple processes, because this is actually
                     // part of the framework so doesn't make sense to track as a
                     // separate apk in the process.
-                    app.addPackage(cpi.applicationInfo.packageName, mProcessStats);
+                    app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
+                            mProcessStats);
                 }
                 ensurePackageDexOpt(cpi.applicationInfo.packageName);
             }
@@ -12588,6 +12723,8 @@
             }
         }
 
+        long nativeProcTotalPss = 0;
+
         if (!isCheckinRequest && procs.size() > 1) {
             // If we are showing aggregations, also look for native processes to
             // include so that our aggregations are more accurate.
@@ -12609,6 +12746,7 @@
 
                         final long myTotalPss = mi.getTotalPss();
                         totalPss += myTotalPss;
+                        nativeProcTotalPss += myTotalPss;
 
                         MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                                 st.name, myTotalPss, st.pid, false);
@@ -12676,6 +12814,15 @@
             }
             MemInfoReader memInfo = new MemInfoReader();
             memInfo.readMemInfo();
+            if (nativeProcTotalPss > 0) {
+                synchronized (this) {
+                    mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+                            memInfo.getFreeSizeKb(),
+                            memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+                            memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()+memInfo.getSlabSizeKb(),
+                            nativeProcTotalPss);
+                }
+            }
             if (!brief) {
                 if (!isCompact) {
                     pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
@@ -15456,6 +15603,7 @@
         }
         if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs!  memLowered=" + memLowered);
         mLastFullPssTime = now;
+        mFullPssPending = true;
         mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
         mPendingPssProcesses.clear();
         for (int i=mLruProcesses.size()-1; i>=0; i--) {
@@ -16651,7 +16799,7 @@
                     return true;
                 }
 
-                mStackSupervisor.setLockTaskModeLocked(null);
+                mStackSupervisor.setLockTaskModeLocked(null, false);
 
                 final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
                 if (userInfo == null) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index dd9cae9..0825f2e 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -582,11 +582,6 @@
         }
     }
 
-    boolean isRootActivity() {
-        final ArrayList<ActivityRecord> activities = task.mActivities;
-        return activities.size() == 0 || this == activities.get(0);
-    }
-
     UriPermissionOwner getUriPermissionsLocked() {
         if (uriPermissions == null) {
             uriPermissions = new UriPermissionOwner(service, this);
@@ -1035,11 +1030,11 @@
             return -1;
         }
         final TaskRecord task = r.task;
-        switch (task.mActivities.indexOf(r)) {
-            case -1: return -1;
-            case 0: return task.taskId;
-            default: return onlyRoot ? -1 : task.taskId;
+        final int activityNdx = task.mActivities.indexOf(r);
+        if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
+            return -1;
         }
+        return task.taskId;
     }
 
     static ActivityRecord isInStackLocked(IBinder token) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 03ce530..fe2a473 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1066,40 +1066,6 @@
         }
     }
 
-    /**
-     * Determine if home should be visible below the passed record.
-     * @param record activity we are querying for.
-     * @return true if home is visible below the passed activity, false otherwise.
-     */
-    boolean isActivityOverHome(ActivityRecord record) {
-        // Start at record and go down, look for either home or a visible fullscreen activity.
-        final TaskRecord recordTask = record.task;
-        for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) {
-            TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            final int startNdx =
-                    task == recordTask ? activities.indexOf(record) : activities.size() - 1;
-            for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
-                if (r.isHomeActivity()) {
-                    return true;
-                }
-                if (!r.finishing && r.fullscreen) {
-                    // Passed activity is over a fullscreen activity.
-                    return false;
-                }
-            }
-            if (task.mOnTopOfHome) {
-                // Got to the bottom of a task on top of home without finding a visible fullscreen
-                // activity. Home is visible.
-                return true;
-            }
-        }
-        // Got to the bottom of this stack and still don't know. If this is over the home stack
-        // then record is over home. May not work if we ever get more than two layers.
-        return mStackSupervisor.isFrontStack(this);
-    }
-
     private void setVisibile(ActivityRecord r, boolean visible) {
         r.visible = visible;
         mWindowManager.setAppVisibility(r.appToken, visible);
@@ -1954,8 +1920,7 @@
                 // existing activities from other tasks in to it.
                 // If the caller has requested that the target task be
                 // reset, then do so.
-                if ((r.intent.getFlags()
-                        & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+                if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                     resetTaskIfNeededLocked(r, r);
                     doShow = topRunningNonDelayedActivityLocked(null) == r;
                 }
@@ -2048,7 +2013,8 @@
         // the root, we may no longer have the task!).
         final ArrayList<ActivityRecord> activities = task.mActivities;
         final int numActivities = activities.size();
-        for (int i = numActivities - 1; i > 0; --i ) {
+        final int rootActivityNdx = task.findEffectiveRootIndex();
+        for (int i = numActivities - 1; i > rootActivityNdx; --i ) {
             ActivityRecord target = activities.get(i);
 
             final int flags = target.info.flags;
@@ -2207,8 +2173,10 @@
 
         final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
         final int numActivities = activities.size();
-        // Do not operate on the root Activity.
-        for (int i = numActivities - 1; i > 0; --i) {
+        final int rootActivityNdx = affinityTask.findEffectiveRootIndex();
+
+        // Do not operate on or below the effective root Activity.
+        for (int i = numActivities - 1; i > rootActivityNdx; --i) {
             ActivityRecord target = activities.get(i);
 
             final int flags = target.info.flags;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9264186..4be208b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -79,6 +79,8 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.EventLog;
 import android.util.Slog;
@@ -142,6 +144,8 @@
 
     private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
 
+    private static final String LOCK_TASK_TAG = "Lock-to-App";
+
     /** Status Bar Service **/
     private IBinder mToken = new Binder();
     private IStatusBarService mStatusBarService;
@@ -255,6 +259,10 @@
     /** If non-null then the task specified remains in front and no other tasks may be started
      * until the task exits or #stopLockTaskMode() is called. */
     TaskRecord mLockTaskModeTask;
+    /**
+     * Notifies the user when entering/exiting lock-task.
+     */
+    private LockTaskNotify mLockTaskNotify;
 
     public ActivityStackSupervisor(ActivityManagerService service) {
         mService = service;
@@ -1178,7 +1186,8 @@
                     // to run in multiple processes, because this is actually
                     // part of the framework so doesn't make sense to track as a
                     // separate apk in the process.
-                    app.addPackage(r.info.packageName, mService.mProcessStats);
+                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
+                            mService.mProcessStats);
                 }
                 realStartActivityLocked(r, app, andResume, checkConfig);
                 return;
@@ -3004,7 +3013,7 @@
         return list;
     }
 
-    void setLockTaskModeLocked(TaskRecord task) {
+    void setLockTaskModeLocked(TaskRecord task, boolean showHomeRecents) {
         if (task == null) {
             // Take out of lock task mode if necessary
             if (mLockTaskModeTask != null) {
@@ -3028,6 +3037,7 @@
         lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName();
         lockTaskMsg.arg1 = mLockTaskModeTask.userId;
         lockTaskMsg.what = LOCK_TASK_START_MSG;
+        lockTaskMsg.arg2 = showHomeRecents ? 1 : 0;
         mHandler.sendMessage(lockTaskMsg);
     }
 
@@ -3130,15 +3140,24 @@
                 case LOCK_TASK_START_MSG: {
                     // When lock task starts, we disable the status bars.
                     try {
-                        if (getStatusBarService() != null) {
-                            getStatusBarService().disable
-                                (StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK,
-                                mToken, mService.mContext.getPackageName());
+                        if (mLockTaskNotify == null) {
+                            mLockTaskNotify = new LockTaskNotify(mService.mContext);
                         }
+                        mLockTaskNotify.show(true);
+                        if (getStatusBarService() != null) {
+                            int flags =
+                                    StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK;
+                            if (msg.arg2 != 0) {
+                                flags ^= StatusBarManager.DISABLE_HOME
+                                        | StatusBarManager.DISABLE_RECENT;
+                            }
+                            getStatusBarService().disable(flags, mToken,
+                                    mService.mContext.getPackageName());
+                        }
+                        mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
                         if (getDevicePolicyManager() != null) {
                             getDevicePolicyManager().notifyLockTaskModeChanged(true,
-                                    (String)msg.obj,
-                                    msg.arg1);
+                                    (String)msg.obj, msg.arg1);
                         }
                     } catch (RemoteException ex) {
                         throw new RuntimeException(ex);
@@ -3147,15 +3166,29 @@
                 case LOCK_TASK_END_MSG: {
                     // When lock task ends, we enable the status bars.
                     try {
-                       if (getStatusBarService() != null) {
-                           getStatusBarService().disable
-                               (StatusBarManager.DISABLE_NONE,
-                               mToken, mService.mContext.getPackageName());
-                       }
+                        if (getStatusBarService() != null) {
+                            getStatusBarService().disable(StatusBarManager.DISABLE_NONE, mToken,
+                                    mService.mContext.getPackageName());
+                        }
+                        mWindowManager.reenableKeyguard(mToken);
                         if (getDevicePolicyManager() != null) {
                             getDevicePolicyManager().notifyLockTaskModeChanged(false, null,
                                     msg.arg1);
                         }
+                        if (mLockTaskNotify == null) {
+                            mLockTaskNotify = new LockTaskNotify(mService.mContext);
+                        }
+                        mLockTaskNotify.show(false);
+                        try {
+                            boolean shouldLockKeyguard = Settings.System.getInt(
+                                    mService.mContext.getContentResolver(),
+                                    Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0;
+                            if (shouldLockKeyguard) {
+                                mWindowManager.lockNow(null);
+                            }
+                        } catch (SettingNotFoundException e) {
+                            // No setting, don't lock.
+                        }
                     } catch (RemoteException ex) {
                         throw new RuntimeException(ex);
                     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7b2b04d..cdcc74b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -887,7 +887,8 @@
                     info.activityInfo.applicationInfo.uid, false);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(info.activityInfo.packageName, mService.mProcessStats);
+                    app.addPackage(info.activityInfo.packageName,
+                            info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
                     processCurBroadcastLocked(r, app);
                     return;
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
new file mode 100644
index 0000000..1997f46
--- /dev/null
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 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.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+/**
+ *  Helper to manage showing/hiding a image to notify them that they are entering
+ *  or exiting lock-to-app mode.
+ */
+public class LockTaskNotify {
+    private static final String TAG = "LockTaskNotify";
+
+    private static final int SHOW_LENGTH_MS = 1500;
+
+    private final Context mContext;
+    private final H mHandler;
+
+    private ClingWindowView mClingWindow;
+    private WindowManager mWindowManager;
+    private boolean mIsStarting;
+
+    public LockTaskNotify(Context context) {
+        mContext = context;
+        mHandler = new H();
+        mWindowManager = (WindowManager)
+                mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    public void show(boolean starting) {
+        mIsStarting = starting;
+        mHandler.obtainMessage(H.SHOW).sendToTarget();
+    }
+
+    public WindowManager.LayoutParams getClingWindowLayoutParams() {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_TOAST,
+                0
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                ,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("LockTaskNotify");
+        lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
+        lp.gravity = Gravity.FILL;
+        return lp;
+    }
+
+    public FrameLayout.LayoutParams getImageLayoutParams() {
+        return new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+    }
+
+    private void handleShow() {
+        mClingWindow = new ClingWindowView(mContext);
+
+        // we will be hiding the nav bar, so layout as if it's already hidden
+        mClingWindow.setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+              | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+        // show the confirmation
+        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        mWindowManager.addView(mClingWindow, lp);
+    }
+
+    private void handleHide() {
+        if (mClingWindow != null) {
+            mWindowManager.removeView(mClingWindow);
+            mClingWindow = null;
+        }
+    }
+
+
+    private class ClingWindowView extends FrameLayout {
+        private View mView;
+
+        private Runnable mUpdateLayoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mView != null && mView.getParent() != null) {
+                    mView.setLayoutParams(getImageLayoutParams());
+                }
+            }
+        };
+
+        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                    post(mUpdateLayoutRunnable);
+                }
+            }
+        };
+
+        public ClingWindowView(Context context) {
+            super(context);
+            setClickable(true);
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            DisplayMetrics metrics = new DisplayMetrics();
+            mWindowManager.getDefaultDisplay().getMetrics(metrics);
+
+            int id = R.layout.lock_to_app_exit;
+            if (mIsStarting) {
+                id = R.layout.lock_to_app_enter;
+            }
+            mView = View.inflate(getContext(), id, null);
+
+            addView(mView, getImageLayoutParams());
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(H.HIDE), SHOW_LENGTH_MS);
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+            mContext.unregisterReceiver(mReceiver);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent motion) {
+            Slog.v(TAG, "ClingWindowView.onTouchEvent");
+            return true;
+        }
+    }
+
+    private final class H extends Handler {
+        private static final int SHOW = 1;
+        private static final int HIDE = 2;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case SHOW:
+                    handleShow();
+                    break;
+                case HIDE:
+                    handleHide();
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
new file mode 100644
index 0000000..6e86dff
--- /dev/null
+++ b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
@@ -0,0 +1,87 @@
+
+package com.android.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
+import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class LockToAppRequestDialog implements OnClickListener {
+    private static final String TAG = "ActivityManager";
+
+    final private Context mContext;
+    final private ActivityManagerService mService;
+
+    private AlertDialog mDialog;
+    private TaskRecord mRequestedTask;
+
+    public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) {
+        mContext = context;
+        mService = activityManagerService;
+    }
+
+    public void showLockTaskPrompt(TaskRecord task) {
+        if (mDialog != null) {
+            mDialog.dismiss();
+            mDialog = null;
+        }
+        mRequestedTask = task;
+
+        final Resources r = Resources.getSystem();
+        final String descriptionString = r.getString(R.string.lock_to_app_description);
+        final SpannableString description =
+                new SpannableString(descriptionString.replace('$', ' '));
+        final ImageSpan imageSpan = new ImageSpan(mContext,
+                BitmapFactory.decodeResource(r, R.drawable.ic_recent),
+                DynamicDrawableSpan.ALIGN_BOTTOM);
+        final int index = descriptionString.indexOf('$');
+        if (index >= 0) {
+            description.setSpan(imageSpan, index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+        mDialog =
+                new AlertDialog.Builder(mContext)
+                        .setTitle(r.getString(R.string.lock_to_app_title))
+                        .setMessage(description)
+                        .setPositiveButton(r.getString(R.string.lock_to_app_positive), this)
+                        .setNegativeButton(r.getString(R.string.lock_to_app_negative), this)
+                        .create();
+
+        mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        mDialog.show();
+
+        // Make icon fit.
+        final TextView msgTxt = (TextView) mDialog.findViewById(R.id.message);
+        final float width = imageSpan.getDrawable().getIntrinsicWidth();
+        final float height = imageSpan.getDrawable().getIntrinsicHeight();
+        final int lineHeight = msgTxt.getLineHeight();
+        imageSpan.getDrawable().setBounds(0, 0, (int) (lineHeight * width / height), lineHeight);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (DialogInterface.BUTTON_POSITIVE == which) {
+            Slog.d(TAG, "accept lock-to-app request");
+            // Automatically enable if not currently on. (Could be triggered by an app)
+            Settings.System.putInt(mContext.getContentResolver(),
+                    Settings.System.LOCK_TO_APP_ENABLED, 1);
+
+            // Start lock-to-app.
+            mService.startLockTaskMode(mRequestedTask);
+        } else {
+            Slog.d(TAG, "ignore lock-to-app request");
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 8d7d300..a5b80bc 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -53,8 +53,8 @@
     final int userId;           // user of process.
     final String processName;   // name of the process
     // List of packages running in the process
-    final ArrayMap<String, ProcessStats.ProcessState> pkgList
-            = new ArrayMap<String, ProcessStats.ProcessState>();
+    final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList
+            = new ArrayMap<String, ProcessStats.ProcessStateHolder>();
     IApplicationThread thread;  // the actual proc...  may be null only if
                                 // 'persistent' is true (in which case we
                                 // are in the process of launching the app)
@@ -371,7 +371,7 @@
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        pkgList.put(_info.packageName, null);
+        pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.versionCode));
         maxAdj = ProcessList.UNKNOWN_ADJ;
         curRawAdj = setRawAdj = -100;
         curAdj = setAdj = -100;
@@ -398,16 +398,15 @@
                     info.versionCode, processName);
             baseProcessTracker.makeActive();
             for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                if (ps != null && ps != origBase) {
-                    ps.makeInactive();
+                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                if (holder.state != null && holder.state != origBase) {
+                    holder.state.makeInactive();
                 }
-                ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+                holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
                         info.versionCode, processName);
-                if (ps != baseProcessTracker) {
-                    ps.makeActive();
+                if (holder.state != baseProcessTracker) {
+                    holder.state.makeActive();
                 }
-                pkgList.setValueAt(i, ps);
             }
         }
         thread = _thread;
@@ -424,11 +423,11 @@
             }
             baseProcessTracker = null;
             for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                if (ps != null && ps != origBase) {
-                    ps.makeInactive();
+                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                if (holder.state != null && holder.state != origBase) {
+                    holder.state.makeInactive();
                 }
-                pkgList.setValueAt(i, null);
+                holder.state = null;
             }
         }
     }
@@ -572,14 +571,16 @@
     /*
      *  Return true if package has been added false if not
      */
-    public boolean addPackage(String pkg, ProcessStatsService tracker) {
+    public boolean addPackage(String pkg, int versionCode, ProcessStatsService tracker) {
         if (!pkgList.containsKey(pkg)) {
             if (baseProcessTracker != null) {
-                ProcessStats.ProcessState state = tracker.getProcessStateLocked(
-                        pkg, info.uid, info.versionCode, processName);
-                pkgList.put(pkg, state);
-                if (state != baseProcessTracker) {
-                    state.makeActive();
+                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                        versionCode);
+                holder.state = tracker.getProcessStateLocked(
+                        pkg, info.uid, versionCode, processName);
+                pkgList.put(pkg, holder);
+                if (holder.state != baseProcessTracker) {
+                    holder.state.makeActive();
                 }
             } else {
                 pkgList.put(pkg, null);
@@ -615,23 +616,26 @@
                     tracker.getMemFactorLocked(), now, pkgList);
             if (N != 1) {
                 for (int i=0; i<N; i++) {
-                    ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                    if (ps != null && ps != baseProcessTracker) {
-                        ps.makeInactive();
+                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                    if (holder.state != null && holder.state != baseProcessTracker) {
+                        holder.state.makeInactive();
                     }
 
                 }
                 pkgList.clear();
                 ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
                         info.packageName, info.uid, info.versionCode, processName);
-                pkgList.put(info.packageName, ps);
+                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                        info.versionCode);
+                holder.state = ps;
+                pkgList.put(info.packageName, holder);
                 if (ps != baseProcessTracker) {
                     ps.makeActive();
                 }
             }
         } else if (N != 1) {
             pkgList.clear();
-            pkgList.put(info.packageName, null);
+            pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.versionCode));
         }
     }
     
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 14f3ef9..7ec14c29 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -171,10 +171,17 @@
         return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
     }
 
+    public void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
+            long nativeMem) {
+        mProcessStats.addSysMemUsage(cachedMem, freeMem, zramMem, kernelMem, nativeMem);
+    }
+
     public boolean shouldWriteNowLocked(long now) {
         if (now > (mLastWriteTime+WRITE_PERIOD)) {
             if (SystemClock.elapsedRealtime()
-                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD)) {
+                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD) &&
+                    SystemClock.uptimeMillis()
+                    > (mProcessStats.mTimePeriodStartUptime+ProcessStats.COMMIT_UPTIME_PERIOD)) {
                 mCommitPending = true;
             }
             return true;
@@ -212,6 +219,7 @@
             if (mPendingWrite == null || !mPendingWriteCommitted) {
                 mPendingWrite = Parcel.obtain();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                mProcessStats.mTimePeriodEndUptime = now;
                 if (commit) {
                     mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
                 }
@@ -439,8 +447,10 @@
         mWriteLock.lock();
         try {
             synchronized (mAm) {
+                long now = SystemClock.uptimeMillis();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
-                mProcessStats.writeToParcel(current, 0);
+                mProcessStats.mTimePeriodEndUptime = now;
+                mProcessStats.writeToParcel(current, now, 0);
             }
             if (historic != null) {
                 ArrayList<String> files = getCommittedFiles(0, false, true);
@@ -470,8 +480,10 @@
             Parcel current = Parcel.obtain();
             long curTime;
             synchronized (mAm) {
+                long now = SystemClock.uptimeMillis();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
-                mProcessStats.writeToParcel(current, 0);
+                mProcessStats.mTimePeriodEndUptime = now;
+                mProcessStats.writeToParcel(current, now, 0);
                 curTime = mProcessStats.mTimePeriodEndRealtime
                         - mProcessStats.mTimePeriodStartRealtime;
             }
@@ -568,8 +580,8 @@
     static private void dumpHelp(PrintWriter pw) {
         pw.println("Process stats (procstats) dump options:");
         pw.println("    [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]");
-        pw.println("    [--details] [--full-details] [--current] [--hours] [--active]");
-        pw.println("    [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
+        pw.println("    [--details] [--full-details] [--current] [--hours N] [--last N]");
+        pw.println("    [--active] [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
         pw.println("  --checkin: perform a checkin: print and delete old committed states.");
         pw.println("  --c: print only state in checkin format.");
         pw.println("  --csv: output data suitable for putting in a spreadsheet.");
@@ -581,6 +593,7 @@
         pw.println("  --full-details: dump all timing and active state details.");
         pw.println("  --current: only dump current state.");
         pw.println("  --hours: aggregate over about N last hours.");
+        pw.println("  --last: only show the last committed stats at index N (starting at 1).");
         pw.println("  --active: only show currently active processes/services.");
         pw.println("  --commit: commit current stats to disk and reset to start new stats.");
         pw.println("  --reset: reset current stats, without committing.");
@@ -621,6 +634,7 @@
         boolean dumpFullDetails = false;
         boolean dumpAll = false;
         int aggregateHours = 0;
+        int lastIndex = 0;
         boolean activeOnly = false;
         String reqPackage = null;
         boolean csvSepScreenStats = false;
@@ -705,6 +719,20 @@
                         dumpHelp(pw);
                         return;
                     }
+                } else if ("--last".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --last");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    try {
+                        lastIndex = Integer.parseInt(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Error: --last argument not an int -- " + args[i]);
+                        dumpHelp(pw);
+                        return;
+                    }
                 } else if ("--active".equals(arg)) {
                     activeOnly = true;
                     currentOnly = true;
@@ -818,6 +846,43 @@
             dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
                     dumpDetails, dumpFullDetails, dumpAll, activeOnly);
             return;
+        } else if (lastIndex > 0) {
+            pw.print("LAST STATS AT INDEX "); pw.print(lastIndex); pw.println(":");
+            ArrayList<String> files = getCommittedFiles(0, false, true);
+            if (lastIndex >= files.size()) {
+                pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
+                return;
+            }
+            AtomicFile file = new AtomicFile(new File(files.get(lastIndex)));
+            ProcessStats processStats = new ProcessStats(false);
+            readLocked(processStats, file);
+            if (processStats.mReadError != null) {
+                if (isCheckin || isCompact) pw.print("err,");
+                pw.print("Failure reading "); pw.print(files.get(lastIndex));
+                pw.print("; "); pw.println(processStats.mReadError);
+                return;
+            }
+            String fileStr = file.getBaseFile().getPath();
+            boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX);
+            if (isCheckin || isCompact) {
+                // Don't really need to lock because we uniquely own this object.
+                processStats.dumpCheckinLocked(pw, reqPackage);
+            } else {
+                pw.print("COMMITTED STATS FROM ");
+                pw.print(processStats.mTimePeriodStartClockStr);
+                if (checkedIn) pw.print(" (checked in)");
+                pw.println(":");
+                if (dumpDetails || dumpFullDetails) {
+                    processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
+                            activeOnly);
+                    if (dumpAll) {
+                        pw.print("  mFile="); pw.println(mFile.getBaseFile());
+                    }
+                } else {
+                    processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+                }
+            }
+            return;
         }
 
         boolean sepNeeded = false;
@@ -859,7 +924,7 @@
                                 // Always dump summary here, dumping all details is just too
                                 // much crud.
                                 if (dumpFullDetails) {
-                                    mProcessStats.dumpLocked(pw, reqPackage, now, false, false,
+                                    processStats.dumpLocked(pw, reqPackage, now, false, false,
                                             activeOnly);
                                 } else {
                                     processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1cde41f..986321d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Slog;
@@ -57,11 +56,12 @@
     private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
+    private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     final int taskId;       // Unique identifier for this task.
-    final String affinity;  // The affinity name for this task, or null.
+    String affinity;        // The affinity name for this task, or null.
     final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
     final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
     Intent intent;          // The original intent that started the task.
@@ -111,13 +111,15 @@
      * Display.DEFAULT_DISPLAY. */
     boolean mOnTopOfHome = false;
 
+    /** If original intent did not allow relinquishing task identity, save that information */
+    boolean mNeverRelinquishIdentity = true;
+
     final ActivityManagerService mService;
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
         taskId = _taskId;
-        affinity = info.taskAffinity;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
         setIntent(_intent, info);
@@ -128,7 +130,7 @@
             String _affinity, ComponentName _realActivity, ComponentName _origActivity,
             boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
             int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
-            long lastTimeMoved) {
+            long lastTimeMoved, boolean neverRelinquishIdentity) {
         mService = service;
         taskId = _taskId;
         intent = _intent;
@@ -146,6 +148,7 @@
         lastDescription = _lastDescription;
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
+        mNeverRelinquishIdentity = neverRelinquishIdentity;
     }
 
     void touchActiveTime() {
@@ -157,6 +160,14 @@
     }
 
     void setIntent(Intent _intent, ActivityInfo info) {
+        if (intent == null) {
+            mNeverRelinquishIdentity =
+                    (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+        } else if (mNeverRelinquishIdentity) {
+            return;
+        }
+
+        affinity = info.taskAffinity;
         stringName = null;
 
         if (info.targetActivity == null) {
@@ -282,6 +293,7 @@
 
         mActivities.remove(newTop);
         mActivities.add(newTop);
+        updateEffectiveIntent();
 
         setFrontOfTask();
     }
@@ -311,6 +323,7 @@
             r.mActivityType = taskType;
         }
         mActivities.add(index, r);
+        updateEffectiveIntent();
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
@@ -325,7 +338,11 @@
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
-        return mActivities.size() == 0;
+        if (mActivities.isEmpty()) {
+            return true;
+        }
+        updateEffectiveIntent();
+        return false;
     }
 
     boolean autoRemoveFromRecents() {
@@ -579,12 +596,19 @@
         // utility activities.
         int activityNdx;
         final int numActivities = mActivities.size();
+        final boolean relinquish = numActivities == 0 ? false :
+                (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
         for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
                 ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
+            if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                // This will be the top activity for determining taskDescription. Pre-inc to
+                // overcome initial decrement below.
+                ++activityNdx;
+                break;
+            }
             if (r.intent != null &&
-                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
-                            != 0) {
+                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                 break;
             }
         }
@@ -615,6 +639,27 @@
         }
     }
 
+    int findEffectiveRootIndex() {
+        int activityNdx;
+        final int topActivityNdx = mActivities.size() - 1;
+        for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (r.finishing) {
+                continue;
+            }
+            if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                break;
+            }
+        }
+        return activityNdx;
+    }
+
+    void updateEffectiveIntent() {
+        final int effectiveRootIndex = findEffectiveRootIndex();
+        final ActivityRecord r = mActivities.get(effectiveRootIndex);
+        setIntent(r.intent, r.info);
+    }
+
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
         Slog.i(TAG, "Saving task=" + this);
 
@@ -634,6 +679,7 @@
         out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
         out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
         out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
+        out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
         if (lastDescription != null) {
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
@@ -684,6 +730,7 @@
         int userId = 0;
         String lastDescription = null;
         long lastTimeOnTop = 0;
+        boolean neverRelinquishIdentity = true;
         int taskId = -1;
         final int outerDepth = in.getDepth();
 
@@ -714,6 +761,8 @@
                 lastDescription = attrValue;
             } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
                 lastTimeOnTop = Long.valueOf(attrValue);
+            } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
+                neverRelinquishIdentity = Boolean.valueOf(attrValue);
             } else {
                 Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
             }
@@ -748,7 +797,7 @@
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
-                lastTimeOnTop);
+                lastTimeOnTop, neverRelinquishIdentity);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 353f603..52c092c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -78,7 +78,7 @@
      */
     void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        HdmiCecDeviceInfo targetDevice = mService.getDeviceInfo(targetAddress);
+        HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
         if (targetDevice == null) {
             invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE);
             return;
@@ -142,6 +142,9 @@
     }
 
     private static void invokeCallback(IHdmiControlCallback callback, int result) {
+        if (callback == null) {
+            return;
+        }
         try {
             callback.onComplete(result);
         } catch (RemoteException e) {
@@ -455,8 +458,11 @@
     final void addCecDevice(HdmiCecDeviceInfo info) {
         assertRunOnServiceThread();
         addDeviceInfo(info);
-
-        // TODO: announce new device detection.
+        if (info.getLogicalAddress() == mAddress) {
+            // The addition of TV device itself should not be notified.
+            return;
+        }
+        mService.invokeDeviceEventListeners(info, true);
     }
 
     /**
@@ -466,10 +472,9 @@
      */
     final void removeCecDevice(int address) {
         assertRunOnServiceThread();
-        removeDeviceInfo(address);
+        HdmiCecDeviceInfo info = removeDeviceInfo(address);
         mCecMessageCache.flushMessagesFrom(address);
-
-        // TODO: announce a device removal.
+        mService.invokeDeviceEventListeners(info, false);
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index fddb833..d323f34 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -37,6 +37,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.SystemService;
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 
@@ -94,21 +95,25 @@
 
     // List of listeners registered by callers that want to get notified of
     // hotplug events.
+    @GuardedBy("mLock")
     private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
 
     // List of records for hotplug event listener to handle the the caller killed in action.
+    @GuardedBy("mLock")
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
             new ArrayList<>();
 
     // List of listeners registered by callers that want to get notified of
     // device status events.
+    @GuardedBy("mLock")
     private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
 
     // List of records for device event listener to handle the the caller killed in action.
+    @GuardedBy("mLock")
     private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
             new ArrayList<>();
 
-    // Handler running on service thread. It's used to run a task in service thread.
+    // Handler used to run a task in service thread.
     private final Handler mHandler = new Handler();
 
     @Nullable
@@ -153,6 +158,7 @@
         // A container for [Logical Address, Local device info].
         final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
         final SparseIntArray finished = new SparseIntArray();
+        mCecController.clearLogicalAddress();
         for (int type : deviceTypes) {
             final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
             localDevice.init();
@@ -171,8 +177,7 @@
                     }
                     finished.append(deviceType, logicalAddress);
 
-                    // Once finish address allocation for all devices, notify
-                    // it to each device.
+                    // Address allocation completed for all devices. Notify each device.
                     if (deviceTypes.size() == finished.size()) {
                         notifyAddressAllocated(devices);
                     }
@@ -185,7 +190,7 @@
         for (int i = 0; i < devices.size(); ++i) {
             int address = devices.keyAt(i);
             HdmiCecLocalDevice device = devices.valueAt(i);
-            device.onAddressAllocated(address);
+            device.handleAddressAllocated(address);
         }
     }
 
@@ -663,15 +668,28 @@
     }
 
     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
+        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
+        try {
+            listener.asBinder().linkToDeath(record, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Listener already died");
+            return;
+        }
         synchronized (mLock) {
-            for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
-                if (record.mListener.asBinder() == listener.asBinder()) {
-                    listener.asBinder().unlinkToDeath(record, 0);
-                    mDeviceEventListenerRecords.remove(record);
-                    break;
+            mDeviceEventListeners.add(listener);
+            mDeviceEventListenerRecords.add(record);
+        }
+    }
+
+    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
+        synchronized (mLock) {
+            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
+                try {
+                    listener.onStatusChanged(device, activated);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report device event:" + e);
                 }
             }
-            mHotplugEventListeners.remove(listener);
         }
     }
 
@@ -683,16 +701,16 @@
         }
     }
 
-    private void announceHotplugEvent(int portNo, boolean connected) {
-        HdmiHotplugEvent event = new HdmiHotplugEvent(portNo, connected);
+    private void announceHotplugEvent(int portId, boolean connected) {
+        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
         synchronized (mLock) {
             for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
-                invokeHotplugEventListener(listener, event);
+                invokeHotplugEventListenerLocked(listener, event);
             }
         }
     }
 
-    private void invokeHotplugEventListener(IHdmiHotplugEventListener listener,
+    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
             HdmiHotplugEvent event) {
         try {
             listener.onReceived(event);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index e48b0dc..92418ab 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -28,9 +28,6 @@
     // State in which waits for <SetSystemAudioMode>.
     private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
 
-    // State in which waits for <ReportAudioStatus>.
-    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 2;
-
     private static final int MAX_SEND_RETRY_COUNT = 2;
 
     private static final int ON_TIMEOUT_MS = 5000;
@@ -92,39 +89,6 @@
         tv().setSystemAudioMode(mode);
     }
 
-    protected void sendGiveAudioStatus() {
-        HdmiCecMessage command = HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(),
-                mAvrLogicalAddress);
-        sendCommand(command, new HdmiControlService.SendMessageCallback() {
-            @Override
-            public void onSendCompleted(int error) {
-                if (error == HdmiConstants.SEND_RESULT_SUCCESS) {
-                    mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
-                    addTimer(mState, TIMEOUT_MS);
-                } else {
-                    handleSendGiveAudioStatusFailure();
-                }
-            }
-        });
-    }
-
-    private void handleSendGiveAudioStatusFailure() {
-        // TODO: Notify the failure status.
-
-        int uiCommand = tv().getSystemAudioMode()
-                ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION  // SystemAudioMode: ON
-                : HdmiConstants.UI_COMMAND_MUTE_FUNCTION;           // SystemAudioMode: OFF
-        sendUserControlPressedAndReleased(uiCommand);
-        finish();
-    }
-
-    private void sendUserControlPressedAndReleased(int uiCommand) {
-        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
-                getSourceAddress(), mAvrLogicalAddress, uiCommand));
-        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
-                getSourceAddress(), mAvrLogicalAddress));
-    }
-
     @Override
     final boolean processCommand(HdmiCecMessage cmd) {
         switch (mState) {
@@ -137,7 +101,8 @@
                 boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
                 if (receivedStatus == mTargetAudioStatus) {
                     setSystemAudioMode(receivedStatus);
-                    sendGiveAudioStatus();
+                    startAudioStatusAction();
+                    return true;
                 } else {
                     // Unexpected response, consider the request is newly initiated by AVR.
                     // To return 'false' will initiate new SystemAudioActionFromAvr by the control
@@ -145,28 +110,14 @@
                     finish();
                     return false;
                 }
-                return true;
-
-            case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
-                // TODO: Handle <FeatureAbort> of <GiveAudioStatus>
-                if (cmd.getOpcode() != HdmiCec.MESSAGE_REPORT_AUDIO_STATUS
-                        || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
-                    return false;
-                }
-                byte[] params = cmd.getParams();
-                if (params.length > 0) {
-                    boolean mute = (params[0] & 0x80) == 0x80;
-                    int volume = params[0] & 0x7F;
-                    tv().setAudioStatus(mute, volume);
-                    if (mTargetAudioStatus && mute || !mTargetAudioStatus && !mute) {
-                        // Toggle AVR's mute status to match with the system audio status.
-                        sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
-                    }
-                }
-                finish();
-                return true;
+            default:
+                return false;
         }
-        return false;
+    }
+
+    protected void startAudioStatusAction() {
+        addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress));
+        finish();
     }
 
     protected void removeSystemAudioActionInProgress() {
@@ -183,9 +134,6 @@
             case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
                 handleSendSystemAudioModeRequestTimeout();
                 return;
-            case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
-                handleSendGiveAudioStatusFailure();
-                return;
         }
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index 3907b71..b743c64 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -59,7 +59,7 @@
         // TODO: Stop the action for System Audio Mode initialization if it is running.
         if (mTargetAudioStatus) {
             setSystemAudioMode(true);
-            sendGiveAudioStatus();
+            startAudioStatusAction();
         } else {
             setSystemAudioMode(false);
             finish();
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index 75e4fef..5f4fc23 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -25,7 +25,6 @@
 /**
  * Action to update audio status (volume or mute) of audio amplifier
  */
-// TODO: refactor SystemAudioMode so that it uses this class instead of internal state.
 final class SystemAudioStatusAction extends FeatureAction {
     private static final String TAG = "SystemAudioStatusAction";
 
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 15a6b25..53337c4 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -203,7 +203,8 @@
     @Override
     public String toString() {
         return String.valueOf(hashCode()).substring(0, 3) + ".."
-                + ":[" + job.getService().getPackageName() + ",jId=" + job.getId()
+                + ":[" + job.getService()
+                + ",jId=" + job.getId()
                 + ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")"
                 + ",N=" + job.getNetworkCapabilities() + ",C=" + job.isRequireCharging()
                 + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 9d61493..1264741 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,6 +17,8 @@
 package com.android.server.media;
 
 import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.media.routeprovider.RouteRequest;
@@ -25,7 +27,6 @@
 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;
@@ -34,9 +35,11 @@
 import android.media.session.MediaSessionInfo;
 import android.media.session.RouteInterface;
 import android.media.session.PlaybackState;
+import android.media.session.ParcelableVolumeInfo;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.VolumeProvider;
 import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Handler;
@@ -107,6 +110,7 @@
     // TODO define a RouteState class with relevant info
     private int mRouteState;
     private long mFlags;
+    private ComponentName mMediaButtonReceiver;
 
     // TransportPerformer fields
 
@@ -117,9 +121,10 @@
     // End TransportPerformer fields
 
     // Volume handling fields
-    private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
+    private AudioManager mAudioManager;
+    private int mVolumeType = MediaSession.VOLUME_TYPE_LOCAL;
     private int mAudioStream = AudioManager.STREAM_MUSIC;
-    private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+    private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
     private int mMaxVolume = 0;
     private int mCurrentVolume = 0;
     // End volume handling fields
@@ -140,6 +145,7 @@
         mSessionCb = new SessionCb(cb);
         mService = service;
         mHandler = new MessageHandler(handler.getLooper());
+        mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
     }
 
     /**
@@ -187,6 +193,10 @@
         return mSessionInfo;
     }
 
+    public ComponentName getMediaButtonReceiver() {
+        return mMediaButtonReceiver;
+    }
+
     /**
      * Get this session's flags.
      *
@@ -265,20 +275,42 @@
      *
      * @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;
+    public void adjustVolumeBy(int delta, int flags) {
+        if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) {
+            if (delta == 0) {
+                mAudioManager.adjustStreamVolume(mAudioStream, delta, flags);
+            } else {
+                int direction = 0;
+                int steps = delta;
+                if (delta > 0) {
+                    direction = 1;
+                } else if (delta < 0) {
+                    direction = -1;
+                    steps = -delta;
+                }
+                for (int i = 0; i < steps; i++) {
+                    mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
+                }
+            }
+        } else {
+            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
+                // Nothing to do, the volume cannot be changed
+                return;
+            }
+            mSessionCb.adjustVolumeBy(delta);
         }
-        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;
+    public void setVolumeTo(int value, int flags) {
+        if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) {
+            mAudioManager.setStreamVolume(mAudioStream, value, flags);
+        } else {
+            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+                // Nothing to do. The volume can't be set directly.
+                return;
+            }
+            mSessionCb.setVolumeTo(value);
         }
-        mSessionCb.setVolumeTo(value);
     }
 
     /**
@@ -352,7 +384,7 @@
      * @return The current type of playback.
      */
     public int getPlaybackType() {
-        return mPlaybackType;
+        return mVolumeType;
     }
 
     /**
@@ -683,6 +715,11 @@
         }
 
         @Override
+        public void setMediaButtonReceiver(ComponentName mbr) {
+            mMediaButtonReceiver = mbr;
+        }
+
+        @Override
         public void setMetadata(MediaMetadata metadata) {
             mMetadata = metadata;
             mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
@@ -754,7 +791,7 @@
         public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
             switch(type) {
                 case MediaSession.VOLUME_TYPE_LOCAL:
-                    mPlaybackType = type;
+                    mVolumeType = type;
                     int audioStream = arg1;
                     if (isValidStream(audioStream)) {
                         mAudioStream = audioStream;
@@ -764,7 +801,7 @@
                     }
                     break;
                 case MediaSession.VOLUME_TYPE_REMOTE:
-                    mPlaybackType = type;
+                    mVolumeType = type;
                     mVolumeControlType = arg1;
                     mMaxVolume = arg2;
                     break;
@@ -985,6 +1022,35 @@
         }
 
         @Override
+        public ParcelableVolumeInfo getVolumeAttributes() {
+            synchronized (mLock) {
+                int type;
+                int max;
+                int current;
+                if (mVolumeType == MediaSession.VOLUME_TYPE_REMOTE) {
+                    type = mVolumeControlType;
+                    max = mMaxVolume;
+                    current = mCurrentVolume;
+                } else {
+                    type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+                    max = mAudioManager.getStreamMaxVolume(mAudioStream);
+                    current = mAudioManager.getStreamVolume(mAudioStream);
+                }
+                return new ParcelableVolumeInfo(mVolumeType, mAudioStream, type, max, current);
+            }
+        }
+
+        @Override
+        public void adjustVolumeBy(int delta, int flags) {
+            MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+        }
+
+        @Override
+        public void setVolumeTo(int value, int flags) {
+            MediaSessionRecord.this.setVolumeTo(value, flags);
+        }
+
+        @Override
         public void play() throws RemoteException {
             mSessionCb.play();
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 67065ba..685717f 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -23,6 +23,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -88,6 +89,7 @@
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
+    private ContentResolver mContentResolver;
 
     private MediaSessionRecord mPrioritySession;
     private int mCurrentUserId = -1;
@@ -115,6 +117,7 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
+        mContentResolver = getContext().getContentResolver();
     }
 
     private IAudioService getAudioService() {
@@ -381,8 +384,7 @@
             return false;
         }
         if (compName != null) {
-            final String enabledNotifListeners = Settings.Secure.getStringForUser(
-                    getContext().getContentResolver(),
+            final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                     userId);
             if (enabledNotifListeners != null) {
@@ -485,6 +487,9 @@
         synchronized (mLock) {
             List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
             int size = records.size();
+            if (size > 0) {
+                persistMediaButtonReceiverLocked(records.get(0));
+            }
             ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
             for (int i = 0; i < size; i++) {
                 tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
@@ -504,6 +509,16 @@
         }
     }
 
+    private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
+        ComponentName receiver = record.getMediaButtonReceiver();
+        if (receiver != null) {
+            Settings.System.putStringForUser(mContentResolver,
+                    Settings.System.MEDIA_BUTTON_RECEIVER,
+                    receiver == null ? "" : receiver.flattenToString(),
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
     private MediaRouteProviderProxy.RoutesListener mRoutesCallback
             = new MediaRouteProviderProxy.RoutesListener() {
         @Override
@@ -881,14 +896,6 @@
 
         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
@@ -901,6 +908,14 @@
                         mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
                                 getContext().getOpPackageName());
                     } else {
+                        int direction = 0;
+                        int steps = delta;
+                        if (delta > 0) {
+                            direction = 1;
+                        } else if (delta < 0) {
+                            direction = -1;
+                            steps = -delta;
+                        }
                         for (int i = 0; i < steps; i++) {
                             mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
                                     flags, getContext().getOpPackageName());
@@ -910,26 +925,7 @@
                     Log.e(TAG, "Error adjusting default volume.", e);
                 }
             } else {
-                if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
-                    try {
-                        if (delta == 0) {
-                            mAudioService.adjustSuggestedStreamVolume(delta,
-                                    session.getAudioStream(), flags,
-                                    getContext().getOpPackageName());
-                        } else {
-                            for (int i = 0; i < steps; i++) {
-                                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);
-                }
+                session.adjustVolumeBy(delta, flags);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 2a7b4f6..1d53016 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -34,9 +34,9 @@
 public class BackgroundDexOptService extends JobService {
     static final String TAG = "BackgroundDexOptService";
 
-    static final int BACKGROUND_DEXOPT_JOB = 808;
+    static final int BACKGROUND_DEXOPT_JOB = 800;
     private static ComponentName sDexoptServiceName = new ComponentName(
-            BackgroundDexOptService.class.getPackage().getName(),
+            "android",
             BackgroundDexOptService.class.getName());
 
     final AtomicBoolean mIdleTime = new AtomicBoolean(false);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a7fc7eb..90e263a 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4229,6 +4229,7 @@
 
         try {
             pp.collectCertificates(pkg, parseFlags);
+            pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
             mLastScanError = e.error;
             return false;
@@ -10272,6 +10273,7 @@
 
         try {
             pp.collectCertificates(pkg, parseFlags);
+            pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
             res.returnCode = e.error;
             return;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index cbbcc58..6798f3f 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -106,11 +106,9 @@
         mLogHandler = new LogHandler(IoThread.get().getLooper());
 
         mTvInputHardwareManager = new TvInputHardwareManager(context);
-        registerBroadcastReceivers();
 
         synchronized (mLock) {
             mUserStates.put(mCurrentUserId, new UserState());
-            buildTvInputListLocked(mCurrentUserId);
         }
     }
 
@@ -119,6 +117,16 @@
         publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
     }
 
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            registerBroadcastReceivers();
+            synchronized (mLock) {
+                buildTvInputListLocked(mCurrentUserId);
+            }
+        }
+    }
+
     private void registerBroadcastReceivers() {
         PackageMonitor monitor = new PackageMonitor() {
             @Override
@@ -631,7 +639,9 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            return false;
+            // STOPSHIP: Redesign the API around the availability change. For now, the service
+            // will be always available.
+            return true;
         }
 
         @Override
diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp
index 9a5079d..3696e24 100644
--- a/services/core/jni/com_android_server_AssetAtlasService.cpp
+++ b/services/core/jni/com_android_server_AssetAtlasService.cpp
@@ -61,8 +61,7 @@
         jobject canvas, jint width, jint height) {
 
     SkBitmap* bitmap = new SkBitmap;
-    bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
-    bitmap->allocPixels();
+    bitmap->allocN32Pixels(width, height);
     bitmap->eraseColor(0);
     INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(bitmap));
 
diff --git a/telecomm/java/android/telecomm/PhoneApplication.java b/telecomm/java/android/telecomm/PhoneApplication.java
deleted file mode 100644
index 1da54e0..0000000
--- a/telecomm/java/android/telecomm/PhoneApplication.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package android.telecomm;
-
-import android.annotation.SystemApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.telecomm.ITelecommService;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class for managing the primary phone application that will receive incoming calls, and be allowed
- * to make emergency outgoing calls.
- *
- * @hide
- */
-public class PhoneApplication {
-    private static final String TAG = PhoneApplication.class.getSimpleName();
-    private static final String TELECOMM_SERVICE_NAME = "telecomm";
-
-    /**
-     * Sets the specified package name as the default phone application. The caller of this method
-     * needs to have permission to write to secure settings.
-     *
-     * @hide
-     * */
-    @SystemApi
-    public static void setDefaultPhoneApplication(String packageName, Context context) {
-        // Get old package name
-        String oldPackageName = Settings.Secure.getString(context.getContentResolver(),
-                Settings.Secure.PHONE_DEFAULT_APPLICATION);
-
-        if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
-            // No change
-            return;
-        }
-
-        // Only make the change if the new package belongs to a valid phone application
-        List<ComponentName> componentNames = getInstalledPhoneApplications(context);
-        ComponentName foundComponent = null;
-        for (ComponentName componentName : componentNames) {
-            if (TextUtils.equals(componentName.getPackageName(), packageName)) {
-                foundComponent = componentName;
-                break;
-            }
-        }
-
-        if (foundComponent != null) {
-            // Update the secure setting.
-            Settings.Secure.putString(context.getContentResolver(),
-                    Settings.Secure.PHONE_DEFAULT_APPLICATION, foundComponent.getPackageName());
-        }
-    }
-
-    /**
-     * Returns the installed phone application that will be used to receive incoming calls, and is
-     * allowed to make emergency calls.
-     *
-     * The application will be returned in order of preference:
-     * 1) User selected phone application (if still installed)
-     * 2) Pre-installed system dialer (if not disabled)
-     * 3) Null
-     *
-     * @hide
-     * */
-    @SystemApi
-    public static ComponentName getDefaultPhoneApplication(Context context) {
-        String defaultPackageName = Settings.Secure.getString(context.getContentResolver(),
-                Settings.Secure.PHONE_DEFAULT_APPLICATION);
-
-        final List<ComponentName> componentNames = getInstalledPhoneApplications(context);
-        if (!TextUtils.isEmpty(defaultPackageName)) {
-            for (ComponentName componentName : componentNames) {
-                if (TextUtils.equals(componentName.getPackageName(), defaultPackageName)) {
-                    return componentName;
-                }
-            }
-        }
-
-        // No user-set dialer found, fallback to system dialer
-        ComponentName systemDialer = null;
-        try {
-            systemDialer = getTelecommService().getSystemPhoneApplication();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelecommService#getSystemPhoneApplication", e);
-            return null;
-        }
-
-        if (systemDialer == null) {
-            // No system dialer configured at build time
-            return null;
-        }
-
-        // Verify that the system dialer has not been disabled.
-        return getComponentName(componentNames, systemDialer.getPackageName());
-    }
-
-    /**
-     * Returns a list of installed and available phone applications.
-     *
-     * In order to appear in the list, a phone application must implement an intent-filter with
-     * the DIAL intent for the following schemes:
-     *
-     * 1) Empty scheme
-     * 2) tel Uri scheme
-     *
-     * @hide
-     **/
-    @SystemApi
-    public static List<ComponentName> getInstalledPhoneApplications(Context context) {
-        PackageManager packageManager = context.getPackageManager();
-
-        // Get the list of apps registered for the DIAL intent with empty scheme
-        Intent intent = new Intent(Intent.ACTION_DIAL);
-        List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
-
-        List<ComponentName> componentNames = new ArrayList<ComponentName> ();
-
-        for (ResolveInfo resolveInfo : resolveInfoList) {
-            final ActivityInfo activityInfo = resolveInfo.activityInfo;
-            if (activityInfo == null) {
-                continue;
-            }
-            final ComponentName componentName =
-                    new ComponentName(activityInfo.packageName, activityInfo.name);
-            componentNames.add(componentName);
-        }
-
-        // TODO: Filter for apps that don't handle DIAL intent with tel scheme
-        return componentNames;
-    }
-
-    /**
-     * Returns the {@link ComponentName} for the installed phone application for a given package
-     * name.
-     *
-     * @param context A valid context.
-     * @param packageName to retrieve the {@link ComponentName} for.
-     *
-     * @return The {@link ComponentName} for the installed phone application corresponding to the
-     * package name, or null if none is found.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static ComponentName getPhoneApplicationForPackageName(Context context,
-            String packageName) {
-        return getComponentName(getInstalledPhoneApplications(context), packageName);
-    }
-
-    /**
-     * Returns the component from a list of application components that corresponds to the package
-     * name.
-     *
-     * @param componentNames A list of component names
-     * @param packageName The package name to look for
-     * @return The {@link ComponentName} that matches the provided packageName, or null if not
-     *         found.
-     */
-    private static ComponentName getComponentName(List<ComponentName> componentNames,
-            String packageName) {
-        for (ComponentName componentName : componentNames) {
-            if (TextUtils.equals(packageName, componentName.getPackageName())) {
-                return componentName;
-            }
-        }
-        return null;
-    }
-
-    private static ITelecommService getTelecommService() {
-        return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
-    }
-}
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index 0952097..0a12c08 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -56,21 +56,6 @@
     public static final String ACTION_CALL_SERVICE_SELECTOR = CallServiceSelector.class.getName();
 
     /**
-     * Activity action: Ask the user to change the default phone application. This will show a
-     * dialog that asks the user whether they want to replace the current default phone application
-     * with the one defined in {@link #EXTRA_PACKAGE_NAME}.
-     */
-    public static final String ACTION_CHANGE_DEFAULT_PHONE =
-            "android.telecomm.ACTION_CHANGE_DEFAULT_PHONE";
-
-    /**
-     * The PackageName string passed in as an extra for {@link #ACTION_CHANGE_DEFAULT_PHONE}.
-     *
-     * @see #ACTION_CHANGE_DEFAULT_PHONE
-     */
-    public static final String EXTRA_PACKAGE_NAME = "package";
-
-    /**
      * Optional extra for {@link Intent#ACTION_CALL} containing a boolean that determines whether
      * the speakerphone should be automatically turned on for an outgoing call.
      */
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index 989c2cd..a97e7e4 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -16,9 +16,7 @@
 
 package android.telecomm;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.os.RemoteException;
 
 import com.android.internal.telecomm.ITelecommService;
 
@@ -47,14 +45,4 @@
     public static TelecommManager from(Context context) {
         return (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
     }
-
-    /** {@hide} */
-    public ComponentName getSystemPhoneApplication() {
-        try {
-            return mService.getSystemPhoneApplication();
-        } catch (RemoteException e) {
-            Log.e(TAG, e, "Error calling ITelecommService#getSystemPhoneApplication");
-            return null;
-        }
-    }
 }
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index dc2b869..c758c6d 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -17,7 +17,6 @@
 package com.android.internal.telecomm;
 
 import android.telecomm.Subscription;
-import android.content.ComponentName;
 
 /**
  * Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing
@@ -43,11 +42,6 @@
     void showCallScreen(boolean showDialpad);
 
     /**
-     * Returns the component name of the phone application installed on the system partition.
-     */
-    ComponentName getSystemPhoneApplication();
-
-    /**
      * Gets a list of Subscriptions.
      */
     List<Subscription> getSubscriptions();
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 777471d..59b48e4 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -373,19 +373,19 @@
     public static int GOOD_RSSI_24 = -65;
 
     /** @hide **/
-    public static int LOW_RSSI_24 = -75;
+    public static int LOW_RSSI_24 = -77;
 
     /** @hide **/
     public static int BAD_RSSI_24 = -87;
 
     /** @hide **/
-    public static int GOOD_RSSI_5 = -55;
+    public static int GOOD_RSSI_5 = -60;
 
     /** @hide **/
-    public static int LOW_RSSI_5 = -65;
+    public static int LOW_RSSI_5 = -72;
 
     /** @hide **/
-    public static int BAD_RSSI_5 = -75;
+    public static int BAD_RSSI_5 = -82;
 
     /** @hide **/
     public static int UNWANTED_BLACKLIST_SOFT_BUMP = 4;
@@ -419,6 +419,15 @@
      * 5GHz band is penalized if the 5GHz RSSI is lower than this threshold **/
     public static int G_BAND_PREFERENCE_RSSI_THRESHOLD = -75;
 
+    /** @hide
+     * Boost given to RSSI on a home network for the purpose of calculating the score
+     * This adds stickiness to home networks, as defined by:
+     * - less than 4 known BSSIDs
+     * - PSK only
+     * - TODO: add a test to verify that all BSSIDs are behind same gateway
+     ***/
+    public static int HOME_NETWORK_RSSI_BOOST = 5;
+
     /**
      * @hide
      * A summary of the RSSI and Band status for that configuration