Merge "Validate that the authority of incoming uris matches." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index d4f2cc5..da1ebae 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -479,6 +479,7 @@
     field public static final int dashWidth = 16843174; // 0x10101a6
     field public static final int data = 16842798; // 0x101002e
     field public static final int datePickerDialogTheme = 16843951; // 0x10104af
+    field public static final int datePickerMode = 16843958; // 0x10104b6
     field public static final int datePickerStyle = 16843612; // 0x101035c
     field public static final int dateTextAppearance = 16843593; // 0x1010349
     field public static final int dayOfWeekBackgroundColor = 16843926; // 0x1010496
@@ -589,7 +590,6 @@
     field public static final int fillBefore = 16843196; // 0x10101bc
     field public static final int fillColor = 16843807; // 0x101041f
     field public static final int fillEnabled = 16843343; // 0x101024f
-    field public static final int fillOpacity = 16843806; // 0x101041e
     field public static final int fillViewport = 16843130; // 0x101017a
     field public static final int filter = 16843035; // 0x101011b
     field public static final int filterTouchesWhenObscured = 16843460; // 0x10102c4
@@ -602,6 +602,7 @@
     field public static final int focusableInTouchMode = 16842971; // 0x10100db
     field public static final int focusedMonthDateColor = 16843587; // 0x1010343
     field public static final int fontFamily = 16843692; // 0x10103ac
+    field public static final int fontFeatureSettings = 16843962; // 0x10104ba
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
@@ -711,6 +712,7 @@
     field public static final int innerRadiusRatio = 16843163; // 0x101019b
     field public static final deprecated int inputMethod = 16843112; // 0x1010168
     field public static final int inputType = 16843296; // 0x1010220
+    field public static final int inset = 16843960; // 0x10104b8
     field public static final int insetBottom = 16843194; // 0x10101ba
     field public static final int insetLeft = 16843191; // 0x10101b7
     field public static final int insetRight = 16843192; // 0x10101b8
@@ -836,6 +838,7 @@
     field public static final int layout_x = 16843135; // 0x101017f
     field public static final int layout_y = 16843136; // 0x1010180
     field public static final int left = 16843181; // 0x10101ad
+    field public static final int letterSpacing = 16843961; // 0x10104b9
     field public static final int lineSpacingExtra = 16843287; // 0x1010217
     field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
     field public static final int lines = 16843092; // 0x1010154
@@ -1189,7 +1192,6 @@
     field public static final int strokeLineCap = 16843815; // 0x1010427
     field public static final int strokeLineJoin = 16843816; // 0x1010428
     field public static final int strokeMiterLimit = 16843817; // 0x1010429
-    field public static final int strokeOpacity = 16843810; // 0x1010422
     field public static final int strokeWidth = 16843811; // 0x1010423
     field public static final int submitBackground = 16843914; // 0x101048a
     field public static final int subtitle = 16843473; // 0x10102d1
@@ -1313,6 +1315,7 @@
     field public static final int tileModeX = 16843897; // 0x1010479
     field public static final int tileModeY = 16843898; // 0x101047a
     field public static final int timePickerDialogTheme = 16843936; // 0x10104a0
+    field public static final int timePickerMode = 16843959; // 0x10104b7
     field public static final int timePickerStyle = 16843935; // 0x101049f
     field public static final int timeZone = 16843724; // 0x10103cc
     field public static final int tint = 16843041; // 0x1010121
@@ -2054,6 +2057,13 @@
     field public static final int TextAppearance_StatusBar_EventContent = 16973927; // 0x1030067
     field public static final int TextAppearance_StatusBar_EventContent_Title = 16973928; // 0x1030068
     field public static final int TextAppearance_StatusBar_Icon = 16973926; // 0x1030066
+    field public static final int TextAppearance_StatusBar_Material = 16974559; // 0x10302df
+    field public static final int TextAppearance_StatusBar_Material_EventContent = 16974560; // 0x10302e0
+    field public static final int TextAppearance_StatusBar_Material_EventContent_Emphasis = 16974565; // 0x10302e5
+    field public static final int TextAppearance_StatusBar_Material_EventContent_Info = 16974563; // 0x10302e3
+    field public static final int TextAppearance_StatusBar_Material_EventContent_Line2 = 16974562; // 0x10302e2
+    field public static final int TextAppearance_StatusBar_Material_EventContent_Time = 16974564; // 0x10302e4
+    field public static final int TextAppearance_StatusBar_Material_EventContent_Title = 16974561; // 0x10302e1
     field public static final int TextAppearance_StatusBar_Title = 16973925; // 0x1030065
     field public static final int TextAppearance_SuggestionHighlight = 16974104; // 0x1030118
     field public static final int TextAppearance_Theme = 16973888; // 0x1030040
@@ -8675,6 +8685,7 @@
     method public abstract void onSuccess();
     method public abstract void onUserActionRequired(android.content.Intent);
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
+    field public static final int FAILURE_ABORTED = 5; // 0x5
     field public static final int FAILURE_CONFLICT = 2; // 0x2
     field public static final int FAILURE_INCOMPATIBLE = 4; // 0x4
     field public static final int FAILURE_INVALID = 1; // 0x1
@@ -8704,9 +8715,12 @@
 
   public static abstract class PackageInstaller.UninstallCallback {
     ctor public PackageInstaller.UninstallCallback();
-    method public abstract void onFailure(java.lang.String);
+    method public abstract void onFailure(int, java.lang.String, android.os.Bundle);
     method public abstract void onSuccess();
     method public abstract void onUserActionRequired(android.content.Intent);
+    field public static final int FAILURE_ABORTED = 2; // 0x2
+    field public static final int FAILURE_BLOCKED = 1; // 0x1
+    field public static final int FAILURE_UNKNOWN = 0; // 0x0
   }
 
   public class PackageItemInfo {
@@ -11026,12 +11040,14 @@
     method public android.graphics.ColorFilter getColorFilter();
     method public boolean getFillPath(android.graphics.Path, android.graphics.Path);
     method public int getFlags();
+    method public java.lang.String getFontFeatureSettings();
     method public float getFontMetrics(android.graphics.Paint.FontMetrics);
     method public android.graphics.Paint.FontMetrics getFontMetrics();
     method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
     method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
     method public float getFontSpacing();
     method public int getHinting();
+    method public float getLetterSpacing();
     method public android.graphics.MaskFilter getMaskFilter();
     method public android.graphics.PathEffect getPathEffect();
     method public deprecated android.graphics.Rasterizer getRasterizer();
@@ -11081,7 +11097,9 @@
     method public void setFakeBoldText(boolean);
     method public void setFilterBitmap(boolean);
     method public void setFlags(int);
+    method public void setFontFeatureSettings(java.lang.String);
     method public void setHinting(int);
+    method public void setLetterSpacing(float);
     method public void setLinearText(boolean);
     method public android.graphics.MaskFilter setMaskFilter(android.graphics.MaskFilter);
     method public android.graphics.PathEffect setPathEffect(android.graphics.PathEffect);
@@ -13234,10 +13252,10 @@
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
     field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+    field public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 16; // 0x10
     field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
     field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
     field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
-    field public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 16; // 0x10
     field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
   }
 
@@ -15132,6 +15150,7 @@
     method public boolean containsKey(java.lang.String);
     method public int describeContents();
     method public android.graphics.Bitmap getBitmap(java.lang.String);
+    method public android.media.MediaMetadata.Description getDescription();
     method public long getLong(java.lang.String);
     method public android.media.Rating getRating(java.lang.String);
     method public java.lang.String getString(java.lang.String);
@@ -15179,6 +15198,14 @@
     method public android.media.MediaMetadata.Builder putText(java.lang.String, java.lang.CharSequence);
   }
 
+  public final class MediaMetadata.Description {
+    method public java.lang.CharSequence getDescription();
+    method public android.graphics.Bitmap getIcon();
+    method public android.net.Uri getIconUri();
+    method public java.lang.CharSequence getSubtitle();
+    method public java.lang.CharSequence getTitle();
+  }
+
   public abstract deprecated class MediaMetadataEditor {
     method public synchronized void addEditableKey(int);
     method public abstract void apply();
@@ -16399,7 +16426,7 @@
   public final class MediaProjection {
     method public void addCallback(android.media.projection.MediaProjection.Callback, android.os.Handler);
     method public android.media.AudioRecord createAudioRecord(int, int, int, int);
-    method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, boolean, android.view.Surface, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
+    method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, int, android.view.Surface, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
     method public void removeCallback(android.media.projection.MediaProjection.Callback);
     method public void stop();
   }
@@ -16716,18 +16743,14 @@
 
   public final class MediaSession {
     ctor public MediaSession(android.content.Context, java.lang.String);
-    method public void addCallback(android.media.session.MediaSession.Callback);
-    method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
-    method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
-    method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback, android.os.Handler);
     method public android.media.session.MediaController getController();
     method public android.media.session.MediaSession.Token getSessionToken();
     method public boolean isActive();
     method public void release();
-    method public void removeCallback(android.media.session.MediaSession.Callback);
-    method public void removeTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
     method public void sendSessionEvent(java.lang.String, android.os.Bundle);
     method public void setActive(boolean);
+    method public void setCallback(android.media.session.MediaSession.Callback);
+    method public void setCallback(android.media.session.MediaSession.Callback, android.os.Handler);
     method public void setExtras(android.os.Bundle);
     method public void setFlags(int);
     method public void setLaunchActivity(android.app.PendingIntent);
@@ -16748,7 +16771,20 @@
   public static abstract class MediaSession.Callback {
     ctor public MediaSession.Callback();
     method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public void onMediaButtonEvent(android.content.Intent);
+    method public void onCustomAction(java.lang.String, android.os.Bundle);
+    method public void onFastForward();
+    method public boolean onMediaButtonEvent(android.content.Intent);
+    method public void onPause();
+    method public void onPlay();
+    method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
+    method public void onPlayUri(android.net.Uri, android.os.Bundle);
+    method public void onRewind();
+    method public void onSeekTo(long);
+    method public void onSetRating(android.media.Rating);
+    method public void onSkipToNext();
+    method public void onSkipToPrevious();
+    method public void onSkipToTrack(long);
+    method public void onStop();
   }
 
   public static final class MediaSession.Token implements android.os.Parcelable {
@@ -16774,23 +16810,6 @@
     method public android.media.session.MediaSession.Track.Builder setExtras(android.os.Bundle);
   }
 
-  public static abstract class MediaSession.TransportControlsCallback {
-    ctor public MediaSession.TransportControlsCallback();
-    method public void onCustomAction(java.lang.String, android.os.Bundle);
-    method public void onFastForward();
-    method public void onPause();
-    method public void onPlay();
-    method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
-    method public void onPlayUri(android.net.Uri, android.os.Bundle);
-    method public void onRewind();
-    method public void onSeekTo(long);
-    method public void onSetRating(android.media.Rating);
-    method public void onSkipToNext();
-    method public void onSkipToPrevious();
-    method public void onSkipToTrack(long);
-    method public void onStop();
-  }
-
   public final class MediaSessionManager {
     method public void addActiveSessionsListener(android.media.session.MediaSessionManager.SessionListener, android.content.ComponentName);
     method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
@@ -27531,13 +27550,16 @@
 
   public static abstract interface AlwaysOnHotwordDetector.Callback {
     method public abstract void onAvailabilityChanged(int);
-    method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.TriggerAudio);
+    method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.EventPayload);
     method public abstract void onError();
+    method public abstract void onRecognitionPaused();
+    method public abstract void onRecognitionResumed();
   }
 
-  public static class AlwaysOnHotwordDetector.TriggerAudio {
+  public static class AlwaysOnHotwordDetector.EventPayload {
     field public final android.media.AudioFormat audioFormat;
     field public final byte[] data;
+    field public final boolean isTriggerAudio;
   }
 
   public class VoiceInteractionService extends android.app.Service {
@@ -28595,6 +28617,7 @@
     method public void swapWithBackgroundCall();
     method public void unhold();
     field public static final int STATE_ACTIVE = 4; // 0x4
+    field public static final int STATE_CONNECTING = 9; // 0x9
     field public static final int STATE_DIALING = 1; // 0x1
     field public static final int STATE_DISCONNECTED = 7; // 0x7
     field public static final int STATE_HOLDING = 3; // 0x3
@@ -28687,6 +28710,7 @@
     method public static android.telecomm.CallState valueOf(java.lang.String);
     method public static final android.telecomm.CallState[] values();
     enum_constant public static final android.telecomm.CallState ACTIVE;
+    enum_constant public static final android.telecomm.CallState CONNECTING;
     enum_constant public static final android.telecomm.CallState DIALING;
     enum_constant public static final android.telecomm.CallState DISCONNECTED;
     enum_constant public static final android.telecomm.CallState NEW;
@@ -35531,6 +35555,7 @@
     method public int getSystemWindowInsetTop();
     method public boolean hasInsets();
     method public boolean hasSystemWindowInsets();
+    method public boolean isConsumed();
     method public boolean isRound();
     method public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int);
   }
@@ -37493,6 +37518,7 @@
     method public void clearChoices();
     method public void clearTextFilter();
     method public void deferNotifyDataSetChanged();
+    method public void fling(int);
     method public int getCacheColorHint();
     method public int getCheckedItemCount();
     method public long[] getCheckedItemIds();
@@ -39651,6 +39677,7 @@
     method public int getExtendedPaddingBottom();
     method public int getExtendedPaddingTop();
     method public android.text.InputFilter[] getFilters();
+    method public java.lang.String getFontFeatureSettings();
     method public boolean getFreezesText();
     method public int getGravity();
     method public int getHighlightColor();
@@ -39664,6 +39691,7 @@
     method public int getInputType();
     method public final android.text.method.KeyListener getKeyListener();
     method public final android.text.Layout getLayout();
+    method public float getLetterSpacing();
     method public int getLineBounds(int, android.graphics.Rect);
     method public int getLineCount();
     method public int getLineHeight();
@@ -39747,6 +39775,7 @@
     method public void setError(java.lang.CharSequence, android.graphics.drawable.Drawable);
     method public void setExtractedText(android.view.inputmethod.ExtractedText);
     method public void setFilters(android.text.InputFilter[]);
+    method public void setFontFeatureSettings(java.lang.String);
     method protected boolean setFrame(int, int, int, int);
     method public void setFreezesText(boolean);
     method public void setGravity(int);
@@ -39763,6 +39792,7 @@
     method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public void setInputType(int);
     method public void setKeyListener(android.text.method.KeyListener);
+    method public void setLetterSpacing(float);
     method public void setLineSpacing(float, float);
     method public void setLines(int);
     method public final void setLinkTextColor(int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fc200550..3bf8e2e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -700,6 +700,13 @@
          */
         public int affiliatedTaskId;
 
+        /**
+         * Task affiliation color of the source task with the affiliated task id.
+         *
+         * @hide
+         */
+        public int affiliatedTaskColor;
+
         public RecentTaskInfo() {
         }
 
@@ -732,6 +739,7 @@
             dest.writeLong(firstActiveTime);
             dest.writeLong(lastActiveTime);
             dest.writeInt(affiliatedTaskId);
+            dest.writeInt(affiliatedTaskColor);
         }
 
         public void readFromParcel(Parcel source) {
@@ -747,6 +755,7 @@
             firstActiveTime = source.readLong();
             lastActiveTime = source.readLong();
             affiliatedTaskId = source.readInt();
+            affiliatedTaskColor = source.readInt();
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 84b5516..a935dc0 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -28,7 +28,6 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
@@ -53,7 +52,6 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -1655,39 +1653,6 @@
                 new UserHandle(mContext.getUserId()));
     }
 
-    private static class LegacyPackageInstallObserver extends PackageInstallObserver {
-        private final IPackageInstallObserver mLegacy;
-
-        public LegacyPackageInstallObserver(IPackageInstallObserver legacy) {
-            mLegacy = legacy;
-        }
-
-        @Override
-        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
-                Bundle extras) {
-            try {
-                mLegacy.packageInstalled(basePackageName, returnCode);
-            } catch (RemoteException ignored) {
-            }
-        }
-    }
-
-    private static class LegacyPackageDeleteObserver extends PackageDeleteObserver {
-        private final IPackageDeleteObserver mLegacy;
-
-        public LegacyPackageDeleteObserver(IPackageDeleteObserver legacy) {
-            mLegacy = legacy;
-        }
-
-        @Override
-        public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
-            try {
-                mLegacy.packageDeleted(basePackageName, returnCode);
-            } catch (RemoteException ignored) {
-            }
-        }
-    }
-
     private final ContextImpl mContext;
     private final IPackageManager mPM;
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5684a7a..90b8b86 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -32,7 +32,6 @@
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.BadParcelableException;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1900,8 +1899,7 @@
             mPriority = PRIORITY_DEFAULT;
             mPeople = new ArrayList<String>();
 
-            mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L ?
-                    NotificationColorUtil.getInstance() : null;
+            mColorUtil = NotificationColorUtil.getInstance();
         }
 
         /**
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index be8108c..e9297b9 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.app.backup.RestoreSession;
 import android.app.backup.IBackupManager;
 import android.app.backup.IRestoreSession;
@@ -114,7 +115,7 @@
             try {
                 sService.dataChanged(packageName);
             } catch (RemoteException e) {
-                Log.d(TAG, "dataChanged(pkg) couldn't connect");
+                Log.e(TAG, "dataChanged(pkg) couldn't connect");
             }
         }
     }
@@ -150,7 +151,7 @@
                     result = session.restorePackage(mContext.getPackageName(), observer);
                 }
             } catch (RemoteException e) {
-                Log.w(TAG, "restoreSelf() unable to contact service");
+                Log.e(TAG, "restoreSelf() unable to contact service");
             } finally {
                 if (session != null) {
                     session.endRestoreSession();
@@ -160,11 +161,14 @@
         return result;
     }
 
+    // system APIs start here
+
     /**
      * Begin the process of restoring data from backup.  See the
      * {@link android.app.backup.RestoreSession} class for documentation on that process.
      * @hide
      */
+    @SystemApi
     public RestoreSession beginRestoreSession() {
         RestoreSession session = null;
         checkServiceBinder();
@@ -176,9 +180,140 @@
                     session = new RestoreSession(mContext, binder);
                 }
             } catch (RemoteException e) {
-                Log.w(TAG, "beginRestoreSession() couldn't connect");
+                Log.e(TAG, "beginRestoreSession() couldn't connect");
             }
         }
         return session;
     }
+
+    /**
+     * Enable/disable the backup service entirely.  When disabled, no backup
+     * or restore operations will take place.  Data-changed notifications will
+     * still be observed and collected, however, so that changes made while the
+     * mechanism was disabled will still be backed up properly if it is enabled
+     * at some point in the future.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setBackupEnabled(boolean isEnabled) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.setBackupEnabled(isEnabled);
+            } catch (RemoteException e) {
+                Log.e(TAG, "setBackupEnabled() couldn't connect");
+            }
+        }
+    }
+
+    /**
+     * Report whether the backup mechanism is currently enabled.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isBackupEnabled() {
+        if (sService != null) {
+            try {
+                return sService.isBackupEnabled();
+            } catch (RemoteException e) {
+                Log.e(TAG, "isBackupEnabled() couldn't connect");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Identify the currently selected transport.  Callers must hold the
+     * android.permission.BACKUP permission to use this method.
+     * @return The name of the currently active backup transport.  In case of
+     *   failure or if no transport is currently active, this method returns {@code null}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String getCurrentTransport() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.getCurrentTransport();
+            } catch (RemoteException e) {
+                Log.e(TAG, "getCurrentTransport() couldn't connect");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Request a list of all available backup transports' names.  Callers must
+     * hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String[] listAllTransports() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.listAllTransports();
+            } catch (RemoteException e) {
+                Log.e(TAG, "listAllTransports() couldn't connect");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Specify the current backup transport.  Callers must hold the
+     * android.permission.BACKUP permission to use this method.
+     *
+     * @param transport The name of the transport to select.  This should be one
+     *   of the names returned by {@link #listAllTransports()}.
+     * @return The name of the previously selected transport.  If the given transport
+     *   name is not one of the currently available transports, no change is made to
+     *   the current transport setting and the method returns null.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String selectBackupTransport(String transport) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.selectBackupTransport(transport);
+            } catch (RemoteException e) {
+                Log.e(TAG, "selectBackupTransport() couldn't connect");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Schedule an immediate backup attempt for all pending key/value updates.  This
+     * is primarily intended for transports to use when they detect a suitable
+     * opportunity for doing a backup pass.  If there are no pending updates to
+     * be sent, no action will be taken.  Even if some updates are pending, the
+     * transport will still be asked to confirm via the usual requestBackupTime()
+     * method.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void backupNow() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.backupNow();
+            } catch (RemoteException e) {
+                Log.e(TAG, "backupNow() couldn't connect");
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 446c03e..6adc2e0 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.os.IBinder;
@@ -30,6 +31,7 @@
  *
  * @hide
  */
+@SystemApi
 public class BackupTransport {
     // Zero return always means things are okay.  If returned from
     // getNextFullRestoreDataChunk(), it means that no data could be delivered at
@@ -403,6 +405,25 @@
         return BackupTransport.TRANSPORT_ERROR;
     }
 
+    /**
+     * Tells the transport to cancel the currently-ongoing full backup operation.  This
+     * will happen between {@link #performFullBackup()} and {@link #finishBackup()}
+     * if the OS needs to abort the backup operation for any reason, such as a crash in
+     * the application undergoing backup.
+     *
+     * <p>When it receives this call, the transport should discard any partial archive
+     * that it has stored so far.  If possible it should also roll back to the previous
+     * known-good archive in its datastore.
+     *
+     * <p>If the transport receives this callback, it will <em>not</em> receive a
+     * call to {@link #finishBackup()}.  It needs to tear down any ongoing backup state
+     * here.
+     */
+    public void cancelFullBackup() {
+        throw new UnsupportedOperationException(
+                "Transport cancelFullBackup() not implemented");
+    }
+
     // ------------------------------------------------------------------------------------
     // Full restore interfaces
 
diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java
index 50ab0b4..611ff07 100644
--- a/core/java/android/app/backup/RestoreDescription.java
+++ b/core/java/android/app/backup/RestoreDescription.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,6 +29,7 @@
  *
  * @hide
  */
+@SystemApi
 public class RestoreDescription implements Parcelable {
     private final String mPackageName;
     private final int mDataType;
diff --git a/core/java/android/app/backup/RestoreObserver.java b/core/java/android/app/backup/RestoreObserver.java
index dbddb78..b4a23d0 100644
--- a/core/java/android/app/backup/RestoreObserver.java
+++ b/core/java/android/app/backup/RestoreObserver.java
@@ -17,6 +17,8 @@
 package android.app.backup;
 
 import java.lang.String;
+
+import android.annotation.SystemApi;
 import android.app.backup.RestoreSet;
 
 /**
@@ -36,6 +38,7 @@
      *
      * @hide
      */
+    @SystemApi
     public void restoreSetsAvailable(RestoreSet[] result) {
     }
 
diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java
index 7181c61..0a885b6 100644
--- a/core/java/android/app/backup/RestoreSession.java
+++ b/core/java/android/app/backup/RestoreSession.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.app.backup.RestoreObserver;
 import android.app.backup.RestoreSet;
 import android.app.backup.IRestoreObserver;
@@ -30,6 +31,7 @@
  * Interface for managing a restore session.
  * @hide
  */
+@SystemApi
 public class RestoreSession {
     static final String TAG = "RestoreSession";
 
diff --git a/core/java/android/app/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index 0431977..aacaf7c 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,6 +26,7 @@
  *
  * @hide
  */
+@SystemApi
 public class RestoreSet implements Parcelable {
     /**
      * Name of this restore set.  May be user generated, may simply be the name
diff --git a/core/java/android/app/trust/ITrustListener.aidl b/core/java/android/app/trust/ITrustListener.aidl
index 45a066d..d80f58c 100644
--- a/core/java/android/app/trust/ITrustListener.aidl
+++ b/core/java/android/app/trust/ITrustListener.aidl
@@ -22,6 +22,6 @@
  * {@hide}
  */
 oneway interface ITrustListener {
-    void onTrustChanged(boolean enabled, int userId);
+    void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser);
     void onTrustManagedChanged(boolean managed, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 796e3cc..3d262b1 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -34,6 +34,7 @@
     private static final int MSG_TRUST_MANAGED_CHANGED = 2;
 
     private static final String TAG = "TrustManager";
+    private static final String DATA_INITIATED_BY_USER = "initiatedByUser";
 
     private final ITrustManager mService;
     private final ArrayMap<TrustListener, ITrustListener> mTrustListeners;
@@ -95,14 +96,17 @@
         try {
             ITrustListener.Stub iTrustListener = new ITrustListener.Stub() {
                 @Override
-                public void onTrustChanged(boolean enabled, int userId) throws RemoteException {
-                    mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId,
-                            trustListener).sendToTarget();
+                public void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser) {
+                    Message m = mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId,
+                            trustListener);
+                    if (initiatedByUser) {
+                        m.getData().putBoolean(DATA_INITIATED_BY_USER, initiatedByUser);
+                    }
+                    m.sendToTarget();
                 }
 
                 @Override
-                public void onTrustManagedChanged(boolean managed, int userId)
-                        throws RemoteException {
+                public void onTrustManagedChanged(boolean managed, int userId) {
                     mHandler.obtainMessage(MSG_TRUST_MANAGED_CHANGED, (managed ? 1 : 0), userId,
                             trustListener).sendToTarget();
                 }
@@ -139,7 +143,11 @@
         public void handleMessage(Message msg) {
             switch(msg.what) {
                 case MSG_TRUST_CHANGED:
-                    ((TrustListener)msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2);
+                    boolean initiatedByUser = msg.peekData() != null &&
+                            msg.peekData().getBoolean(DATA_INITIATED_BY_USER);
+                    ((TrustListener)msg.obj).onTrustChanged(
+                            msg.arg1 != 0, msg.arg2, initiatedByUser);
+
                     break;
                 case MSG_TRUST_MANAGED_CHANGED:
                     ((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2);
@@ -153,8 +161,10 @@
          * Reports that the trust state has changed.
          * @param enabled if true, the system believes the environment to be trusted.
          * @param userId the user, for which the trust changed.
+         * @param initiatedByUser indicates that the user has explicitly initiated an action that
+         *                        proves the user is about to use the device.
          */
-        void onTrustChanged(boolean enabled, int userId);
+        void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser);
 
         /**
          * Reports that whether trust is managed has changed
diff --git a/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java b/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
index 0eb9d21..da992f5 100644
--- a/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
+++ b/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
@@ -124,8 +124,7 @@
     }
 
     @Override
-    public void onFoundOrLost(boolean onFound, String address, int rssi, byte[] advData)
-            throws RemoteException {
+    public void onFoundOrLost(boolean onFound, ScanResult scanResult) throws RemoteException {
     }
 
 }
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index edf823e..7070bae 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -21,6 +21,7 @@
 import android.bluetooth.le.AdvertiseData;
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
+import android.bluetooth.le.ResultStorageDescriptor;
 import android.os.ParcelUuid;
 
 import android.bluetooth.IBluetoothGattCallback;
@@ -34,7 +35,8 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
 
     void startScan(in int appIf, in boolean isServer, in ScanSettings settings,
-                   in List<ScanFilter> filters);
+                   in List<ScanFilter> filters,
+                   in List scanStorages);
     void stopScan(in int appIf, in boolean isServer);
     void flushPendingBatchResults(in int appIf, in boolean isServer);
     void startMultiAdvertising(in int appIf,
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index f14cce0..00b6b1b 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -69,6 +69,5 @@
                                   in AdvertiseSettings advertiseSettings);
     void onConfigureMTU(in String address, in int mtu, in int status);
     void onConnectionCongested(in String address, in boolean congested);
-    void onFoundOrLost(in boolean onFound, in String address, in int rssi,
-                             in byte[] advData);
+    void onFoundOrLost(in boolean onFound, in ScanResult scanResult);
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 45e466f..7c3cbc6 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -16,20 +16,19 @@
 
 package android.bluetooth.le;
 
+import android.annotation.SystemApi;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothGattCallbackWrapper;
 import android.bluetooth.IBluetoothGatt;
-import android.bluetooth.IBluetoothGattCallback;
 import android.bluetooth.IBluetoothManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -100,6 +99,11 @@
      */
     public void startScan(List<ScanFilter> filters, ScanSettings settings,
             final ScanCallback callback) {
+        startScan(filters, settings, callback, null);
+    }
+
+    private void startScan(List<ScanFilter> filters, ScanSettings settings,
+            final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages) {
         checkAdapterState();
         if (settings == null || callback == null) {
             throw new IllegalArgumentException("settings or callback is null");
@@ -125,7 +129,7 @@
                 return;
             }
             BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
-                    settings, callback);
+                    settings, callback, resultStorages);
             try {
                 UUID uuid = UUID.randomUUID();
                 gatt.registerClient(new ParcelUuid(uuid), wrapper);
@@ -155,7 +159,8 @@
         synchronized (mLeScanClients) {
             BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
             if (wrapper == null) {
-                if (DBG) Log.d(TAG, "could not find callback wrapper");
+                if (DBG)
+                    Log.d(TAG, "could not find callback wrapper");
                 return;
             }
             wrapper.stopLeScan();
@@ -185,6 +190,25 @@
     }
 
     /**
+     * Start truncated scan.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings,
+            final ScanCallback callback) {
+        int filterSize = truncatedFilters.size();
+        List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize);
+        List<List<ResultStorageDescriptor>> scanStorages =
+                new ArrayList<List<ResultStorageDescriptor>>(filterSize);
+        for (TruncatedFilter filter : truncatedFilters) {
+            scanFilters.add(filter.getFilter());
+            scanStorages.add(filter.getStorageDescriptors());
+        }
+        startScan(scanFilters, settings, callback, scanStorages);
+    }
+
+    /**
      * Bluetooth GATT interface callbacks
      */
     private static class BleScanCallbackWrapper extends BluetoothGattCallbackWrapper {
@@ -194,6 +218,7 @@
         private final List<ScanFilter> mFilters;
         private ScanSettings mSettings;
         private IBluetoothGatt mBluetoothGatt;
+        private List<List<ResultStorageDescriptor>> mResultStorages;
 
         // mLeHandle 0: not registered
         // -1: scan stopped
@@ -202,12 +227,13 @@
 
         public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
                 List<ScanFilter> filters, ScanSettings settings,
-                ScanCallback scanCallback) {
+                ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) {
             mBluetoothGatt = bluetoothGatt;
             mFilters = filters;
             mSettings = settings;
             mScanCallback = scanCallback;
             mClientIf = 0;
+            mResultStorages = resultStorages;
         }
 
         public boolean scanStarted() {
@@ -272,7 +298,8 @@
                 if (status == BluetoothGatt.GATT_SUCCESS) {
                     mClientIf = clientIf;
                     try {
-                        mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters);
+                        mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters,
+                                mResultStorages);
                     } catch (RemoteException e) {
                         Log.e(TAG, "fail to start le scan: " + e);
                         mClientIf = -1;
@@ -322,26 +349,34 @@
         }
 
         @Override
-        public void onFoundOrLost(boolean onFound, String address, int rssi,
-                byte[] advData) {
+        public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
             if (DBG) {
-                Log.d(TAG, "onFoundOrLost() - Device=" + address);
+                Log.d(TAG, "onFoundOrLost() - onFound = " + onFound +
+                        " " + scanResult.toString());
             }
-            // ToDo: Fix issue with underlying reporting from chipset
-            BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
-                    address);
-            long scanNanos = SystemClock.elapsedRealtimeNanos();
-            ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(advData), rssi,
-                    scanNanos);
-            if (onFound) {
-                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
-            } else {
-                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
+
+            // Check null in case the scan has been stopped
+            synchronized (this) {
+                if (mClientIf <= 0)
+                    return;
             }
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(new Runnable() {
+                    @Override
+                public void run() {
+                    if (onFound) {
+                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH,
+                                scanResult);
+                    } else {
+                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST,
+                                scanResult);
+                    }
+                }
+            });
         }
     }
 
-    //TODO: move this api to a common util class.
+    // TODO: move this api to a common util class.
     private void checkAdapterState() {
         if (mBluetoothAdapter.getState() != mBluetoothAdapter.STATE_ON) {
             throw new IllegalStateException("BT Adapter is not turned ON");
diff --git a/core/java/android/bluetooth/le/ResultStorageDescriptor.aidl b/core/java/android/bluetooth/le/ResultStorageDescriptor.aidl
new file mode 100644
index 0000000..f218a01
--- /dev/null
+++ b/core/java/android/bluetooth/le/ResultStorageDescriptor.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.bluetooth.le;
+
+/**
+ * {@hide}
+ */
+
+parcelable ResultStorageDescriptor;
diff --git a/core/java/android/bluetooth/le/ResultStorageDescriptor.java b/core/java/android/bluetooth/le/ResultStorageDescriptor.java
new file mode 100644
index 0000000..748f97d
--- /dev/null
+++ b/core/java/android/bluetooth/le/ResultStorageDescriptor.java
@@ -0,0 +1,93 @@
+/*
+ * 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.bluetooth.le;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the way to store scan result.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ResultStorageDescriptor implements Parcelable {
+    private int mType;
+    private int mOffset;
+    private int mLength;
+
+    public int getType() {
+        return mType;
+    }
+
+    public int getOffset() {
+        return mOffset;
+    }
+
+    public int getLength() {
+        return mLength;
+    }
+
+    /**
+     * Constructor of {@link ResultStorageDescriptor}
+     *
+     * @param type Type of the data.
+     * @param offset Offset from start of the advertise packet payload.
+     * @param length Byte length of the data
+     */
+    public ResultStorageDescriptor(int type, int offset, int length) {
+        mType = type;
+        mOffset = offset;
+        mLength = length;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mOffset);
+        dest.writeInt(mLength);
+    }
+
+    private ResultStorageDescriptor(Parcel in) {
+        ReadFromParcel(in);
+    }
+
+    private void ReadFromParcel(Parcel in) {
+        mType = in.readInt();
+        mOffset = in.readInt();
+        mLength = in.readInt();
+    }
+
+    public static final Parcelable.Creator<ResultStorageDescriptor>
+            CREATOR = new Creator<ResultStorageDescriptor>() {
+                    @Override
+                public ResultStorageDescriptor createFromParcel(Parcel source) {
+                    return new ResultStorageDescriptor(source);
+                }
+
+                    @Override
+                public ResultStorageDescriptor[] newArray(int size) {
+                    return new ResultStorageDescriptor[size];
+                }
+            };
+}
diff --git a/core/java/android/bluetooth/le/TruncatedFilter.java b/core/java/android/bluetooth/le/TruncatedFilter.java
new file mode 100644
index 0000000..6a6b3e3
--- /dev/null
+++ b/core/java/android/bluetooth/le/TruncatedFilter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.bluetooth.le;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * A special scan filter that lets the client decide how the scan record should be stored.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TruncatedFilter {
+    private final ScanFilter mFilter;
+    private final List<ResultStorageDescriptor> mStorageDescriptors;
+
+    /**
+     * Constructor for {@link TruncatedFilter}.
+     *
+     * @param filter Scan filter of the truncated filter.
+     * @param storageDescriptors Describes how the scan should be stored.
+     */
+    public TruncatedFilter(ScanFilter filter, List<ResultStorageDescriptor> storageDescriptors) {
+        mFilter = filter;
+        mStorageDescriptors = storageDescriptors;
+    }
+
+    /**
+     * Returns the scan filter.
+     */
+    public ScanFilter getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Returns a list of descriptor for scan result storage.
+     */
+    public List<ResultStorageDescriptor> getStorageDescriptors() {
+        return mStorageDescriptors;
+    }
+
+
+}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 87d14b9..0ca800f 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -329,7 +329,7 @@
 
         try {
             String type = ActivityManagerNative.getDefault().getProviderMimeType(
-                    url, UserHandle.myUserId());
+                    ContentProvider.getUriWithoutUserId(url), resolveUserId(url));
             return type;
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7417208..5f046c5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2616,6 +2616,7 @@
      *
      * @see #getSystemService
      */
+    @SystemApi
     public static final String BACKUP_SERVICE = "backup";
 
     /**
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 176a81c..5223476 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -21,7 +21,6 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
-import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
 interface IPackageInstaller {
@@ -37,4 +36,6 @@
 
     void uninstall(String packageName, int flags, in IPackageDeleteObserver2 observer, int userId);
     void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver2 observer, int userId);
+
+    void setPermissionsResult(int sessionId, boolean accepted);
 }
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
index f263885..161bcde 100644
--- a/core/java/android/content/pm/InstallSessionInfo.java
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -16,7 +16,6 @@
 
 package android.content.pm;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -33,8 +32,12 @@
     /** {@hide} */
     public String installerPackageName;
     /** {@hide} */
+    public String resolvedBaseCodePath;
+    /** {@hide} */
     public float progress;
     /** {@hide} */
+    public boolean sealed;
+    /** {@hide} */
     public boolean open;
 
     /** {@hide} */
@@ -56,7 +59,9 @@
     public InstallSessionInfo(Parcel source) {
         sessionId = source.readInt();
         installerPackageName = source.readString();
+        resolvedBaseCodePath = source.readString();
         progress = source.readFloat();
+        sealed = source.readInt() != 0;
         open = source.readInt() != 0;
 
         mode = source.readInt();
@@ -149,7 +154,9 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sessionId);
         dest.writeString(installerPackageName);
+        dest.writeString(resolvedBaseCodePath);
         dest.writeFloat(progress);
+        dest.writeInt(sealed ? 1 : 0);
         dest.writeInt(open ? 1 : 0);
 
         dest.writeInt(mode);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8c37e9e..268919c 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -210,20 +210,6 @@
      * Starts an activity in the specified profile.
      *
      * @param component The ComponentName of the activity to launch
-     * @param sourceBounds The Rect containing the source bounds of the clicked icon
-     * @param opts Options to pass to startActivity
-     * @param user The UserHandle of the profile
-     * @hide remove before ship
-     */
-    public void startActivityForProfile(ComponentName component, Rect sourceBounds,
-            Bundle opts, UserHandle user) {
-        startActivityForProfile(component, user, sourceBounds, opts);
-    }
-
-    /**
-     * Starts an activity in the specified profile.
-     *
-     * @param component The ComponentName of the activity to launch
      * @param user The UserHandle of the profile
      * @param sourceBounds The Rect containing the source bounds of the clicked icon
      * @param opts Options to pass to startActivity
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 01c080d..d70e22c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -81,6 +81,10 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
 
+    /** {@hide} */
+    public static final String
+            ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS";
+
     /**
      * An integer session ID.
      *
@@ -88,6 +92,9 @@
      */
     public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
 
+    /** {@hide} */
+    public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
+
     private final PackageManager mPm;
     private final IPackageInstaller mInstaller;
     private final int mUserId;
@@ -206,6 +213,15 @@
         }
     }
 
+    /** {@hide} */
+    public void setPermissionsResult(int sessionId, boolean accepted) {
+        try {
+            mInstaller.setPermissionsResult(sessionId, accepted);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     /**
      * Events for observing session lifecycle.
      * <p>
@@ -541,6 +557,26 @@
      */
     public static abstract class UninstallCallback {
         /**
+         * Generic unknown failure. The system will always try to provide a more
+         * specific failure reason, but in some rare cases this may be
+         * delivered.
+         */
+        public static final int FAILURE_UNKNOWN = 0;
+
+        /**
+         * This uninstall was blocked. The package may be required for core
+         * system operation, or the user may be restricted. Attempting to
+         * uninstall again will have the same result.
+         */
+        public static final int FAILURE_BLOCKED = 1;
+
+        /**
+         * This uninstall was actively aborted. For example, the user declined
+         * to uninstall. You may try to uninstall again.
+         */
+        public static final int FAILURE_ABORTED = 2;
+
+        /**
          * User action is required to proceed. You can start the given intent
          * activity to involve the user and continue.
          * <p>
@@ -551,7 +587,7 @@
         public abstract void onUserActionRequired(Intent intent);
 
         public abstract void onSuccess();
-        public abstract void onFailure(String msg);
+        public abstract void onFailure(int failureReason, String msg, Bundle extras);
     }
 
     /** {@hide} */
@@ -572,8 +608,9 @@
             if (returnCode == PackageManager.DELETE_SUCCEEDED) {
                 target.onSuccess();
             } else {
+                final int failureReason = PackageManager.deleteStatusToFailureReason(returnCode);
                 msg = PackageManager.deleteStatusToString(returnCode) + ": " + msg;
-                target.onFailure(msg);
+                target.onFailure(failureReason, msg, null);
             }
         }
     }
@@ -603,9 +640,8 @@
          * permission, incompatible certificates, etc. The user may be able to
          * uninstall another app to fix the issue.
          * <p>
-         * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} if one
-         * specific package was identified as the cause of the conflict. If
-         * unknown, or multiple packages, the extra may be {@code null}.
+         * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} with the
+         * specific packages identified as the cause of the conflict.
          */
         public static final int FAILURE_CONFLICT = 2;
 
@@ -626,6 +662,15 @@
          */
         public static final int FAILURE_INCOMPATIBLE = 4;
 
+        /**
+         * This install session failed because it was actively aborted. For
+         * example, the user declined requested permissions, or a verifier
+         * rejected the session.
+         *
+         * @see PackageManager#VERIFICATION_REJECT
+         */
+        public static final int FAILURE_ABORTED = 5;
+
         public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
 
         /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1e4ed31..b957a15 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -21,6 +21,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
+import android.app.PackageDeleteObserver;
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -28,6 +29,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller.CommitCallback;
+import android.content.pm.PackageInstaller.UninstallCallback;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -35,6 +37,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.AndroidException;
 
@@ -770,6 +773,9 @@
      */
     public static final int NO_NATIVE_LIBRARIES = -114;
 
+    /** {@hide} */
+    public static final int INSTALL_FAILED_ABORTED = -115;
+
     /**
      * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
      * package's data directory.
@@ -842,7 +848,10 @@
      *
      * @hide
      */
-    public static final int DELETE_FAILED_OWNER_BLOCKED= -4;
+    public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
+
+    /** {@hide} */
+    public static final int DELETE_FAILED_ABORTED = -5;
 
     /**
      * Return code that is passed to the {@link IPackageMoveObserver} by
@@ -3830,6 +3839,7 @@
             case INSTALL_FAILED_USER_RESTRICTED: return "INSTALL_FAILED_USER_RESTRICTED";
             case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION";
             case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS";
+            case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
             default: return Integer.toString(status);
         }
     }
@@ -3857,8 +3867,8 @@
             case INSTALL_FAILED_CONTAINER_ERROR: return CommitCallback.FAILURE_STORAGE;
             case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return CommitCallback.FAILURE_STORAGE;
             case INSTALL_FAILED_MEDIA_UNAVAILABLE: return CommitCallback.FAILURE_STORAGE;
-            case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_UNKNOWN;
-            case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_UNKNOWN;
+            case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_ABORTED;
+            case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_ABORTED;
             case INSTALL_FAILED_PACKAGE_CHANGED: return CommitCallback.FAILURE_INVALID;
             case INSTALL_FAILED_UID_CHANGED: return CommitCallback.FAILURE_INVALID;
             case INSTALL_FAILED_VERSION_DOWNGRADE: return CommitCallback.FAILURE_INVALID;
@@ -3876,6 +3886,7 @@
             case INSTALL_FAILED_USER_RESTRICTED: return CommitCallback.FAILURE_INCOMPATIBLE;
             case INSTALL_FAILED_DUPLICATE_PERMISSION: return CommitCallback.FAILURE_CONFLICT;
             case INSTALL_FAILED_NO_MATCHING_ABIS: return CommitCallback.FAILURE_INCOMPATIBLE;
+            case INSTALL_FAILED_ABORTED: return CommitCallback.FAILURE_ABORTED;
             default: return CommitCallback.FAILURE_UNKNOWN;
         }
     }
@@ -3888,7 +3899,57 @@
             case DELETE_FAILED_DEVICE_POLICY_MANAGER: return "DELETE_FAILED_DEVICE_POLICY_MANAGER";
             case DELETE_FAILED_USER_RESTRICTED: return "DELETE_FAILED_USER_RESTRICTED";
             case DELETE_FAILED_OWNER_BLOCKED: return "DELETE_FAILED_OWNER_BLOCKED";
+            case DELETE_FAILED_ABORTED: return "DELETE_FAILED_ABORTED";
             default: return Integer.toString(status);
         }
     }
+
+    /** {@hide} */
+    public static int deleteStatusToFailureReason(int status) {
+        switch (status) {
+            case DELETE_FAILED_INTERNAL_ERROR: return UninstallCallback.FAILURE_UNKNOWN;
+            case DELETE_FAILED_DEVICE_POLICY_MANAGER: return UninstallCallback.FAILURE_BLOCKED;
+            case DELETE_FAILED_USER_RESTRICTED: return UninstallCallback.FAILURE_BLOCKED;
+            case DELETE_FAILED_OWNER_BLOCKED: return UninstallCallback.FAILURE_BLOCKED;
+            case DELETE_FAILED_ABORTED: return UninstallCallback.FAILURE_ABORTED;
+            default: return UninstallCallback.FAILURE_UNKNOWN;
+        }
+    }
+
+    /** {@hide} */
+    public static class LegacyPackageInstallObserver extends PackageInstallObserver {
+        private final IPackageInstallObserver mLegacy;
+
+        public LegacyPackageInstallObserver(IPackageInstallObserver legacy) {
+            mLegacy = legacy;
+        }
+
+        @Override
+        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
+                Bundle extras) {
+            if (mLegacy == null) return;
+            try {
+                mLegacy.packageInstalled(basePackageName, returnCode);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** {@hide} */
+    public static class LegacyPackageDeleteObserver extends PackageDeleteObserver {
+        private final IPackageDeleteObserver mLegacy;
+
+        public LegacyPackageDeleteObserver(IPackageDeleteObserver legacy) {
+            mLegacy = legacy;
+        }
+
+        @Override
+        public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
+            if (mLegacy == null) return;
+            try {
+                mLegacy.packageDeleted(basePackageName, returnCode);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 900b41d..3c290f7 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -233,7 +233,7 @@
             }
 
             if (alphaRes != 0) {
-                alpha = r.getFraction(alphaRes, 1, 1);
+                alpha = r.getFloat(alphaRes);
             }
 
             // Apply alpha modulation.
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 5face69..52d1c79 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -986,6 +986,34 @@
     }
 
     /**
+     * Retrieve a floating-point value for a particular resource ID.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     *
+     * @return Returns the floating-point value contained in the resource.
+     *
+     * @throws NotFoundException Throws NotFoundException if the given ID does
+     *         not exist or is not a floating-point value.
+     * @hide Pending API council approval.
+     */
+    public float getFloat(int id) {
+        synchronized (mAccessLock) {
+            TypedValue value = mTmpValue;
+            if (value == null) {
+                mTmpValue = value = new TypedValue();
+            }
+            getValue(id, value, true);
+            if (value.type == TypedValue.TYPE_FLOAT) {
+                return value.getFloat();
+            }
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+                    + Integer.toHexString(value.type) + " is not valid");
+        }
+    }
+
+    /**
      * Return an XmlResourceParser through which you can read a view layout
      * description for the given resource ID.  This parser has limited
      * functionality -- in particular, you can't change its input, and only
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index c461511..6a9d565 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -130,15 +130,6 @@
     public abstract String getId();
 
     /**
-     * <p>Set up a new output set of Surfaces for the camera device.</p>
-     *
-     * @deprecated Use {@link #createCaptureSession} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract void configureOutputs(List<Surface> outputs) throws CameraAccessException;
-
-    /**
      * <p>Create a new camera capture session by providing the target output set of Surfaces to the
      * camera device.</p>
      *
@@ -276,68 +267,6 @@
             throws CameraAccessException;
 
     /**
-     * <p>Submit a request for an image to be captured by this CameraDevice.</p>
-     *
-     * @deprecated Use {@link CameraCaptureSession#capture} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int capture(CaptureRequest request, CaptureListener listener, Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * Submit a list of requests to be captured in sequence as a burst.
-     *
-     * @deprecated Use {@link CameraCaptureSession#captureBurst} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
-            Handler handler) throws CameraAccessException;
-
-    /**
-     * Request endlessly repeating capture of images by this CameraDevice.
-     *
-     * @deprecated Use {@link CameraCaptureSession#setRepeatingRequest} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
-            Handler handler) throws CameraAccessException;
-
-    /**
-     * <p>Request endlessly repeating capture of a sequence of images by this
-     * CameraDevice.</p>
-     *
-     * @deprecated Use {@link CameraCaptureSession#setRepeatingBurst} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
-            Handler handler) throws CameraAccessException;
-
-    /**
-     * <p>Cancel any ongoing repeating capture set by either
-     * {@link #setRepeatingRequest setRepeatingRequest} or
-     * {@link #setRepeatingBurst}.
-     *
-     * @deprecated Use {@link CameraCaptureSession#stopRepeating} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract void stopRepeating() throws CameraAccessException;
-
-    /**
-     * Flush all captures currently pending and in-progress as fast as
-     * possible.
-     *
-     * @deprecated Use {@link CameraCaptureSession#abortCaptures} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract void flush() throws CameraAccessException;
-
-    /**
      * Close the connection to this camera device as quickly as possible.
      *
      * <p>Immediately after this call, all calls to the camera device or active session interface
@@ -356,96 +285,6 @@
     public abstract void close();
 
     /**
-     * <p>A listener for tracking the progress of a {@link CaptureRequest}
-     * submitted to the camera device.</p>
-     *
-     * @deprecated Use {@link CameraCaptureSession.CaptureListener} instead
-     * @hide
-     */
-    @Deprecated
-    public static abstract class CaptureListener {
-
-        /**
-         * This constant is used to indicate that no images were captured for
-         * the request.
-         *
-         * @hide
-         */
-        public static final int NO_FRAMES_CAPTURED = -1;
-
-        /**
-         * This method is called when the camera device has started capturing
-         * the output image for the request, at the beginning of image exposure.
-         *
-         * @see android.media.MediaActionSound
-         */
-        public void onCaptureStarted(CameraDevice camera,
-                CaptureRequest request, long timestamp) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called when some results from an image capture are
-         * available.
-         *
-         * @hide
-         */
-        public void onCapturePartial(CameraDevice camera,
-                CaptureRequest request, CaptureResult result) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called when an image capture makes partial forward progress; some
-         * (but not all) results from an image capture are available.
-         *
-         */
-        public void onCaptureProgressed(CameraDevice camera,
-                CaptureRequest request, CaptureResult partialResult) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called when an image capture has fully completed and all the
-         * result metadata is available.
-         */
-        public void onCaptureCompleted(CameraDevice camera,
-                CaptureRequest request, TotalCaptureResult result) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called instead of {@link #onCaptureCompleted} when the
-         * camera device failed to produce a {@link CaptureResult} for the
-         * request.
-         */
-        public void onCaptureFailed(CameraDevice camera,
-                CaptureRequest request, CaptureFailure failure) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called independently of the others in CaptureListener,
-         * when a capture sequence finishes and all {@link CaptureResult}
-         * or {@link CaptureFailure} for it have been returned via this listener.
-         */
-        public void onCaptureSequenceCompleted(CameraDevice camera,
-                int sequenceId, long frameNumber) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called independently of the others in CaptureListener,
-         * when a capture sequence aborts before any {@link CaptureResult}
-         * or {@link CaptureFailure} for it have been returned via this listener.
-         */
-        public void onCaptureSequenceAborted(CameraDevice camera,
-                int sequenceId) {
-            // default empty implementation
-        }
-    }
-
-    /**
      * A listener for notifications about the state of a camera
      * device.
      *
@@ -542,40 +381,6 @@
         public abstract void onOpened(CameraDevice camera); // Must implement
 
         /**
-         * The method called when a camera device has no outputs configured.
-         *
-         * @deprecated Use {@link #onOpened} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onUnconfigured(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
-         * The method called when a camera device begins processing
-         * {@link CaptureRequest capture requests}.
-         *
-         * @deprecated Use {@link CameraCaptureSession.StateListener#onActive} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onActive(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
-         * The method called when a camera device is busy.
-         *
-         * @deprecated Use {@link CameraCaptureSession.StateListener#onConfigured} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onBusy(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
          * The method called when a camera device has been closed with
          * {@link CameraDevice#close}.
          *
@@ -591,18 +396,6 @@
         }
 
         /**
-         * The method called when a camera device has finished processing all
-         * submitted capture requests and has reached an idle state.
-         *
-         * @deprecated Use {@link CameraCaptureSession.StateListener#onReady} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onIdle(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
          * The method called when a camera device is no longer available for
          * use.
          *
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index f829f5e..a15028c 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -103,7 +103,7 @@
          * Use the same handler as the device's StateListener for all the internal coming events
          *
          * This ensures total ordering between CameraDevice.StateListener and
-         * CameraDevice.CaptureListener events.
+         * CameraDeviceImpl.CaptureListener events.
          */
         mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
                 /*name*/"seq");
@@ -141,7 +141,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             Log.v(TAG, "capture - request " + request + ", listener " + listener + " handler" +
@@ -164,7 +164,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
@@ -186,7 +186,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             Log.v(TAG, "setRepeatingRequest - request " + request + ", listener " + listener +
@@ -209,7 +209,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
@@ -261,9 +261,12 @@
      * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
      * <p>
      *
+     * <p>After this call completes, the session will not call any further methods on the camera
+     * device.</p>
+     *
      * @see CameraCaptureSession#close
      */
-    synchronized void replaceSessionClose(CameraCaptureSession other) {
+    synchronized void replaceSessionClose() {
         /*
          * In order for creating new sessions to be fast, the new session should be created
          * before the old session is closed.
@@ -278,13 +281,17 @@
 
         if (VERBOSE) Log.v(TAG, "replaceSessionClose");
 
-        // #close was already called explicitly, keep going the slow route
-        if (mClosed) {
-            if (VERBOSE) Log.v(TAG, "replaceSessionClose - close was already called");
-            return;
-        }
-
+        // Set up fast shutdown. Possible alternative paths:
+        // - This session is active, so close() below starts the shutdown drain
+        // - This session is mid-shutdown drain, and hasn't yet reached the idle drain listener.
+        // - This session is already closed and has executed the idle drain listener, and
+        //   configureOutputs(null) has already been called.
+        //
+        // Do not call configureOutputs(null) going forward, since it would race with the
+        // configuration for the new session. If it was already called, then we don't care, since it
+        // won't get called again.
         mSkipUnconfigure = true;
+
         close();
     }
 
@@ -347,7 +354,7 @@
 
     /**
      * Forward callbacks from
-     * CameraDevice.CaptureListener to the CameraCaptureSession.CaptureListener.
+     * CameraDeviceImpl.CaptureListener to the CameraCaptureSession.CaptureListener.
      *
      * <p>In particular, all calls are automatically split to go both to our own
      * internal listener, and to the user-specified listener (by transparently posting
@@ -356,9 +363,9 @@
      * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
      */
     @SuppressWarnings("deprecation")
-    private CameraDevice.CaptureListener createCaptureListenerProxy(
+    private CameraDeviceImpl.CaptureListener createCaptureListenerProxy(
             Handler handler, CaptureListener listener) {
-        CameraDevice.CaptureListener localListener = new CameraDevice.CaptureListener() {
+        CameraDeviceImpl.CaptureListener localListener = new CameraDeviceImpl.CaptureListener() {
             @Override
             public void onCaptureSequenceCompleted(CameraDevice camera,
                     int sequenceId, long frameNumber) {
@@ -379,27 +386,30 @@
          * - then forward the call to a handler
          * - then finally invoke the destination method on the session listener object
          */
-        Dispatchable<CaptureListener> userListenerSink;
-        if (listener == null) { // OK: API allows the user to not specify a listener
-            userListenerSink = new NullDispatcher<>();
-        } else {
-            userListenerSink = new InvokeDispatcher<>(listener);
+        if (listener == null) {
+            // OK: API allows the user to not specify a listener, and the handler may
+            // also be null in that case. Collapse whole dispatch chain to only call the local
+            // listener
+            return localListener;
         }
 
-        InvokeDispatcher<CameraDevice.CaptureListener> localSink =
+        InvokeDispatcher<CameraDeviceImpl.CaptureListener> localSink =
                 new InvokeDispatcher<>(localListener);
+
+        InvokeDispatcher<CaptureListener> userListenerSink =
+                new InvokeDispatcher<>(listener);
         HandlerDispatcher<CaptureListener> handlerPassthrough =
                 new HandlerDispatcher<>(userListenerSink, handler);
-        DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSession
+        DuckTypingDispatcher<CameraDeviceImpl.CaptureListener, CaptureListener> duckToSession
                 = new DuckTypingDispatcher<>(handlerPassthrough, CaptureListener.class);
-        ArgumentReplacingDispatcher<CameraDevice.CaptureListener, CameraCaptureSessionImpl>
-            replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
-                    /*argumentIndex*/0, this);
+        ArgumentReplacingDispatcher<CameraDeviceImpl.CaptureListener, CameraCaptureSessionImpl>
+                replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
+                        /*argumentIndex*/0, this);
 
-        BroadcastDispatcher<CameraDevice.CaptureListener> broadcaster =
-                new BroadcastDispatcher<CameraDevice.CaptureListener>(
-                        replaceDeviceWithSession,
-                        localSink);
+        BroadcastDispatcher<CameraDeviceImpl.CaptureListener> broadcaster =
+                new BroadcastDispatcher<CameraDeviceImpl.CaptureListener>(
+                    replaceDeviceWithSession,
+                    localSink);
 
         return new ListenerProxies.DeviceCaptureListenerProxy(broadcaster);
     }
@@ -415,10 +425,10 @@
      * </ul>
      * </p>
      * */
-    CameraDevice.StateListener getDeviceStateListener() {
+    CameraDeviceImpl.StateListenerKK getDeviceStateListener() {
         final CameraCaptureSession session = this;
 
-        return new CameraDevice.StateListener() {
+        return new CameraDeviceImpl.StateListenerKK() {
             private boolean mBusy = false;
             private boolean mActive = false;
 
@@ -596,6 +606,8 @@
                  *
                  * This operation is idempotent; a session will not be closed twice.
                  */
+                if (VERBOSE) Log.v(TAG, "Session drain complete, skip unconfigure: " +
+                        mSkipUnconfigure);
 
                 // Fast path: A new capture session has replaced this one; don't unconfigure.
                 if (mSkipUnconfigure) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 18b1202..71eb0e9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -21,8 +21,10 @@
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.TotalCaptureResult;
@@ -47,7 +49,7 @@
 /**
  * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
  */
-public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
+public class CameraDeviceImpl extends CameraDevice {
 
     private final String TAG;
     private final boolean DEBUG;
@@ -62,7 +64,7 @@
     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
 
     private final StateListener mDeviceListener;
-    private volatile StateListener mSessionStateListener;
+    private volatile StateListenerKK mSessionStateListener;
     private final Handler mDeviceHandler;
 
     private volatile boolean mClosing = false;
@@ -103,7 +105,7 @@
     private final Runnable mCallOnOpened = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -119,7 +121,7 @@
     private final Runnable mCallOnUnconfigured = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -128,14 +130,13 @@
             if (sessionListener != null) {
                 sessionListener.onUnconfigured(CameraDeviceImpl.this);
             }
-            mDeviceListener.onUnconfigured(CameraDeviceImpl.this);
         }
     };
 
     private final Runnable mCallOnActive = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -144,14 +145,13 @@
             if (sessionListener != null) {
                 sessionListener.onActive(CameraDeviceImpl.this);
             }
-            mDeviceListener.onActive(CameraDeviceImpl.this);
         }
     };
 
     private final Runnable mCallOnBusy = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -160,7 +160,6 @@
             if (sessionListener != null) {
                 sessionListener.onBusy(CameraDeviceImpl.this);
             }
-            mDeviceListener.onBusy(CameraDeviceImpl.this);
         }
     };
 
@@ -172,7 +171,7 @@
             if (mClosedOnce) {
                 throw new AssertionError("Don't post #onClosed more than once");
             }
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 sessionListener = mSessionStateListener;
             }
@@ -187,7 +186,7 @@
     private final Runnable mCallOnIdle = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -196,14 +195,13 @@
             if (sessionListener != null) {
                 sessionListener.onIdle(CameraDeviceImpl.this);
             }
-            mDeviceListener.onIdle(CameraDeviceImpl.this);
         }
     };
 
     private final Runnable mCallOnDisconnected = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -313,7 +311,6 @@
         return mCameraId;
     }
 
-    @Override
     public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
         // Treat a null input the same an empty list
         if (outputs == null) {
@@ -390,7 +387,11 @@
 
             checkIfCameraClosedOrInError();
 
-            // TODO: we must be in UNCONFIGURED mode to begin with, or using another session
+            // Notify current session that it's going away, before starting camera operations
+            // After this call completes, the session is not allowed to call into CameraDeviceImpl
+            if (mCurrentSession != null) {
+                mCurrentSession.replaceSessionClose();
+            }
 
             // TODO: dont block for this
             boolean configureSuccess = true;
@@ -410,10 +411,6 @@
                     new CameraCaptureSessionImpl(outputs, listener, handler, this, mDeviceHandler,
                             configureSuccess);
 
-            if (mCurrentSession != null) {
-                mCurrentSession.replaceSessionClose(newSession);
-            }
-
             // TODO: wait until current session closes, then create the new session
             mCurrentSession = newSession;
 
@@ -425,6 +422,15 @@
         }
     }
 
+    /**
+     * For use by backwards-compatibility code only.
+     */
+    public void setSessionListener(StateListenerKK sessionListener) {
+        synchronized(mInterfaceLock) {
+            mSessionStateListener = sessionListener;
+        }
+    }
+
     @Override
     public CaptureRequest.Builder createCaptureRequest(int templateType)
             throws CameraAccessException {
@@ -449,7 +455,6 @@
         }
     }
 
-    @Override
     public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
             throws CameraAccessException {
         if (DEBUG) {
@@ -460,7 +465,6 @@
         return submitCaptureRequest(requestList, listener, handler, /*streaming*/false);
     }
 
-    @Override
     public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException {
         if (requests == null || requests.isEmpty()) {
@@ -543,9 +547,7 @@
 
         // Need a valid handler, or current thread needs to have a looper, if
         // listener is valid
-        if (listener != null) {
-            handler = checkHandler(handler);
-        }
+        handler = checkHandler(handler, listener);
 
         // Make sure that there all requests have at least 1 surface; all surfaces are non-null
         for (CaptureRequest request : requestList) {
@@ -613,7 +615,6 @@
         }
     }
 
-    @Override
     public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
             Handler handler) throws CameraAccessException {
         List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
@@ -621,7 +622,6 @@
         return submitCaptureRequest(requestList, listener, handler, /*streaming*/true);
     }
 
-    @Override
     public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException {
         if (requests == null || requests.isEmpty()) {
@@ -630,7 +630,6 @@
         return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
     }
 
-    @Override
     public void stopRepeating() throws CameraAccessException {
 
         synchronized(mInterfaceLock) {
@@ -681,7 +680,6 @@
         }
     }
 
-    @Override
     public void flush() throws CameraAccessException {
         synchronized(mInterfaceLock) {
             checkIfCameraClosedOrInError();
@@ -739,6 +737,133 @@
         }
     }
 
+    /**
+     * <p>A listener for tracking the progress of a {@link CaptureRequest}
+     * submitted to the camera device.</p>
+     *
+     */
+    public static abstract class CaptureListener {
+
+        /**
+         * This constant is used to indicate that no images were captured for
+         * the request.
+         *
+         * @hide
+         */
+        public static final int NO_FRAMES_CAPTURED = -1;
+
+        /**
+         * This method is called when the camera device has started capturing
+         * the output image for the request, at the beginning of image exposure.
+         *
+         * @see android.media.MediaActionSound
+         */
+        public void onCaptureStarted(CameraDevice camera,
+                CaptureRequest request, long timestamp) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called when some results from an image capture are
+         * available.
+         *
+         * @hide
+         */
+        public void onCapturePartial(CameraDevice camera,
+                CaptureRequest request, CaptureResult result) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called when an image capture makes partial forward progress; some
+         * (but not all) results from an image capture are available.
+         *
+         */
+        public void onCaptureProgressed(CameraDevice camera,
+                CaptureRequest request, CaptureResult partialResult) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called when an image capture has fully completed and all the
+         * result metadata is available.
+         */
+        public void onCaptureCompleted(CameraDevice camera,
+                CaptureRequest request, TotalCaptureResult result) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called instead of {@link #onCaptureCompleted} when the
+         * camera device failed to produce a {@link CaptureResult} for the
+         * request.
+         */
+        public void onCaptureFailed(CameraDevice camera,
+                CaptureRequest request, CaptureFailure failure) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called independently of the others in CaptureListener,
+         * when a capture sequence finishes and all {@link CaptureResult}
+         * or {@link CaptureFailure} for it have been returned via this listener.
+         */
+        public void onCaptureSequenceCompleted(CameraDevice camera,
+                int sequenceId, long frameNumber) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called independently of the others in CaptureListener,
+         * when a capture sequence aborts before any {@link CaptureResult}
+         * or {@link CaptureFailure} for it have been returned via this listener.
+         */
+        public void onCaptureSequenceAborted(CameraDevice camera,
+                int sequenceId) {
+            // default empty implementation
+        }
+    }
+
+    /**
+     * A listener for notifications about the state of a camera device, adding in the callbacks that
+     * were part of the earlier KK API design, but now only used internally.
+     */
+    public static abstract class StateListenerKK extends StateListener {
+        /**
+         * The method called when a camera device has no outputs configured.
+         *
+         */
+        public void onUnconfigured(CameraDevice camera) {
+            // Default empty implementation
+        }
+
+        /**
+         * The method called when a camera device begins processing
+         * {@link CaptureRequest capture requests}.
+         *
+         */
+        public void onActive(CameraDevice camera) {
+            // Default empty implementation
+        }
+
+        /**
+         * The method called when a camera device is busy.
+         *
+         */
+        public void onBusy(CameraDevice camera) {
+            // Default empty implementation
+        }
+
+        /**
+         * The method called when a camera device has finished processing all
+         * submitted capture requests and has reached an idle state.
+         *
+         */
+        public void onIdle(CameraDevice camera) {
+            // Default empty implementation
+        }
+    }
+
     static class CaptureListenerHolder {
 
         private final boolean mRepeating;
@@ -1155,6 +1280,18 @@
         return handler;
     }
 
+    /**
+     * Default handler management, conditional on there being a listener.
+     *
+     * <p>If the listener isn't null, check the handler, otherwise pass it through.</p>
+     */
+    static <T> Handler checkHandler(Handler handler, T listener) {
+        if (listener != null) {
+            return checkHandler(handler);
+        }
+        return handler;
+    }
+
     private void checkIfCameraClosedOrInError() throws CameraAccessException {
         if (mInError) {
             throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index dc71a06..febb015 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -67,6 +67,7 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Implementation of camera metadata marshal/unmarshal across Binder to
@@ -227,6 +228,7 @@
 
     private static final String CELLID_PROCESS = "CELLID";
     private static final String GPS_PROCESS = "GPS";
+    private static final int FACE_LANDMARK_SIZE = 6;
 
     private static String translateLocationProviderToProcess(final String provider) {
         if (provider == null) {
@@ -347,7 +349,7 @@
         // Check if key has been overridden to use a wrapper class on the java side.
         GetCommand g = sGetCommandMap.get(key);
         if (g != null) {
-            return (T) g.getValue(this, key);
+            return g.getValue(this, key);
         }
         return getBase(key);
     }
@@ -587,9 +589,71 @@
         return availableFormats;
     }
 
-    private Face[] getFaces() {
-        final int FACE_LANDMARK_SIZE = 6;
+    private boolean setFaces(Face[] faces) {
+        if (faces == null) {
+            return false;
+        }
 
+        int numFaces = faces.length;
+
+        // Detect if all faces are SIMPLE or not; count # of valid faces
+        boolean fullMode = true;
+        for (Face face : faces) {
+            if (face == null) {
+                numFaces--;
+                Log.w(TAG, "setFaces - null face detected, skipping");
+                continue;
+            }
+
+            if (face.getId() == Face.ID_UNSUPPORTED) {
+                fullMode = false;
+            }
+        }
+
+        Rect[] faceRectangles = new Rect[numFaces];
+        byte[] faceScores = new byte[numFaces];
+        int[] faceIds = null;
+        int[] faceLandmarks = null;
+
+        if (fullMode) {
+            faceIds = new int[numFaces];
+            faceLandmarks = new int[numFaces * FACE_LANDMARK_SIZE];
+        }
+
+        int i = 0;
+        for (Face face : faces) {
+            if (face == null) {
+                continue;
+            }
+
+            faceRectangles[i] = face.getBounds();
+            faceScores[i] = (byte)face.getScore();
+
+            if (fullMode) {
+                faceIds[i] = face.getId();
+
+                int j = 0;
+
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getLeftEyePosition().x;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getLeftEyePosition().y;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getRightEyePosition().x;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getRightEyePosition().y;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getMouthPosition().x;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getMouthPosition().y;
+            }
+
+            i++;
+        }
+
+        set(CaptureResult.STATISTICS_FACE_RECTANGLES, faceRectangles);
+        set(CaptureResult.STATISTICS_FACE_IDS, faceIds);
+        set(CaptureResult.STATISTICS_FACE_LANDMARKS, faceLandmarks);
+        set(CaptureResult.STATISTICS_FACE_SCORES, faceScores);
+
+        return true;
+    }
+
+    private Face[] getFaces() {
         Integer faceDetectMode = get(CaptureResult.STATISTICS_FACE_DETECT_MODE);
         if (faceDetectMode == null) {
             Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
@@ -653,9 +717,12 @@
                 if (faceScores[i] <= Face.SCORE_MAX &&
                         faceScores[i] >= Face.SCORE_MIN &&
                         faceIds[i] >= 0) {
-                    Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]);
-                    Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]);
-                    Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]);
+                    Point leftEye = new Point(faceLandmarks[i*FACE_LANDMARK_SIZE],
+                            faceLandmarks[i*FACE_LANDMARK_SIZE+1]);
+                    Point rightEye = new Point(faceLandmarks[i*FACE_LANDMARK_SIZE+2],
+                            faceLandmarks[i*FACE_LANDMARK_SIZE+3]);
+                    Point mouth = new Point(faceLandmarks[i*FACE_LANDMARK_SIZE+4],
+                            faceLandmarks[i*FACE_LANDMARK_SIZE+5]);
                     Face face = new Face(faceRectangles[i], faceScores[i], faceIds[i],
                             leftEye, rightEye, mouth);
                     faceList.add(face);
@@ -865,6 +932,13 @@
                 metadata.setFaceRectangles((Rect[]) value);
             }
         });
+        sSetCommandMap.put(CaptureResult.STATISTICS_FACES.getNativeKey(),
+                new SetCommand() {
+            @Override
+            public <T> void setValue(CameraMetadataNative metadata, T value) {
+                metadata.setFaces((Face[])value);
+            }
+        });
         sSetCommandMap.put(CaptureRequest.TONEMAP_CURVE.getNativeKey(), new SetCommand() {
             @Override
             public <T> void setValue(CameraMetadataNative metadata, T value) {
diff --git a/core/java/android/hardware/camera2/impl/ListenerProxies.java b/core/java/android/hardware/camera2/impl/ListenerProxies.java
index ab9a4d5..f44f9ad 100644
--- a/core/java/android/hardware/camera2/impl/ListenerProxies.java
+++ b/core/java/android/hardware/camera2/impl/ListenerProxies.java
@@ -36,13 +36,13 @@
 
     // TODO: replace with codegen
 
-    public static class DeviceStateListenerProxy extends CameraDevice.StateListener {
-        private final MethodNameInvoker<CameraDevice.StateListener> mProxy;
+    public static class DeviceStateListenerProxy extends CameraDeviceImpl.StateListenerKK {
+        private final MethodNameInvoker<CameraDeviceImpl.StateListenerKK> mProxy;
 
         public DeviceStateListenerProxy(
-                Dispatchable<CameraDevice.StateListener> dispatchTarget) {
+                Dispatchable<CameraDeviceImpl.StateListenerKK> dispatchTarget) {
             dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
-            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.StateListener.class);
+            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDeviceImpl.StateListenerKK.class);
         }
 
         @Override
@@ -87,13 +87,13 @@
     }
 
     @SuppressWarnings("deprecation")
-    public static class DeviceCaptureListenerProxy extends CameraDevice.CaptureListener {
-        private final MethodNameInvoker<CameraDevice.CaptureListener> mProxy;
+    public static class DeviceCaptureListenerProxy extends CameraDeviceImpl.CaptureListener {
+        private final MethodNameInvoker<CameraDeviceImpl.CaptureListener> mProxy;
 
         public DeviceCaptureListenerProxy(
-                Dispatchable<CameraDevice.CaptureListener> dispatchTarget) {
+                Dispatchable<CameraDeviceImpl.CaptureListener> dispatchTarget) {
             dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
-            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.CaptureListener.class);
+            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDeviceImpl.CaptureListener.class);
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
new file mode 100644
index 0000000..1470b70
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
@@ -0,0 +1,231 @@
+/*
+ * 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.legacy;
+
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.hardware.Camera.FaceDetectionListener;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.utils.ListUtils;
+import android.hardware.camera2.utils.ParamsUtils;
+import android.util.Log;
+import android.util.Size;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.hardware.camera2.CaptureRequest.*;
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Map legacy face detect callbacks into face detection results.
+ */
+@SuppressWarnings("deprecation")
+public class LegacyFaceDetectMapper {
+    private static String TAG = "LegacyFaceDetectMapper";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private final Camera mCamera;
+    private final boolean mFaceDetectSupported;
+    private boolean mFaceDetectEnabled = false;
+
+    private final Object mLock = new Object();
+    private Camera.Face[] mFaces;
+    private Camera.Face[] mFacesPrev;
+    /**
+     * Instantiate a new face detect mapper.
+     *
+     * @param camera a non-{@code null} camera1 device
+     * @param characteristics a  non-{@code null} camera characteristics for that camera1
+     *
+     * @throws NullPointerException if any of the args were {@code null}
+     */
+    public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
+        mCamera = checkNotNull(camera, "camera must not be null");
+        checkNotNull(characteristics, "characteristics must not be null");
+
+        mFaceDetectSupported = ArrayUtils.contains(
+                characteristics.get(
+                        CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
+                STATISTICS_FACE_DETECT_MODE_SIMPLE);
+
+        if (!mFaceDetectSupported) {
+            return;
+        }
+
+       mCamera.setFaceDetectionListener(new FaceDetectionListener() {
+
+        @Override
+        public void onFaceDetection(Camera.Face[] faces, Camera camera) {
+            int lengthFaces = faces == null ? 0 : faces.length;
+            synchronized (mLock) {
+                if (mFaceDetectEnabled) {
+                    mFaces = faces;
+                } else if (lengthFaces > 0) {
+                    // stopFaceDetectMode could race against the requests, print a debug log
+                    Log.d(TAG,
+                            "onFaceDetection - Ignored some incoming faces since" +
+                            "face detection was disabled");
+                }
+            }
+
+            if (VERBOSE) {
+                Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
+            }
+        }
+       });
+    }
+
+    /**
+     * Process the face detect mode from the capture request into an api1 face detect toggle.
+     *
+     * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
+     * with the request.</p>
+     *
+     * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
+     * will have the latest faces detected as reflected by the camera1 callbacks.</p>
+     *
+     * <p>None of the arguments will be mutated.</p>
+     *
+     * @param captureRequest a non-{@code null} request
+     * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
+     */
+    public void processFaceDetectMode(CaptureRequest captureRequest,
+            Camera.Parameters parameters) {
+        checkNotNull(captureRequest, "captureRequest must not be null");
+
+        /*
+         * statistics.faceDetectMode
+         */
+        int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
+                STATISTICS_FACE_DETECT_MODE_OFF);
+
+        if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
+            Log.w(TAG,
+                    "processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
+                    "face detection is not available");
+            return;
+        }
+
+        // Print some warnings out in case the values were wrong
+        switch (fdMode) {
+            case STATISTICS_FACE_DETECT_MODE_OFF:
+            case STATISTICS_FACE_DETECT_MODE_SIMPLE:
+                break;
+            case STATISTICS_FACE_DETECT_MODE_FULL:
+                Log.w(TAG,
+                        "processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
+                        "downgrading to SIMPLE");
+                break;
+            default:
+                Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
+                        + fdMode);
+                return;
+        }
+
+        boolean enableFaceDetect = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
+        synchronized (mLock) {
+            // Enable/disable face detection if it's changed since last time
+            if (enableFaceDetect != mFaceDetectEnabled) {
+                if (enableFaceDetect) {
+                    mCamera.startFaceDetection();
+
+                    if (VERBOSE) {
+                        Log.v(TAG, "processFaceDetectMode - start face detection");
+                    }
+                } else {
+                    mCamera.stopFaceDetection();
+
+                    if (VERBOSE) {
+                        Log.v(TAG, "processFaceDetectMode - stop face detection");
+                    }
+
+                    mFaces = null;
+                }
+
+                mFaceDetectEnabled = enableFaceDetect;
+            }
+        }
+    }
+
+    /**
+     * Update the {@code result} camera metadata map with the new value for the
+     * {@code statistics.faces} and {@code statistics.faceDetectMode}.
+     *
+     * <p>Face detect callbacks are processed in the background, and each call to
+     * {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
+     *
+     * @param result a non-{@code null} result
+     * @param legacyRequest a non-{@code null} request (read-only)
+     */
+    public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
+        checkNotNull(result, "result must not be null");
+        checkNotNull(legacyRequest, "legacyRequest must not be null");
+
+        Camera.Face[] faces, previousFaces;
+        int fdMode;
+        synchronized (mLock) {
+            fdMode = mFaceDetectEnabled ?
+                            STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
+
+            if (mFaceDetectEnabled) {
+                faces = mFaces;
+            } else {
+                faces = null;
+            }
+
+            previousFaces = mFacesPrev;
+            mFacesPrev = faces;
+        }
+
+        CameraCharacteristics characteristics = legacyRequest.characteristics;
+        CaptureRequest request = legacyRequest.captureRequest;
+        Size previewSize = legacyRequest.previewSize;
+        Camera.Parameters params = legacyRequest.parameters;
+
+        Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
+                request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
+
+        List<Face> convertedFaces = new ArrayList<>();
+        if (faces != null) {
+            for (Camera.Face face : faces) {
+                if (face != null) {
+                    convertedFaces.add(
+                            ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
+                } else {
+                    Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
+                }
+            }
+        }
+
+        if (VERBOSE && previousFaces != faces) { // Log only in verbose and IF the faces changed
+            Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
+        }
+
+        result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
+        result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
+    }
+}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
index e576b43..d0a3a3f 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
@@ -247,7 +247,8 @@
                 // No action necessary. The callbacks will handle transitions.
                 break;
             default:
-                Log.w(TAG, "mapTriggers - ignoring unknown control.afTrigger = " + afTrigger);
+                Log.w(TAG, "processRequestTriggers - ignoring unknown control.afTrigger = "
+                        + afTrigger);
         }
     }
 
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 711edf4..b05508b 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -204,6 +204,11 @@
         mapSensor(m, p);
 
         /*
+         * statistics.*
+         */
+        mapStatistics(m, p);
+
+        /*
          * sync.*
          */
         mapSync(m, p);
@@ -487,6 +492,18 @@
 
     private static void mapControlOther(CameraMetadataNative m, Camera.Parameters p) {
         /*
+         * android.control.availableVideoStabilizationModes
+         */
+        {
+            int stabModes[] = p.isVideoStabilizationSupported() ?
+                    new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                                CONTROL_VIDEO_STABILIZATION_MODE_ON } :
+                    new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF };
+
+            m.set(CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, stabModes);
+        }
+
+        /*
          * android.control.maxRegions
          */
         final int AE = 0, AWB = 1, AF = 2;
@@ -742,6 +759,31 @@
         m.set(SENSOR_INFO_PIXEL_ARRAY_SIZE, largestJpegSize);
     }
 
+    private static void mapStatistics(CameraMetadataNative m, Parameters p) {
+        /*
+         * statistics.info.availableFaceDetectModes
+         */
+        int[] fdModes;
+
+        if (p.getMaxNumDetectedFaces() > 0) {
+            fdModes = new int[] {
+                STATISTICS_FACE_DETECT_MODE_OFF,
+                STATISTICS_FACE_DETECT_MODE_SIMPLE
+                // FULL is never-listed, since we have no way to query it statically
+            };
+        } else {
+            fdModes = new int[] {
+                STATISTICS_FACE_DETECT_MODE_OFF
+            };
+        }
+        m.set(STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, fdModes);
+
+        /*
+         * statistics.info.maxFaceCount
+         */
+        m.set(STATISTICS_INFO_MAX_FACE_COUNT, p.getMaxNumDetectedFaces());
+    }
+
     private static void mapSync(CameraMetadataNative m, Parameters p) {
         /*
          * sync.maxLatency
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index a6fe035..20f3fd2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -150,10 +150,8 @@
             if (supported) {
                 params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
                         legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
-                params.setRecordingHint(false);
             } else {
                 Log.w(TAG, "Unsupported FPS range set [" + legacyFps[0] + "," + legacyFps[1] + "]");
-                params.setRecordingHint(true);
             }
         }
 
@@ -248,6 +246,18 @@
          // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
         }
 
+        // control.videoStabilizationMode
+        {
+            Integer stabMode = getIfSupported(request, CONTROL_VIDEO_STABILIZATION_MODE,
+                    /*defaultValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                    params.isVideoStabilizationSupported(),
+                    /*allowedValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+
+            if (stabMode != null) {
+                params.setVideoStabilization(stabMode == CONTROL_VIDEO_STABILIZATION_MODE_ON);
+            }
+        }
+
         // lens.focusDistance
         {
             boolean infinityFocusSupported =
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index 9eff943..a2487f4 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -35,6 +35,9 @@
 import java.util.List;
 
 import static com.android.internal.util.Preconditions.*;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
+import static android.hardware.camera2.CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE;
 import static android.hardware.camera2.CaptureResult.*;
 
 /**
@@ -142,7 +145,6 @@
          */
         mapAwb(result, /*out*/params);
 
-
         /*
          * control.mode
          */
@@ -171,7 +173,6 @@
             }
         }
 
-
         /*
          * control.effectMode
          */
@@ -187,6 +188,15 @@
             }
         }
 
+        // control.videoStabilizationMode
+        {
+            int stabMode =
+                    (params.isVideoStabilizationSupported() && params.getVideoStabilization()) ?
+                        CONTROL_VIDEO_STABILIZATION_MODE_ON :
+                        CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+            result.set(CONTROL_VIDEO_STABILIZATION_MODE, stabMode);
+        }
+
         /*
          * flash
          */
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index efd12f2..385f844 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -43,6 +43,7 @@
 /**
  * Various utilities for dealing with camera API1 parameters.
  */
+@SuppressWarnings("deprecation")
 public class ParameterUtils {
     /** Upper/left minimal point of a normalized rectangle */
     public static final int NORMALIZED_RECTANGLE_MIN = -1000;
@@ -164,19 +165,23 @@
          * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
          * the score is clipped first and a warning is printed to logcat.</p>
          *
+         * <p>If the id is negative, the id is changed to 0 and a warning is printed to
+         * logcat.</p>
+         *
          * <p>All other parameters are passed-through as-is.</p>
          *
          * @return a new face with the optional features set
          */
         public Face toFace(
                 int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) {
+            int idSafe = clipLower(id, /*lo*/0, rect, "id");
             int score = clip(weight,
                     Face.SCORE_MIN,
                     Face.SCORE_MAX,
                     rect,
                     "score");
 
-            return new Face(rect, score, id, leftEyePosition, rightEyePosition, mouthPosition);
+            return new Face(rect, score, idSafe, leftEyePosition, rightEyePosition, mouthPosition);
         }
 
         /**
@@ -861,6 +866,61 @@
                 /*usePreviewCrop*/true);
     }
 
+    /**
+     * Convert an api1 face into an active-array based api2 face.
+     *
+     * <p>Out-of-ranges scores and ids will be clipped to be within range (with a warning).</p>
+     *
+     * @param face a non-{@code null} api1 face
+     * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+     * @param zoomData the calculated zoom data corresponding to this request
+     *
+     * @return a non-{@code null} api2 face
+     *
+     * @throws NullPointerException if the {@code face} was {@code null}
+     */
+    public static Face convertFaceFromLegacy(Camera.Face face, Rect activeArray,
+            ZoomData zoomData) {
+        checkNotNull(face, "face must not be null");
+
+        Face api2Face;
+
+        Camera.Area fakeArea = new Camera.Area(face.rect, /*weight*/1);
+
+        WeightedRectangle faceRect =
+                convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea);
+
+        Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth;
+        if (leftEye != null && rightEye != null && mouth != null) {
+            leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
+                    leftEye, /*usePreviewCrop*/true);
+            rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
+                    leftEye, /*usePreviewCrop*/true);
+            mouth = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
+                    leftEye, /*usePreviewCrop*/true);
+
+            api2Face = faceRect.toFace(face.id, leftEye, rightEye, mouth);
+        } else {
+            api2Face = faceRect.toFace();
+        }
+
+        return api2Face;
+    }
+
+    private static Point convertCameraPointToActiveArrayPoint(
+            Rect activeArray, ZoomData zoomData, Point point, boolean usePreviewCrop) {
+        Rect pointedRect = new Rect(point.x, point.y, point.x, point.y);
+        Camera.Area pointedArea = new Area(pointedRect, /*weight*/1);
+
+        WeightedRectangle adjustedRect =
+                convertCameraAreaToActiveArrayRectangle(activeArray,
+                        zoomData, pointedArea, usePreviewCrop);
+
+        Point transformedPoint = new Point(adjustedRect.rect.left, adjustedRect.rect.top);
+
+        return transformedPoint;
+    }
+
     private static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
             Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) {
         Rect previewCrop = zoomData.previewCrop;
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index c556c32..ec233da7 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -39,7 +39,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 import static com.android.internal.util.Preconditions.*;
@@ -55,18 +54,23 @@
  * - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
  * </p>
  */
+@SuppressWarnings("deprecation")
 public class RequestThreadManager {
     private final String TAG;
     private final int mCameraId;
     private final RequestHandlerThread mRequestThread;
 
     private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+    // For slightly more spammy messages that will get repeated every frame
+    private static final boolean VERBOSE =
+            Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.VERBOSE);
     private final Camera mCamera;
     private final CameraCharacteristics mCharacteristics;
 
     private final CameraDeviceState mDeviceState;
     private final CaptureCollector mCaptureCollector;
     private final LegacyFocusStateMapper mFocusStateMapper;
+    private final LegacyFaceDetectMapper mFaceDetectMapper;
 
     private static final int MSG_CONFIGURE_OUTPUTS = 1;
     private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
@@ -219,6 +223,9 @@
             };
 
     private void stopPreview() {
+        if (VERBOSE) {
+            Log.v(TAG, "stopPreview - preview running? " + mPreviewRunning);
+        }
         if (mPreviewRunning) {
             mCamera.stopPreview();
             mPreviewRunning = false;
@@ -226,14 +233,18 @@
     }
 
     private void startPreview() {
+        if (VERBOSE) {
+            Log.v(TAG, "startPreview - preview running? " + mPreviewRunning);
+        }
         if (!mPreviewRunning) {
+            // XX: CameraClient:;startPreview is not getting called after a stop
             mCamera.startPreview();
             mPreviewRunning = true;
         }
     }
 
-    private void doJpegCapture(RequestHolder request) throws IOException {
-        if (DEBUG) Log.d(TAG, "doJpegCapture");
+    private void doJpegCapturePrepare(RequestHolder request) throws IOException {
+        if (DEBUG) Log.d(TAG, "doJpegCapturePrepare - preview running? " + mPreviewRunning);
 
         if (!mPreviewRunning) {
             if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
@@ -242,11 +253,20 @@
             mCamera.setPreviewTexture(mDummyTexture);
             startPreview();
         }
+    }
+
+    private void doJpegCapture(RequestHolder request) {
+        if (DEBUG) Log.d(TAG, "doJpegCapturePrepare");
+
         mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
         mPreviewRunning = false;
     }
 
     private void doPreviewCapture(RequestHolder request) throws IOException {
+        if (VERBOSE) {
+            Log.v(TAG, "doPreviewCapture - preview running? " + mPreviewRunning);
+        }
+
         if (mPreviewRunning) {
             return; // Already running
         }
@@ -264,7 +284,20 @@
     }
 
     private void configureOutputs(Collection<Surface> outputs) throws IOException {
+        if (DEBUG) {
+            String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
+
+            Log.d(TAG, "configureOutputs with " + outputsStr);
+        }
+
         stopPreview();
+        /*
+         * Try to release the previous preview's surface texture earlier if we end up
+         * using a different one; this also reduces the likelihood of getting into a deadlock
+         * when disconnecting from the old previous texture at a later time.
+         */
+        mCamera.setPreviewTexture(/*surfaceTexture*/null);
+
         if (mGLThreadManager != null) {
             mGLThreadManager.waitUntilStarted();
             mGLThreadManager.ignoreNewFrames();
@@ -305,7 +338,6 @@
         }
         mParams.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
                 bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
-        mParams.setRecordingHint(true);
 
         if (mPreviewOutputs.size() > 0) {
             List<Size> outputSizes = new ArrayList<>(outputs.size());
@@ -575,7 +607,6 @@
                             Log.e(TAG, "Interrupted while waiting for requests to complete.");
                         }
                         mDeviceState.setIdle();
-                        stopPreview();
                         break;
                     } else {
                         // Queue another capture if we did not get the last burst.
@@ -613,10 +644,6 @@
                             }
                         }
 
-                        // Unconditionally process AF triggers, since they're non-idempotent
-                        // - must be done after setting the most-up-to-date AF mode
-                        mFocusStateMapper.processRequestTriggers(request, mParams);
-
                         try {
                             boolean success = mCaptureCollector.queueRequest(holder,
                                     mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
@@ -624,6 +651,8 @@
                             if (!success) {
                                 Log.e(TAG, "Timed out while queueing capture request.");
                             }
+                            // Starting the preview needs to happen before enabling
+                            // face detection or auto focus
                             if (holder.hasPreviewTargets()) {
                                 doPreviewCapture(holder);
                             }
@@ -635,12 +664,33 @@
                                     Log.e(TAG, "Timed out waiting for prior requests to complete.");
                                 }
                                 mReceivedJpeg.close();
+                                doJpegCapturePrepare(holder);
+                                if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
+                                    // TODO: report error to CameraDevice
+                                    Log.e(TAG, "Hit timeout for jpeg callback!");
+                                }
+                            }
+
+                            /*
+                             * Do all the actions that require a preview to have been started
+                             */
+
+                            // Toggle face detection on/off
+                            // - do this before AF to give AF a chance to use faces
+                            mFaceDetectMapper.processFaceDetectMode(request, /*in*/mParams);
+
+                            // Unconditionally process AF triggers, since they're non-idempotent
+                            // - must be done after setting the most-up-to-date AF mode
+                            mFocusStateMapper.processRequestTriggers(request, mParams);
+
+                            if (holder.hasJpegTargets()) {
                                 doJpegCapture(holder);
                                 if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
                                     // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for jpeg callback!");
                                 }
                             }
+
                         } catch (IOException e) {
                             // TODO: report error to CameraDevice
                             throw new IOError(e);
@@ -677,6 +727,8 @@
                                 mLastRequest, timestampMutable.value);
                         // Update AF state
                         mFocusStateMapper.mapResultTriggers(result);
+                        // Update detected faces list
+                        mFaceDetectMapper.mapResultFaces(result, mLastRequest);
 
                         mDeviceState.setCaptureResult(holder, result);
                     }
@@ -731,6 +783,7 @@
         TAG = name;
         mDeviceState = checkNotNull(deviceState, "deviceState must not be null");
         mFocusStateMapper = new LegacyFocusStateMapper(mCamera);
+        mFaceDetectMapper = new LegacyFaceDetectMapper(mCamera, mCharacteristics);
         mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState);
         mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
     }
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 1efabb1..2e6b9ae 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -813,6 +813,7 @@
         switch (format) {
             case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
             case HAL_PIXEL_FORMAT_BLOB:
+            case HAL_PIXEL_FORMAT_RAW_OPAQUE:
                 return format;
             case ImageFormat.JPEG:
                 throw new IllegalArgumentException(
@@ -843,12 +844,6 @@
      * @throws IllegalArgumentException if the format was not user-defined
      */
     static int checkArgumentFormat(int format) {
-        // TODO: remove this hack , CTS shouldn't have been using internal constants
-        if (format == HAL_PIXEL_FORMAT_RAW_OPAQUE) {
-            Log.w(TAG, "RAW_OPAQUE is not yet a published format; allowing it anyway");
-            return format;
-        }
-
         if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
             throw new IllegalArgumentException(String.format(
                     "format 0x%x was not defined in either ImageFormat or PixelFormat", format));
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index d4e6df5..51b7229 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -96,11 +96,9 @@
      * windows on the display and the system may mirror the contents of other displays
      * onto it.
      * </p><p>
-     * Creating a public virtual display requires the
-     * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}
-     * or {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} permission.
-     * These permissions are reserved for use by system components and are not available to
-     * third-party applications.
+     * Creating a public virtual display that isn't restricted to own-content only implicitly
+     * creates an auto-mirroring display. See {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for
+     * restrictions on who is allowed to create an auto-mirroring display.
      * </p>
      *
      * <h3>Private virtual displays</h3>
@@ -108,6 +106,8 @@
      * When this flag is not set, the virtual display is private as defined by the
      * {@link Display#FLAG_PRIVATE} display flag.
      * </p>
+     *
+     * <p>
      * A private virtual display belongs to the application that created it.
      * Only the a owner of a private virtual display is allowed to place windows upon it.
      * The private virtual display also does not participate in display mirroring: it will
@@ -115,10 +115,11 @@
      * be mirrored elsewhere.  More precisely, the only processes that are allowed to
      * enumerate or interact with the private display are those that have the same UID as the
      * application that originally created the private virtual display.
-      * </p>
+     * </p>
      *
      * @see #createVirtualDisplay
      * @see #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+     * @see #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
      */
     public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1 << 0;
 
@@ -187,29 +188,51 @@
      * will be blanked instead if it has no windows.
      * </p>
      *
+     * <p>
+     * This flag is mutually exclusive with {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.  If both
+     * flags are specified then the own-content only behavior will be applied.
+     * </p>
+     *
+     * <p>
+     * This behavior of this flag is implied whenever neither {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}
+     * nor {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} have been set.  This flag is only required to
+     * override the default behavior when creating a public display.
+     * </p>
+     *
      * @see #createVirtualDisplay
      */
     public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 1 << 3;
 
 
     /**
-     * Virtual display flag: Indicates that the display is being created for
-     * the purpose of screen sharing.  This implies
-     * VIRTUAL_DISPLAY_FLAG_PRIVATE.  Other flags are not allowed (especially
-     * not VIRTUAL_DISPLAY_FLAG_PUBLIC or PRESENTATION).
+     * Virtual display flag: Allows content to be mirrored on private displays when no content is
+     * being shown.
      *
      * <p>
-     * Requires screen share permission for use.
+     * This flag is mutually exclusive with {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+     * If both flags are specified then the own-content only behavior will be applied.
      * </p>
      *
      * <p>
-     * While a display of this type exists, the system will show some sort of
-     * notification to the user indicating that the screen is being shared.
+     * The behavior of this flag is implied whenever {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC} is set
+     * and {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY} has not been set.   This flag is only
+     * required to override the default behavior when creating a private display.
+     * </p>
+     *
+     * <p>
+     * Creating an auto-mirroing virtual display requires the
+     * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}
+     * or {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} permission.
+     * These permissions are reserved for use by system components and are not available to
+     * third-party applications.
+     *
+     * Alternatively, an appropriate {@link MediaProjection} may be used to create an
+     * auto-mirroring virtual display.
      * </p>
      *
      * @see #createVirtualDisplay
      */
-    public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 1 << 4;
+    public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1 << 4;
 
     /** @hide */
     public DisplayManager(Context context) {
@@ -489,7 +512,7 @@
      * @param flags A combination of virtual display flags:
      * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
      * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
-     * or {@link #VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE}.
+     * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
      * @param callbacks Callbacks to call when the state of the {@link VirtualDisplay} changes
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index f279668..2f6dbe7 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -34,4 +34,12 @@
      * @param status The error code that was seen.
      */
     void onError(int status);
+    /**
+     * Called when the recognition is paused temporarily for some reason.
+     */
+    void onRecognitionPaused();
+    /**
+     * Called when the recognition is resumed after it was temporarily paused.
+     */
+    void onRecognitionResumed();
 }
\ No newline at end of file
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 786439e..3f18519 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
 import android.app.DownloadManager;
 import android.app.backup.BackupManager;
 import android.content.Context;
@@ -127,6 +128,16 @@
     }
 
     /**
+     * System API for backup-related support components to tag network traffic
+     * appropriately.
+     * @hide
+     */
+    @SystemApi
+    public static void setThreadStatsTagBackup() {
+        setThreadStatsTag(TAG_SYSTEM_BACKUP);
+    }
+
+    /**
      * Get the active tag used when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * {@link #tagSocket(Socket)}.
@@ -160,11 +171,13 @@
      *
      * @hide
      */
+    @SystemApi
     public static void setThreadStatsUid(int uid) {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
     }
 
     /** {@hide} */
+    @SystemApi
     public static void clearThreadStatsUid() {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
     }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 545b19a..7d086e1 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -33,6 +33,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.view.Display;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
@@ -158,7 +159,7 @@
     private static final long BYTES_PER_MB = 1048576; // 1024^2
     private static final long BYTES_PER_GB = 1073741824; //1024^3
     
-
+    private static final String VERSION_DATA = "vers";
     private static final String UID_DATA = "uid";
     private static final String APK_DATA = "apk";
     private static final String PROCESS_DATA = "pr";
@@ -1462,6 +1463,21 @@
     public abstract long getStartClockTime();
 
     /**
+     * Return platform version tag that we were running in when the battery stats started.
+     */
+    public abstract String getStartPlatformVersion();
+
+    /**
+     * Return platform version tag that we were running in when the battery stats ended.
+     */
+    public abstract String getEndPlatformVersion();
+
+    /**
+     * Return the internal version code of the parcelled format.
+     */
+    public abstract int getParcelVersion();
+
+    /**
      * Return whether we are currently running on battery.
      */
     public abstract boolean getIsOnBattery();
@@ -1596,6 +1612,27 @@
      */
     public abstract long computeBatteryTimeRemaining(long curTime);
 
+    // The part of a step duration that is the actual time.
+    public static final long STEP_LEVEL_TIME_MASK = 0x000000ffffffffffL;
+
+    // Bits in a step duration that are the new battery level we are at.
+    public static final long STEP_LEVEL_LEVEL_MASK = 0x0000ff0000000000L;
+    public static final long STEP_LEVEL_LEVEL_SHIFT = 40;
+
+    // Bits in a step duration that are the initial mode we were in at that step.
+    public static final long STEP_LEVEL_INITIAL_MODE_MASK = 0x00ff000000000000L;
+    public static final long STEP_LEVEL_INITIAL_MODE_SHIFT = 48;
+
+    // Bits in a step duration that indicate which modes changed during that step.
+    public static final long STEP_LEVEL_MODIFIED_MODE_MASK = 0xff00000000000000L;
+    public static final long STEP_LEVEL_MODIFIED_MODE_SHIFT = 56;
+
+    // Step duration mode: the screen is on, off, dozed, etc; value is Display.STATE_* - 1.
+    public static final int STEP_LEVEL_MODE_SCREEN_STATE = 0x03;
+
+    // Step duration mode: power save is on.
+    public static final int STEP_LEVEL_MODE_POWER_SAVE = 0x04;
+
     /**
      * Return the historical number of discharge steps we currently have.
      */
@@ -1986,7 +2023,8 @@
         } else {
             dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
                     getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
-                    getDischargeAmountScreenOn(), getDischargeAmountScreenOff());
+                    getDischargeAmountScreenOnSinceCharge(),
+                    getDischargeAmountScreenOffSinceCharge());
         }
         
         if (reqUid < 0) {
@@ -3620,14 +3658,60 @@
         if (!checkin) {
             pw.println(header);
         }
-        String[] lineArgs = new String[1];
+        String[] lineArgs = new String[4];
         for (int i=0; i<count; i++) {
+            long duration = steps[i] & STEP_LEVEL_TIME_MASK;
+            int level = (int)((steps[i] & STEP_LEVEL_LEVEL_MASK)
+                    >> STEP_LEVEL_LEVEL_SHIFT);
+            long initMode = (steps[i] & STEP_LEVEL_INITIAL_MODE_MASK)
+                    >> STEP_LEVEL_INITIAL_MODE_SHIFT;
+            long modMode = (steps[i] & STEP_LEVEL_MODIFIED_MODE_MASK)
+                    >> STEP_LEVEL_MODIFIED_MODE_SHIFT;
             if (checkin) {
-                lineArgs[0] = Long.toString(steps[i]);
+                lineArgs[0] = Long.toString(duration);
+                lineArgs[1] = Integer.toString(level);
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: lineArgs[2] = "s-"; break;
+                        case Display.STATE_ON: lineArgs[2] = "s+"; break;
+                        case Display.STATE_DOZE: lineArgs[2] = "sd"; break;
+                        case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break;
+                        default: lineArgs[1] = "?"; break;
+                    }
+                } else {
+                    lineArgs[2] = "";
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    lineArgs[3] = (initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0 ? "p+" : "p-";
+                } else {
+                    lineArgs[3] = "";
+                }
                 dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
             } else {
                 pw.print("  #"); pw.print(i); pw.print(": ");
-                TimeUtils.formatDuration(steps[i], pw);
+                TimeUtils.formatDuration(duration, pw);
+                pw.print(" to "); pw.print(level);
+                boolean haveModes = false;
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    pw.print(" (");
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: pw.print("screen-off"); break;
+                        case Display.STATE_ON: pw.print("screen-on"); break;
+                        case Display.STATE_DOZE: pw.print("screen-doze"); break;
+                        case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break;
+                        default: lineArgs[1] = "screen-?"; break;
+                    }
+                    haveModes = true;
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    pw.print(haveModes ? ", " : " (");
+                    pw.print((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0
+                            ? "power-save-on" : "power-save-off");
+                    haveModes = true;
+                }
+                if (haveModes) {
+                    pw.print(")");
+                }
                 pw.println();
             }
         }
@@ -3808,6 +3892,9 @@
             if (didPid) {
                 pw.println();
             }
+        }
+
+        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
             if (dumpDurationSteps(pw, "Discharge step durations:", getDischargeStepDurationsArray(),
                     getNumDischargeStepDurations(), false)) {
                 long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
@@ -3828,9 +3915,6 @@
                 }
                 pw.println();
             }
-        }
-
-        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
             pw.println("Statistics since last charge:");
             pw.println("  System starts: " + getStartCount()
                     + ", currently on battery: " + getIsOnBattery());
@@ -3847,7 +3931,10 @@
     public void dumpCheckinLocked(Context context, PrintWriter pw,
             List<ApplicationInfo> apps, int flags, long histStart) {
         prepareForDumpLocked();
-        
+
+        dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA,
+                "10", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion());
+
         long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
 
         final boolean filtering =
@@ -3908,7 +3995,7 @@
                 }
             }
         }
-        if (!filtering) {
+        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
             dumpDurationSteps(pw, DISCHARGE_STEP_DATA, getDischargeStepDurationsArray(),
                     getNumDischargeStepDurations(), true);
             String[] lineArgs = new String[1];
@@ -3926,8 +4013,6 @@
                 dumpLine(pw, 0 /* uid */, "i" /* category */, CHARGE_TIME_REMAIN_DATA,
                         (Object[])lineArgs);
             }
-        }
-        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
             dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1);
         }
         if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c43644a..713fcd8 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -18,7 +18,6 @@
 package android.os;
 
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
 import android.content.pm.UserInfo;
 import android.content.RestrictionEntry;
 import android.graphics.Bitmap;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index afbf983..5ce9d5c 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.SystemApi;
 import android.util.SparseArray;
 
 import java.io.PrintWriter;
@@ -237,6 +238,16 @@
         return getUserId(Process.myUid());
     }
 
+    /**
+     * Returns true if this UserHandle refers to the owner user; false otherwise.
+     * @return true if this UserHandle refers to the owner user; false otherwise.
+     * @hide
+     */
+    @SystemApi
+    public final boolean isOwner() {
+        return this.equals(OWNER);
+    }
+
     /** @hide */
     public UserHandle(int h) {
         mHandle = h;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 04e6227..3087506 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -864,15 +864,6 @@
     }
 
     /**
-     * Kept during L development to simplify updating unbundled apps.
-     * TODO: Remove after 2014-08-04
-     * @hide
-     */
-    public String getBadgedLabelForUser(String label, UserHandle user) {
-        return (String) getBadgedLabelForUser((CharSequence) label, user);
-    }
-
-    /**
      * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a drawable to use as a small
      * icon to include in a view to distinguish it from the original icon.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index de1df56..733ab32 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2375,6 +2375,16 @@
         public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
 
         /**
+         * Whether automatic routing of system audio to USB audio peripheral is disabled.
+         * The value is boolean (1 or 0), where 1 means automatic routing is disabled,
+         * and 0 means automatic routing is enabled.
+         *
+         * @hide
+         */
+        public static final String USB_AUDIO_AUTOMATIC_ROUTING_DISABLED =
+                "usb_audio_automatic_routing_disabled";
+
+        /**
          * Whether the audible DTMF tones are played by the dialer when dialing. The value is
          * boolean (1 or 0).
          */
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index b0ff947..4860b44 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -161,6 +161,8 @@
     private static final int MSG_AVAILABILITY_CHANGED = 1;
     private static final int MSG_HOTWORD_DETECTED = 2;
     private static final int MSG_DETECTION_ERROR = 3;
+    private static final int MSG_DETECTION_PAUSE = 4;
+    private static final int MSG_DETECTION_RESUME = 5;
 
     private final String mText;
     private final String mLocale;
@@ -180,21 +182,27 @@
     private int mAvailability = STATE_NOT_READY;
 
     /**
-     * Details of the audio that triggered the keyphrase.
+     * Additional payload for {@link Callback#onDetected}.
      */
-    public static class TriggerAudio {
+    public static class EventPayload {
         /**
-         * Format of {@code data}.
+         * Indicates if {@code data} is the audio that triggered the keyphrase.
          */
-        @NonNull
+        public final boolean isTriggerAudio;
+        /**
+         * Format of {@code data}. May be null if {@code isTriggerAudio} is false.
+         */
+        @Nullable
         public final AudioFormat audioFormat;
         /**
-         * Raw audio data that triggered they keyphrase.
+         * Raw data associated with the event.
+         * This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
          */
-        @NonNull
+        @Nullable
         public final byte[] data;
 
-        private TriggerAudio(AudioFormat _audioFormat, byte[] _data) {
+        private EventPayload(boolean _isTriggerAudio, AudioFormat _audioFormat, byte[] _data) {
+            isTriggerAudio = _isTriggerAudio;
             audioFormat = _audioFormat;
             data = _data;
         }
@@ -224,14 +232,27 @@
          * Clients should start a recognition again once they are done handling this
          * detection.
          *
-         * @param triggerAudio Optional trigger audio data, if it was requested during
+         * @param eventPayload Payload data for the detection event.
+         *        This may contain the trigger audio, if requested when calling
          *        {@link AlwaysOnHotwordDetector#startRecognition(int)}.
          */
-        void onDetected(@Nullable TriggerAudio triggerAudio);
+        void onDetected(@NonNull EventPayload eventPayload);
         /**
          * Called when the detection fails due to an error.
          */
         void onError();
+        /**
+         * Called when the recognition is paused temporarily for some reason.
+         * This is an informational callback, and the clients shouldn't be doing anything here
+         * except showing an indication on their UI if they have to.
+         */
+        void onRecognitionPaused();
+        /**
+         * Called when the recognition is resumed after it was temporarily paused.
+         * This is an informational callback, and the clients shouldn't be doing anything here
+         * except showing an indication on their UI if they have to.
+         */
+        void onRecognitionResumed();
     }
 
     /**
@@ -487,22 +508,13 @@
         @Override
         public void onDetected(KeyphraseRecognitionEvent event) {
             if (DBG) {
-                Slog.d(TAG, "OnDetected(" + event + ")");
+                Slog.d(TAG, "onDetected(" + event + ")");
             } else {
                 Slog.i(TAG, "onDetected");
             }
-            Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED);
-            // FIXME: Check whether the event contains trigger data or not.
-            // FIXME: Read the audio format from the event.
-            if (event.data != null) {
-                AudioFormat audioFormat = new AudioFormat.Builder()
-                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                        .setSampleRate(16000)
-                        .build();
-                message.obj = new TriggerAudio(audioFormat, event.data);
-            }
-            message.sendToTarget();
+            Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
+                    new EventPayload(event.triggerInData, event.captureFormat, event.data))
+                    .sendToTarget();
         }
 
         @Override
@@ -510,6 +522,18 @@
             Slog.i(TAG, "onError: " + status);
             mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
         }
+
+        @Override
+        public void onRecognitionPaused() {
+            Slog.i(TAG, "onRecognitionPaused");
+            mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
+        }
+
+        @Override
+        public void onRecognitionResumed() {
+            Slog.i(TAG, "onRecognitionResumed");
+            mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+        }
     }
 
     class MyHandler extends Handler {
@@ -527,11 +551,17 @@
                     mExternalCallback.onAvailabilityChanged(msg.arg1);
                     break;
                 case MSG_HOTWORD_DETECTED:
-                    mExternalCallback.onDetected((TriggerAudio) msg.obj);
+                    mExternalCallback.onDetected((EventPayload) msg.obj);
                     break;
                 case MSG_DETECTION_ERROR:
                     mExternalCallback.onError();
                     break;
+                case MSG_DETECTION_PAUSE:
+                    mExternalCallback.onRecognitionPaused();
+                    break;
+                case MSG_DETECTION_RESUME:
+                    mExternalCallback.onRecognitionResumed();
+                    break;
                 default:
                     super.handleMessage(msg);
             }
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index e3e328c..19d14bf 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -448,6 +448,7 @@
     }
 
     void doDestroy() {
+        onDestroy();
         if (mInitialized) {
             mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                     mInsetsComputer);
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 910f862..a410aa9 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -386,9 +386,6 @@
 
     @Override
     public void scale(float sx, float sy) {
-        // TODO: remove
-        if (sx > 1000000 || sy > 1000000) throw new IllegalArgumentException("invalid scales passed " + sx + ", " + sy);
-
         nScale(mRenderer, sx, sy);
     }
 
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index eee4973..099f153 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -598,9 +598,6 @@
      * @see #getScaleX()
      */
     public boolean setScaleX(float scaleX) {
-        if (scaleX > 1000000) {
-            throw new IllegalArgumentException("Invalid scale: " + scaleX);
-        }
         return nSetScaleX(mNativeRenderNode, scaleX);
     }
 
@@ -622,9 +619,6 @@
      * @see #getScaleY()
      */
     public boolean setScaleY(float scaleY) {
-        if (scaleY > 1000000) {
-            throw new IllegalArgumentException("Invalid scale: " + scaleY);
-        }
         return nSetScaleY(mNativeRenderNode, scaleY);
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 328d67c..8c2048d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4649,16 +4649,18 @@
      *         otherwise is returned.
      */
     public boolean performClick() {
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
-
-        ListenerInfo li = mListenerInfo;
+        final boolean result;
+        final ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnClickListener != null) {
             playSoundEffect(SoundEffectConstants.CLICK);
             li.mOnClickListener.onClick(this);
-            return true;
+            result = true;
+        } else {
+            result = false;
         }
 
-        return false;
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+        return result;
     }
 
     /**
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 1832cc3..571a8f0 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -266,7 +266,6 @@
      * {@link View#fitSystemWindows(android.graphics.Rect)}.</p>
      *
      * @return true if the insets have been fully consumed.
-     * @hide Pending API
      */
     public boolean isConsumed() {
         return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index d93ca2c..23894ee 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -175,7 +175,9 @@
         String[] nativePaths = null;
         try {
             nativePaths = getWebViewNativeLibraryPaths();
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Log.e(LOGTAG, "error preparing webview native library", t);
         }
         prepareWebViewInSystemServer(nativePaths);
     }
@@ -201,35 +203,37 @@
         String[] nativeLibs = null;
         try {
             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
-        } catch (PackageManager.NameNotFoundException e) {
-        }
+            if (nativeLibs != null) {
+                long newVmSize = 0L;
 
-        if (nativeLibs != null) {
-            long newVmSize = 0L;
-
-            for (String path : nativeLibs) {
-                if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
-                if (path == null) continue;
-                File f = new File(path);
-                if (f.exists()) {
-                    long length = f.length();
-                    if (length > newVmSize) {
-                        newVmSize = length;
+                for (String path : nativeLibs) {
+                    if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
+                    if (path == null) continue;
+                    File f = new File(path);
+                    if (f.exists()) {
+                        long length = f.length();
+                        if (length > newVmSize) {
+                            newVmSize = length;
+                        }
                     }
                 }
-            }
 
-            if (DEBUG) {
-                Log.v(LOGTAG, "Based on library size, need " + newVmSize +
-                        " bytes of address space.");
+                if (DEBUG) {
+                    Log.v(LOGTAG, "Based on library size, need " + newVmSize +
+                            " bytes of address space.");
+                }
+                // The required memory can be larger than the file on disk (due to .bss), and an
+                // upgraded version of the library will likely be larger, so always attempt to
+                // reserve twice as much as we think to allow for the library to grow during this
+                // boot cycle.
+                newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+                Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+                SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
+                        Long.toString(newVmSize));
             }
-            // The required memory can be larger than the file on disk (due to .bss), and an
-            // upgraded version of the library will likely be larger, so always attempt to reserve
-            // twice as much as we think to allow for the library to grow during this boot cycle.
-            newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
-            Log.d(LOGTAG, "Setting new address space to " + newVmSize);
-            SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
-                    Long.toString(newVmSize));
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Log.e(LOGTAG, "error preparing webview native library", t);
         }
         prepareWebViewInSystemServer(nativeLibs);
     }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index aa0b94f..60ef693 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3857,13 +3857,17 @@
                                     firstChildTop == contentTop - mOverscrollDistance) ||
                               (mFirstPosition + childCount == mItemCount &&
                                     lastChildBottom == contentBottom + mOverscrollDistance))) {
-                        if (mFlingRunnable == null) {
-                            mFlingRunnable = new FlingRunnable();
+                        if (!dispatchNestedPreFling(0, -initialVelocity)) {
+                            if (mFlingRunnable == null) {
+                                mFlingRunnable = new FlingRunnable();
+                            }
+                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+                            mFlingRunnable.start(-initialVelocity);
+                            dispatchNestedFling(0, -initialVelocity, true);
+                        } else {
+                            mTouchMode = TOUCH_MODE_REST;
+                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                         }
-                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
-
-                        mFlingRunnable.start(-initialVelocity);
-                        dispatchNestedFling(0, -initialVelocity, true);
                     } else {
                         mTouchMode = TOUCH_MODE_REST;
                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
@@ -3992,6 +3996,22 @@
         return super.onGenericMotionEvent(event);
     }
 
+    /**
+     * Initiate a fling with the given velocity.
+     *
+     * <p>Applications can use this method to manually initiate a fling as if the user
+     * initiated it via touch interaction.</p>
+     *
+     * @param velocityY Vertical velocity in pixels per second
+     */
+    public void fling(int velocityY) {
+        if (mFlingRunnable == null) {
+            mFlingRunnable = new FlingRunnable();
+        }
+        reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+        mFlingRunnable.start(-velocityY);
+    }
+
     @Override
     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
         return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
@@ -4029,7 +4049,9 @@
             if (mFlingRunnable == null) {
                 mFlingRunnable = new FlingRunnable();
             }
-            mFlingRunnable.start((int) velocityY);
+            if (!dispatchNestedPreFling(0, velocityY)) {
+                mFlingRunnable.start((int) velocityY);
+            }
             return true;
         }
         return dispatchNestedFling(velocityX, velocityY, consumed);
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 74c66c8..d0a2eab 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -84,6 +84,9 @@
 public class DatePicker extends FrameLayout {
     private static final String LOG_TAG = DatePicker.class.getSimpleName();
 
+    private static final int MODE_SPINNER = 1;
+    private static final int MODE_CALENDAR = 2;
+
     private final DatePickerDelegate mDelegate;
 
     /**
@@ -120,24 +123,28 @@
 
         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
                 defStyleAttr, defStyleRes);
-        final boolean legacyMode = a.getBoolean(R.styleable.DatePicker_legacyMode, true);
+        int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
         a.recycle();
 
-        if (legacyMode) {
-            mDelegate = createLegacyUIDelegate(context, attrs, defStyleAttr, defStyleRes);
-        } else {
-            mDelegate = createNewUIDelegate(context, attrs, defStyleAttr, defStyleRes);
+        switch (mode) {
+            case MODE_CALENDAR:
+                mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes);
+                break;
+            case MODE_SPINNER:
+            default:
+                mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes);
+                break;
         }
     }
 
-    private DatePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
+    private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
-        return new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+        return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
     }
 
-    private DatePickerDelegate createNewUIDelegate(Context context, AttributeSet attrs,
+    private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
-        return new android.widget.DatePickerDelegate(this, context, attrs, defStyleAttr,
+        return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr,
                 defStyleRes);
     }
 
@@ -454,7 +461,7 @@
     /**
      * A delegate implementing the basic DatePicker
      */
-    private static class LegacyDatePickerDelegate extends AbstractDatePickerDelegate {
+    private static class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
 
         private static final String DATE_FORMAT = "MM/dd/yyyy";
 
@@ -500,7 +507,7 @@
 
         private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
 
-        LegacyDatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+        DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
                 int defStyleAttr, int defStyleRes) {
             super(delegator, context);
 
diff --git a/core/java/android/widget/DatePickerDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
similarity index 99%
rename from core/java/android/widget/DatePickerDelegate.java
rename to core/java/android/widget/DatePickerCalendarDelegate.java
index ddc565d..c0c76ac 100644
--- a/core/java/android/widget/DatePickerDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -52,7 +52,7 @@
 /**
  * A delegate for picking up a date (day / month / year).
  */
-class DatePickerDelegate extends DatePicker.AbstractDatePickerDelegate implements
+class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements
         View.OnClickListener, DatePickerController {
 
     private static final int UNINITIALIZED = -1;
@@ -113,7 +113,7 @@
 
     private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
 
-    public DatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+    public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(delegator, context);
 
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 33cc66e..d263625 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -351,6 +351,7 @@
         final int selectedPosition = mSelectedPosition;
 
         View child = null;
+        final int nextChildDir = isLayoutRtl ? -1 : +1;
         for (int pos = startPos; pos < last; pos++) {
             // is this the selected item?
             boolean selected = pos == selectedPosition;
@@ -359,9 +360,9 @@
             final int where = flow ? -1 : pos - startPos;
             child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
 
-            nextLeft += (isLayoutRtl ? -1 : +1) * columnWidth;
+            nextLeft += nextChildDir * columnWidth;
             if (pos < last - 1) {
-                nextLeft += horizontalSpacing;
+                nextLeft += nextChildDir * horizontalSpacing;
             }
 
             if (selected && (hasFocus || inClick)) {
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index 546cc5f..f1aaa4d 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.media.AudioManager;
 import android.os.Handler;
@@ -55,7 +56,7 @@
  * <p>
  * Functions like show() and hide() have no effect when MediaController
  * is created in an xml layout.
- * 
+ *
  * MediaController will hide and
  * show the buttons according to these rules:
  * <ul>
@@ -70,32 +71,34 @@
  */
 public class MediaController extends FrameLayout {
 
-    private MediaPlayerControl  mPlayer;
-    private Context             mContext;
-    private View                mAnchor;
-    private View                mRoot;
-    private WindowManager       mWindowManager;
-    private Window              mWindow;
-    private View                mDecor;
+    private MediaPlayerControl mPlayer;
+    private Context mContext;
+    private View mAnchor;
+    private View mRoot;
+    private WindowManager mWindowManager;
+    private Window mWindow;
+    private View mDecor;
     private WindowManager.LayoutParams mDecorLayoutParams;
-    private ProgressBar         mProgress;
-    private TextView            mEndTime, mCurrentTime;
-    private boolean             mShowing;
-    private boolean             mDragging;
-    private static final int    sDefaultTimeout = 3000;
-    private static final int    FADE_OUT = 1;
-    private static final int    SHOW_PROGRESS = 2;
-    private boolean             mUseFastForward;
-    private boolean             mFromXml;
-    private boolean             mListenersSet;
+    private ProgressBar mProgress;
+    private TextView mEndTime, mCurrentTime;
+    private boolean mShowing;
+    private boolean mDragging;
+    private static final int sDefaultTimeout = 3000;
+    private static final int FADE_OUT = 1;
+    private static final int SHOW_PROGRESS = 2;
+    private boolean mUseFastForward;
+    private boolean mFromXml;
+    private boolean mListenersSet;
     private View.OnClickListener mNextListener, mPrevListener;
-    StringBuilder               mFormatBuilder;
-    Formatter                   mFormatter;
-    private ImageButton         mPauseButton;
-    private ImageButton         mFfwdButton;
-    private ImageButton         mRewButton;
-    private ImageButton         mNextButton;
-    private ImageButton         mPrevButton;
+    StringBuilder mFormatBuilder;
+    Formatter mFormatter;
+    private ImageButton mPauseButton;
+    private ImageButton mFfwdButton;
+    private ImageButton mRewButton;
+    private ImageButton mNextButton;
+    private ImageButton mPrevButton;
+    private CharSequence mPlayDescription;
+    private CharSequence mPauseDescription;
 
     public MediaController(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -132,7 +135,7 @@
         mDecor.setOnTouchListener(mTouchListener);
         mWindow.setContentView(this);
         mWindow.setBackgroundDrawableResource(android.R.color.transparent);
-        
+
         // While the media controller is up, the volume control keys should
         // affect the media stream type
         mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
@@ -201,7 +204,7 @@
             return false;
         }
     };
-    
+
     public void setMediaPlayer(MediaPlayerControl player) {
         mPlayer = player;
         updatePausePlay();
@@ -249,6 +252,11 @@
     }
 
     private void initControllerView(View v) {
+        Resources res = mContext.getResources();
+        mPlayDescription = res
+                .getText(com.android.internal.R.string.lockscreen_transport_play_description);
+        mPauseDescription = res
+                .getText(com.android.internal.R.string.lockscreen_transport_pause_description);
         mPauseButton = (ImageButton) v.findViewById(com.android.internal.R.id.pause);
         if (mPauseButton != null) {
             mPauseButton.requestFocus();
@@ -271,7 +279,7 @@
             }
         }
 
-        // By default these are hidden. They will be enabled when setPrevNextListeners() is called 
+        // By default these are hidden. They will be enabled when setPrevNextListeners() is called
         mNextButton = (ImageButton) v.findViewById(com.android.internal.R.id.next);
         if (mNextButton != null && !mFromXml && !mListenersSet) {
             mNextButton.setVisibility(View.GONE);
@@ -328,7 +336,7 @@
             // the buttons.
         }
     }
-    
+
     /**
      * Show the controller on screen. It will go away
      * automatically after 'timeout' milliseconds of inactivity.
@@ -347,7 +355,7 @@
             mShowing = true;
         }
         updatePausePlay();
-        
+
         // cause the progress bar to be updated even if mShowing
         // was already true.  This happens, for example, if we're
         // paused with the progress bar showing the user hits play.
@@ -359,7 +367,7 @@
             mHandler.sendMessageDelayed(msg, timeout);
         }
     }
-    
+
     public boolean isShowing() {
         return mShowing;
     }
@@ -525,8 +533,10 @@
 
         if (mPlayer.isPlaying()) {
             mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause);
+            mPauseButton.setContentDescription(mPauseDescription);
         } else {
             mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play);
+            mPauseButton.setContentDescription(mPlayDescription);
         }
     }
 
@@ -668,7 +678,7 @@
 
         if (mRoot != null) {
             installPrevNextListeners();
-            
+
             if (mNextButton != null && !mFromXml) {
                 mNextButton.setVisibility(View.VISIBLE);
             }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index fd04890..dd312a6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1577,9 +1577,11 @@
     private void flingWithNestedDispatch(int velocityY) {
         final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
                 (mScrollY < getScrollRange() || velocityY < 0);
-        dispatchNestedFling(0, velocityY, canFling);
-        if (canFling) {
-            fling(velocityY);
+        if (!dispatchNestedPreFling(0, velocityY)) {
+            dispatchNestedFling(0, velocityY, canFling);
+            if (canFling) {
+                fling(velocityY);
+            }
         }
     }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a0f7baf..b162e54 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.R;
+import android.annotation.Nullable;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
@@ -222,6 +223,8 @@
  * @attr ref android.R.styleable#TextView_imeActionId
  * @attr ref android.R.styleable#TextView_editorExtras
  * @attr ref android.R.styleable#TextView_elegantTextHeight
+ * @attr ref android.R.styleable#TextView_letterSpacing
+ * @attr ref android.R.styleable#TextView_fontFeatureSettings
  */
 @RemoteView
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -2702,7 +2705,6 @@
      *
      * @see #setLetterSpacing(float)
      * @see Paint#setLetterSpacing
-     * @hide
      */
     public float getLetterSpacing() {
         return mTextPaint.getLetterSpacing();
@@ -2716,7 +2718,6 @@
      * @see Paint#getLetterSpacing
      *
      * @attr ref android.R.styleable#TextView_letterSpacing
-     * @hide
      */
     @android.view.RemotableViewMethod
     public void setLetterSpacing(float letterSpacing) {
@@ -2736,8 +2737,8 @@
      *
      * @see #setFontFeatureSettings(String)
      * @see Paint#setFontFeatureSettings
-     * @hide
      */
+    @Nullable
     public String getFontFeatureSettings() {
         return mTextPaint.getFontFeatureSettings();
     }
@@ -2747,14 +2748,14 @@
      * font-feature-settings attribute:
      * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
      *
+     * @param fontFeatureSettings font feature settings represented as CSS compatible string
      * @see #getFontFeatureSettings()
      * @see Paint#getFontFeatureSettings
      *
      * @attr ref android.R.styleable#TextView_fontFeatureSettings
-     * @hide
      */
     @android.view.RemotableViewMethod
-    public void setFontFeatureSettings(String fontFeatureSettings) {
+    public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
 
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 026a8ee..c488666 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -45,6 +45,9 @@
  */
 @Widget
 public class TimePicker extends FrameLayout {
+    private static final int MODE_SPINNER = 1;
+    private static final int MODE_CLOCK = 2;
+
     private final TimePickerDelegate mDelegate;
 
     /**
@@ -77,15 +80,19 @@
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
-        final boolean legacyMode = a.getBoolean(R.styleable.TimePicker_legacyMode, true);
+        int mode = a.getInt(R.styleable.TimePicker_timePickerMode, MODE_SPINNER);
         a.recycle();
 
-        if (legacyMode) {
-            mDelegate = new LegacyTimePickerDelegate(
-                    this, context, attrs, defStyleAttr, defStyleRes);
-        } else {
-            mDelegate = new android.widget.TimePickerDelegate(
-                    this, context, attrs, defStyleAttr, defStyleRes);
+        switch (mode) {
+            case MODE_CLOCK:
+                mDelegate = new TimePickerSpinnerDelegate(
+                        this, context, attrs, defStyleAttr, defStyleRes);
+                break;
+            case MODE_SPINNER:
+            default:
+                mDelegate = new TimePickerClockDelegate(
+                        this, context, attrs, defStyleAttr, defStyleRes);
+                break;
         }
     }
 
diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
similarity index 97%
rename from core/java/android/widget/LegacyTimePickerDelegate.java
rename to core/java/android/widget/TimePickerClockDelegate.java
index 70db211..8102c4a 100644
--- a/core/java/android/widget/LegacyTimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.os.Parcel;
@@ -44,7 +45,7 @@
 /**
  * A delegate implementing the basic TimePicker
  */
-class LegacyTimePickerDelegate extends TimePicker.AbstractTimePickerDelegate {
+class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
 
     private static final boolean DEFAULT_ENABLED_STATE = true;
 
@@ -100,8 +101,8 @@
                 }
             };
 
-    public LegacyTimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
-                                    int defStyleAttr, int defStyleRes) {
+    public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
         super(delegator, context);
 
         // process style attributes
@@ -171,7 +172,10 @@
         mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
 
             /* Get the localized am/pm strings and use them in the spinner */
-        mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
+        final Resources res = context.getResources();
+        final String amText = res.getString(R.string.time_picker_am_label);
+        final String pmText = res.getString(R.string.time_picker_pm_label);
+        mAmPmStrings = new String[] {amText, pmText};
 
         // am/pm
         View amPmView = mDelegator.findViewById(R.id.amPm);
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
similarity index 99%
rename from core/java/android/widget/TimePickerDelegate.java
rename to core/java/android/widget/TimePickerSpinnerDelegate.java
index c68619c..523965c 100644
--- a/core/java/android/widget/TimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -51,7 +51,7 @@
 /**
  * A view for selecting the time of day, in either 24 hour or AM/PM mode.
  */
-class TimePickerDelegate extends TimePicker.AbstractTimePickerDelegate implements
+class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate implements
         RadialTimePickerView.OnValueSelectedListener {
 
     private static final String TAG = "TimePickerDelegate";
@@ -119,8 +119,8 @@
 
     private Calendar mTempCalendar;
 
-    public TimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
-                              int defStyleAttr, int defStyleRes) {
+    public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
         super(delegator, context);
 
         // process style attributes
@@ -134,6 +134,8 @@
         mSelectHours = res.getString(R.string.select_hours);
         mMinutePickerDescription = res.getString(R.string.minute_picker_description);
         mSelectMinutes = res.getString(R.string.select_minutes);
+        mAmText = res.getString(R.string.time_picker_am_label);
+        mPmText = res.getString(R.string.time_picker_pm_label);
 
         final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
                 R.layout.time_picker_holo);
@@ -182,10 +184,6 @@
                 R.id.radial_picker);
         mDoneButton = (Button) mainView.findViewById(R.id.done_button);
 
-        String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
-        mAmText = amPmTexts[0];
-        mPmText = amPmTexts[1];
-
         setupListeners();
 
         mAllowAutoAdvance = true;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 5655506..40965f0 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,6 +18,7 @@
 
 import com.android.internal.os.BatteryStatsImpl;
 
+import android.os.ParcelFileDescriptor;
 import android.os.WorkSource;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.SignalStrength;
@@ -37,6 +38,8 @@
     // Remaining methods are only used in Java.
     byte[] getStatistics();
 
+    ParcelFileDescriptor getStatisticsStream();
+
     // Return the computed amount of time remaining on battery, in milliseconds.
     // Returns -1 if nothing could be computed.
     long computeBatteryTimeRemaining();
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 97e1102..d8dffe0 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -273,11 +273,15 @@
 
     @Override
     public int finishBackup() {
-        if (DEBUG) Log.v(TAG, "finishBackup()");
+        if (DEBUG) Log.v(TAG, "finishBackup() of " + mFullTargetPackage);
+        return tearDownFullBackup();
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Full backup handling
+
+    private int tearDownFullBackup() {
         if (mSocket != null) {
-            if (DEBUG) {
-                Log.v(TAG, "Concluding full backup of " + mFullTargetPackage);
-            }
             try {
                 mFullBackupOutputStream.flush();
                 mFullBackupOutputStream.close();
@@ -286,7 +290,7 @@
                 mSocket.close();
             } catch (IOException e) {
                 if (DEBUG) {
-                    Log.w(TAG, "Exception caught in finishBackup()", e);
+                    Log.w(TAG, "Exception caught in tearDownFullBackup()", e);
                 }
                 return TRANSPORT_ERROR;
             } finally {
@@ -296,8 +300,9 @@
         return TRANSPORT_OK;
     }
 
-    // ------------------------------------------------------------------------------------
-    // Full backup handling
+    private File tarballFile(String pkgName) {
+        return new File(mCurrentSetFullDir, pkgName);
+    }
 
     @Override
     public long requestFullBackupTime() {
@@ -329,7 +334,7 @@
         mFullTargetPackage = targetPackage.packageName;
         FileOutputStream tarstream;
         try {
-            File tarball = new File(mCurrentSetFullDir, mFullTargetPackage);
+            File tarball = tarballFile(mFullTargetPackage);
             tarstream = new FileOutputStream(tarball);
         } catch (FileNotFoundException e) {
             return TRANSPORT_ERROR;
@@ -368,6 +373,19 @@
         return TRANSPORT_OK;
     }
 
+    // For now we can't roll back, so just tear everything down.
+    @Override
+    public void cancelFullBackup() {
+        if (DEBUG) {
+            Log.i(TAG, "Canceling full backup of " + mFullTargetPackage);
+        }
+        File archive = tarballFile(mFullTargetPackage);
+        tearDownFullBackup();
+        if (archive.exists()) {
+            archive.delete();
+        }
+    }
+
     // ------------------------------------------------------------------------------------
     // Restore handling
     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
index 445d10a..5a83f33 100644
--- a/core/java/com/android/internal/os/AtomicFile.java
+++ b/core/java/com/android/internal/os/AtomicFile.java
@@ -40,7 +40,7 @@
  * appropriate mutual exclusion invariants whenever it accesses the file.
  * </p>
  */
-public class AtomicFile {
+public final class AtomicFile {
     private final File mBaseName;
     private final File mBackupName;
     
@@ -129,7 +129,16 @@
         } catch (IOException e) {
         }
     }
-    
+
+    public boolean exists() {
+        return mBaseName.exists() || mBackupName.exists();
+    }
+
+    public void delete() {
+        mBaseName.delete();
+        mBackupName.delete();
+    }
+
     public FileInputStream openRead() throws FileNotFoundException {
         if (mBackupName.exists()) {
             mBaseName.delete();
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 6c9b4b8..63be2d4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -30,19 +30,26 @@
 import android.os.BatteryStats;
 import android.os.BatteryStats.Uid;
 import android.os.Bundle;
+import android.os.MemoryFile;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telephony.SignalStrength;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BatterySipper.DrainType;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -55,7 +62,7 @@
  * The caller must initialize this class as soon as activity object is ready to use (for example, in
  * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
  */
-public class BatteryStatsHelper {
+public final class BatteryStatsHelper {
 
     private static final boolean DEBUG = false;
 
@@ -63,6 +70,7 @@
 
     private static BatteryStats sStatsXfer;
     private static Intent sBatteryBroadcastXfer;
+    private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>();
 
     final private Context mContext;
     final private boolean mCollectBatteryBroadcast;
@@ -117,6 +125,68 @@
         mCollectBatteryBroadcast = collectBatteryBroadcast;
     }
 
+    public void storeStatsHistoryInFile(String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(mContext, fname);
+            sFileXfer.put(path, this.getStats());
+            FileOutputStream fout = null;
+            try {
+                fout = new FileOutputStream(path);
+                Parcel hist = Parcel.obtain();
+                getStats().writeToParcelWithoutUids(hist, 0);
+                byte[] histData = hist.marshall();
+                fout.write(histData);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to write history to file", e);
+            } finally {
+                if (fout != null) {
+                    try {
+                        fout.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    public static BatteryStats statsFromFile(Context context, String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(context, fname);
+            BatteryStats stats = sFileXfer.get(path);
+            if (stats != null) {
+                return stats;
+            }
+            FileInputStream fin = null;
+            try {
+                fin = new FileInputStream(path);
+                byte[] data = readFully(fin);
+                Parcel parcel = Parcel.obtain();
+                parcel.unmarshall(data, 0, data.length);
+                parcel.setDataPosition(0);
+                return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to read history to file", e);
+            } finally {
+                if (fin != null) {
+                    try {
+                        fin.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        return getStats(IBatteryStats.Stub.asInterface(
+                        ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+    }
+
+    public static void dropFile(Context context, String fname) {
+        makeFilePath(context, fname).delete();
+    }
+
+    private static File makeFilePath(Context context, String fname) {
+        return new File(context.getFilesDir(), fname);
+    }
+
     /** Clears the current stats and forces recreating for future use. */
     public void clearStats() {
         mStats = null;
@@ -853,25 +923,64 @@
 
     public long getChargeTimeRemaining() { return mChargeTimeRemaining; }
 
+    public static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+        return readFully(stream, stream.available());
+    }
+
+    public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException {
+        int pos = 0;
+        byte[] data = new byte[avail];
+        while (true) {
+            int amt = stream.read(data, pos, data.length-pos);
+            //Log.i("foo", "Read " + amt + " bytes at " + pos
+            //        + " of avail " + data.length);
+            if (amt <= 0) {
+                //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                //        + " len=" + data.length);
+                return data;
+            }
+            pos += amt;
+            avail = stream.available();
+            if (avail > data.length-pos) {
+                byte[] newData = new byte[pos+avail];
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+
     private void load() {
         if (mBatteryInfo == null) {
             return;
         }
-        try {
-            byte[] data = mBatteryInfo.getStatistics();
-            Parcel parcel = Parcel.obtain();
-            parcel.unmarshall(data, 0, data.length);
-            parcel.setDataPosition(0);
-            BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
-                    .createFromParcel(parcel);
-            stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
-            mStats = stats;
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException:", e);
-        }
+        mStats = getStats(mBatteryInfo);
         if (mCollectBatteryBroadcast) {
             mBatteryBroadcast = mContext.registerReceiver(null,
                     new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         }
     }
+
+    private static BatteryStatsImpl getStats(IBatteryStats service) {
+        try {
+            ParcelFileDescriptor pfd = service.getStatisticsStream();
+            if (pfd != null) {
+                FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                try {
+                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
+                    Parcel parcel = Parcel.obtain();
+                    parcel.unmarshall(data, 0, data.length);
+                    parcel.setDataPosition(0);
+                    BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
+                            .createFromParcel(parcel);
+                    stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
+                    return stats;
+                } catch (IOException e) {
+                    Log.w(TAG, "Unable to read statistics stream", e);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException:", e);
+        }
+        return null;
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aad1156..ee0d14b 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -29,6 +29,7 @@
 import android.os.BadParcelableException;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.Build;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
@@ -91,7 +92,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 112 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 113 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -109,6 +110,7 @@
     private static int sNumSpeedSteps;
 
     private final JournaledFile mFile;
+    public final AtomicFile mCheckinFile;
 
     static final int MSG_UPDATE_WAKELOCKS = 1;
     static final int MSG_REPORT_POWER_CHANGE = 2;
@@ -142,7 +144,7 @@
         }
     }
 
-    private final MyHandler mHandler;
+    public final MyHandler mHandler;
 
     private BatteryCallback mCallback;
 
@@ -199,8 +201,8 @@
     boolean mRecordingHistory = false;
     int mNumHistoryItems;
 
-    static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
-    static final int MAX_MAX_HISTORY_BUFFER = 144*1024; // 144KB
+    static final int MAX_HISTORY_BUFFER = 256*1024; // 256KB
+    static final int MAX_MAX_HISTORY_BUFFER = 320*1024; // 320KB
     final Parcel mHistoryBuffer = Parcel.obtain();
     final HistoryItem mHistoryLastWritten = new HistoryItem();
     final HistoryItem mHistoryLastLastWritten = new HistoryItem();
@@ -232,6 +234,8 @@
     int mStartCount;
 
     long mStartClockTime;
+    String mStartPlatformVersion;
+    String mEndPlatformVersion;
 
     long mUptime;
     long mUptimeStart;
@@ -240,7 +244,7 @@
 
     int mWakeLockNesting;
     boolean mWakeLockImportant;
-    boolean mRecordAllWakeLocks;
+    boolean mRecordAllHistory;
     boolean mNoAutoReset;
 
     int mScreenState = Display.STATE_UNKNOWN;
@@ -340,7 +344,11 @@
     int mDischargeAmountScreenOff;
     int mDischargeAmountScreenOffSinceCharge;
 
-    static final int MAX_LEVEL_STEPS = 100;
+    static final int MAX_LEVEL_STEPS = 200;
+
+    int mInitStepMode = 0;
+    int mCurStepMode = 0;
+    int mModStepMode = 0;
 
     int mLastDischargeStepLevel;
     long mLastDischargeStepTime;
@@ -429,10 +437,11 @@
     @GuardedBy("this")
     private String[] mWifiIfaces = new String[0];
 
-    // For debugging
     public BatteryStatsImpl() {
         mFile = null;
+        mCheckinFile = null;
         mHandler = null;
+        clearHistoryLocked();
     }
 
     public static interface TimeBaseObs {
@@ -2353,6 +2362,9 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_PROC_START, name, uid, 0)) {
             return;
         }
+        if (!mRecordAllHistory) {
+            return;
+        }
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_START, name, uid);
@@ -2371,9 +2383,12 @@
         }
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
-        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
         getUidStatsLocked(uid).updateProcessStateLocked(name, Uid.PROCESS_STATE_NONE,
                 elapsedRealtime);
+        if (!mRecordAllHistory) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
     }
 
     public void noteSyncStartLocked(String name, int uid) {
@@ -2427,11 +2442,41 @@
         }
     }
 
-    public void setRecordAllWakeLocksLocked(boolean enabled) {
-        mRecordAllWakeLocks = enabled;
+    public void setRecordAllHistoryLocked(boolean enabled) {
+        mRecordAllHistory = enabled;
         if (!enabled) {
             // Clear out any existing state.
             mActiveEvents.removeEvents(HistoryItem.EVENT_WAKE_LOCK);
+            // Record the currently running processes as stopping, now that we are no
+            // longer tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                final long mSecUptime = SystemClock.uptimeMillis();
+                for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                    SparseIntArray uids = ent.getValue();
+                    for (int j=0; j<uids.size(); j++) {
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
+        } else {
+            // Record the currently running processes as starting, now that we are tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                final long mSecUptime = SystemClock.uptimeMillis();
+                for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                    SparseIntArray uids = ent.getValue();
+                    for (int j=0; j<uids.size(); j++) {
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
         }
     }
 
@@ -2452,7 +2497,7 @@
             if (historyName == null) {
                 historyName = name;
             }
-            if (mRecordAllWakeLocks) {
+            if (mRecordAllHistory) {
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
                         uid, 0)) {
                     addHistoryEventLocked(elapsedRealtime, uptime,
@@ -2496,7 +2541,7 @@
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
-            if (mRecordAllWakeLocks) {
+            if (mRecordAllHistory) {
                 if (historyName == null) {
                     historyName = name;
                 }
@@ -2788,6 +2833,16 @@
             if (DEBUG) Slog.v(TAG, "Screen state: oldState=" + Display.stateToString(oldState)
                     + ", newState=" + Display.stateToString(state));
 
+            if (state != Display.STATE_UNKNOWN) {
+                int stepState = state-1;
+                if (stepState < 4) {
+                    mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_SCREEN_STATE) ^ stepState;
+                    mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_SCREEN_STATE) | stepState;
+                } else {
+                    Slog.wtf(TAG, "Unexpected screen state: " + state);
+                }
+            }
+
             if (state == Display.STATE_ON) {
                 // Screen turning on.
                 final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2924,6 +2979,9 @@
 
     public void noteLowPowerMode(boolean enabled) {
         if (mLowPowerModeEnabled != enabled) {
+            int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
+            mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
+            mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
             mLowPowerModeEnabled = enabled;
@@ -3871,6 +3929,18 @@
         return mStartClockTime;
     }
 
+    @Override public String getStartPlatformVersion() {
+        return mStartPlatformVersion;
+    }
+
+    @Override public String getEndPlatformVersion() {
+        return mEndPlatformVersion;
+    }
+
+    @Override public int getParcelVersion() {
+        return VERSION;
+    }
+
     @Override public boolean getIsOnBattery() {
         return mOnBattery;
     }
@@ -4535,6 +4605,7 @@
                     proc.detach();
                     mProcessStats.removeAt(ip);
                 } else {
+                    proc.reset();
                     active = true;
                 }
             }
@@ -4791,7 +4862,7 @@
             mProcessStats.clear();
             for (int k = 0; k < numProcs; k++) {
                 String processName = in.readString();
-                Uid.Proc proc = new Proc();
+                Uid.Proc proc = new Proc(processName);
                 proc.readFromParcelLocked(in);
                 mProcessStats.put(processName, proc);
             }
@@ -5040,6 +5111,11 @@
          */
         public final class Proc extends BatteryStats.Uid.Proc implements TimeBaseObs {
             /**
+             * The name of this process.
+             */
+            final String mName;
+
+            /**
              * Remains true until removed from the stats.
              */
             boolean mActive = true;
@@ -5133,7 +5209,8 @@
 
             ArrayList<ExcessivePower> mExcessivePower;
 
-            Proc() {
+            Proc(String name) {
+                mName = name;
                 mOnBatteryTimeBase.add(this);
                 mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
             }
@@ -5148,6 +5225,24 @@
             public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
             }
 
+            void reset() {
+                mUserTime = mSystemTime = mForegroundTime = 0;
+                mStarts = 0;
+                mLoadedUserTime = mLoadedSystemTime = mLoadedForegroundTime = 0;
+                mLoadedStarts = 0;
+                mLastUserTime = mLastSystemTime = mLastForegroundTime = 0;
+                mLastStarts = 0;
+                mUnpluggedUserTime = mUnpluggedSystemTime = mUnpluggedForegroundTime = 0;
+                mUnpluggedStarts = 0;
+                for (int i = 0; i < mSpeedBins.length; i++) {
+                    SamplingCounter c = mSpeedBins[i];
+                    if (c != null) {
+                        c.reset(false);
+                    }
+                }
+                mExcessivePower = null;
+            }
+
             void detach() {
                 mActive = false;
                 mOnBatteryTimeBase.remove(this);
@@ -5736,7 +5831,7 @@
         public Proc getProcessStatsLocked(String name) {
             Proc ps = mProcessStats.get(name);
             if (ps == null) {
-                ps = new Proc();
+                ps = new Proc(name);
                 mProcessStats.put(name, ps);
             }
 
@@ -6032,8 +6127,14 @@
         }
     }
 
-    public BatteryStatsImpl(String filename, Handler handler) {
-        mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
+    public BatteryStatsImpl(File systemDir, Handler handler) {
+        if (systemDir != null) {
+            mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
+                    new File(systemDir, "batterystats.bin.tmp"));
+        } else {
+            mFile = null;
+        }
+        mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
@@ -6084,6 +6185,7 @@
         long uptime = SystemClock.uptimeMillis() * 1000;
         long realtime = SystemClock.elapsedRealtime() * 1000;
         initTimes(uptime, realtime);
+        mStartPlatformVersion = mEndPlatformVersion = Build.ID;
         mDischargeStartLevel = 0;
         mDischargeUnplugLevel = 0;
         mDischargePlugLevel = -1;
@@ -6095,6 +6197,7 @@
 
     public BatteryStatsImpl(Parcel p) {
         mFile = null;
+        mCheckinFile = null;
         mHandler = null;
         clearHistoryLocked();
         readFromParcel(p);
@@ -6390,6 +6493,10 @@
 
     private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
         for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+            if (!mRecordAllHistory && i == HistoryItem.EVENT_PROC) {
+                // Not recording process starts/stops.
+                continue;
+            }
             HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(i);
             if (active == null) {
                 continue;
@@ -6455,7 +6562,37 @@
             boolean reset = false;
             if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
                     || level >= 90
-                    || (mDischargeCurrentLevel < 20 && level >= 80))) {
+                    || getLowDischargeAmountSinceCharge() >= 60)
+                    || (getHighDischargeAmountSinceCharge() >= 60
+                            && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER)) {
+                // Before we write, collect a snapshot of the final aggregated
+                // stats to be reported in the next checkin.  Only do this if we have
+                // a sufficient amount of data to make it interesting.
+                if (getLowDischargeAmountSinceCharge() >= 20) {
+                    final Parcel parcel = Parcel.obtain();
+                    writeSummaryToParcel(parcel, true);
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override public void run() {
+                            synchronized (mCheckinFile) {
+                                FileOutputStream stream = null;
+                                try {
+                                    stream = mCheckinFile.startWrite();
+                                    stream.write(parcel.marshall());
+                                    stream.flush();
+                                    FileUtils.sync(stream);
+                                    stream.close();
+                                    mCheckinFile.finishWrite(stream);
+                                } catch (IOException e) {
+                                    Slog.w("BatteryStats",
+                                            "Error writing checkin battery statistics", e);
+                                    mCheckinFile.failWrite(stream);
+                                } finally {
+                                    parcel.recycle();
+                                }
+                            }
+                        }
+                    });
+                }
                 doWrite = true;
                 resetAllStatsLocked();
                 mDischargeStartLevel = level;
@@ -6465,6 +6602,8 @@
             mLastDischargeStepLevel = level;
             mMinDischargeStepLevel = level;
             mLastDischargeStepTime = -1;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
             pullPendingStateUpdatesLocked();
             mHistoryCur.batteryLevel = (byte)level;
             mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
@@ -6504,6 +6643,8 @@
             mLastChargeStepLevel = level;
             mMaxChargeStepLevel = level;
             mLastChargeStepTime = -1;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
         }
         if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
             if (mFile != null) {
@@ -6529,14 +6670,17 @@
     private static final int BATTERY_PLUGGED_NONE = 0;
 
     private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime,
-            int numStepLevels, long elapsedRealtime) {
+            int numStepLevels, long modeBits, long elapsedRealtime) {
         if (lastStepTime >= 0 && numStepLevels > 0) {
             long duration = elapsedRealtime - lastStepTime;
             for (int i=0; i<numStepLevels; i++) {
                 System.arraycopy(steps, 0, steps, 1, steps.length-1);
                 long thisDuration = duration / (numStepLevels-i);
                 duration -= thisDuration;
-                steps[0] = thisDuration;
+                if (thisDuration > STEP_LEVEL_TIME_MASK) {
+                    thisDuration = STEP_LEVEL_TIME_MASK;
+                }
+                steps[0] = thisDuration | modeBits;
             }
             stepCount += numStepLevels;
             if (stepCount > steps.length) {
@@ -6623,23 +6767,30 @@
                 if (changed) {
                     addHistoryRecordLocked(elapsedRealtime, uptime);
                 }
+                long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
+                        | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
+                        | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
                 if (onBattery) {
                     if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
                         mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
                                 mNumDischargeStepDurations, mLastDischargeStepTime,
-                                mLastDischargeStepLevel - level, elapsedRealtime);
+                                mLastDischargeStepLevel - level, modeBits, elapsedRealtime);
                         mLastDischargeStepLevel = level;
                         mMinDischargeStepLevel = level;
                         mLastDischargeStepTime = elapsedRealtime;
+                        mInitStepMode = mCurStepMode;
+                        mModStepMode = 0;
                     }
                 } else {
                     if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
                         mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
                                 mNumChargeStepDurations, mLastChargeStepTime,
-                                level - mLastChargeStepLevel, elapsedRealtime);
+                                level - mLastChargeStepLevel, modeBits, elapsedRealtime);
                         mLastChargeStepLevel = level;
                         mMaxChargeStepLevel = level;
                         mLastChargeStepTime = elapsedRealtime;
+                        mInitStepMode = mCurStepMode;
+                        mModStepMode = 0;
                     }
                 }
             }
@@ -6863,7 +7014,7 @@
         }
         long total = 0;
         for (int i=0; i<numSteps; i++) {
-            total += steps[i];
+            total += steps[i] & STEP_LEVEL_TIME_MASK;
         }
         return total / numSteps;
         /*
@@ -6875,7 +7026,7 @@
             long totalTime = 0;
             int num = 0;
             for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
-                totalTime += steps[i+j];
+                totalTime += steps[i+j] & STEP_LEVEL_TIME_MASK;
                 num++;
             }
             buckets[numBuckets] = totalTime / num;
@@ -7213,7 +7364,7 @@
         }
 
         Parcel out = Parcel.obtain();
-        writeSummaryToParcel(out);
+        writeSummaryToParcel(out, true);
         mLastWriteTime = SystemClock.elapsedRealtime();
 
         if (mPendingWrite != null) {
@@ -7224,14 +7375,11 @@
         if (sync) {
             commitPendingDataToDisk();
         } else {
-            Thread thr = new Thread("BatteryStats-Write") {
-                @Override
-                public void run() {
-                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            BackgroundThread.getHandler().post(new Runnable() {
+                @Override public void run() {
                     commitPendingDataToDisk();
                 }
-            };
-            thr.start();
+            });
         }
     }
 
@@ -7263,29 +7411,6 @@
         }
     }
 
-    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
-        int pos = 0;
-        int avail = stream.available();
-        byte[] data = new byte[avail];
-        while (true) {
-            int amt = stream.read(data, pos, data.length-pos);
-            //Log.i("foo", "Read " + amt + " bytes at " + pos
-            //        + " of avail " + data.length);
-            if (amt <= 0) {
-                //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                //        + " len=" + data.length);
-                return data;
-            }
-            pos += amt;
-            avail = stream.available();
-            if (avail > data.length-pos) {
-                byte[] newData = new byte[pos+avail];
-                System.arraycopy(data, 0, newData, 0, pos);
-                data = newData;
-            }
-        }
-    }
-
     public void readLocked() {
         if (mFile == null) {
             Slog.w("BatteryStats", "readLocked: no file associated with this instance");
@@ -7301,7 +7426,7 @@
             }
             FileInputStream stream = new FileInputStream(file);
 
-            byte[] raw = readFully(stream);
+            byte[] raw = BatteryStatsHelper.readFully(stream);
             Parcel in = Parcel.obtain();
             in.unmarshall(raw, 0, raw.length);
             in.setDataPosition(0);
@@ -7312,6 +7437,8 @@
             Slog.e("BatteryStats", "Error reading battery statistics", e);
         }
 
+        mEndPlatformVersion = Build.ID;
+
         if (mHistoryBuffer.dataPosition() > 0) {
             mRecordingHistory = true;
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -7410,7 +7537,7 @@
         }
     }
 
-    void writeHistory(Parcel out, boolean andOldHistory) {
+    void writeHistory(Parcel out, boolean inclData, boolean andOldHistory) {
         if (DEBUG_HISTORY) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("****************** WRITING mHistoryBaseTime: ");
@@ -7420,6 +7547,11 @@
             Slog.i(TAG, sb.toString());
         }
         out.writeLong(mHistoryBaseTime + mLastHistoryElapsedRealtime);
+        if (!inclData) {
+            out.writeInt(0);
+            out.writeInt(0);
+            return;
+        }
         out.writeInt(mHistoryTagPool.size());
         for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
             HistoryTag tag = ent.getKey();
@@ -7449,7 +7581,7 @@
         out.writeLong(-1);
     }
 
-    private void readSummaryFromParcel(Parcel in) {
+    public void readSummaryFromParcel(Parcel in) {
         final int version = in.readInt();
         if (version != VERSION) {
             Slog.w("BatteryStats", "readFromParcel: version got " + version
@@ -7463,6 +7595,8 @@
         mUptime = in.readLong();
         mRealtime = in.readLong();
         mStartClockTime = in.readLong();
+        mStartPlatformVersion = in.readString();
+        mEndPlatformVersion = in.readString();
         mOnBatteryTimeBase.readSummaryFromParcel(in);
         mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in);
         mDischargeUnplugLevel = in.readInt();
@@ -7742,7 +7876,7 @@
      *
      * @param out the Parcel to be written to.
      */
-    public void writeSummaryToParcel(Parcel out) {
+    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
         pullPendingStateUpdatesLocked();
 
         final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
@@ -7750,12 +7884,14 @@
 
         out.writeInt(VERSION);
 
-        writeHistory(out, true);
+        writeHistory(out, inclHistory, true);
 
         out.writeInt(mStartCount);
         out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
         out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
         out.writeLong(mStartClockTime);
+        out.writeString(mStartPlatformVersion);
+        out.writeString(mEndPlatformVersion);
         mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
         mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
         out.writeInt(mDischargeUnplugLevel);
@@ -8043,6 +8179,8 @@
 
         mStartCount = in.readInt();
         mStartClockTime = in.readLong();
+        mStartPlatformVersion = in.readString();
+        mEndPlatformVersion = in.readString();
         mUptime = in.readLong();
         mUptimeStart = in.readLong();
         mRealtime = in.readLong();
@@ -8194,10 +8332,12 @@
 
         out.writeInt(MAGIC);
 
-        writeHistory(out, false);
+        writeHistory(out, true, false);
 
         out.writeInt(mStartCount);
         out.writeLong(mStartClockTime);
+        out.writeString(mStartPlatformVersion);
+        out.writeString(mEndPlatformVersion);
         out.writeLong(mUptime);
         out.writeLong(mUptimeStart);
         out.writeLong(mRealtime);
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
index eeffc16..9f775d3 100644
--- a/core/java/com/android/internal/util/JournaledFile.java
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -20,7 +20,7 @@
 import java.io.IOException;
 
 /**
- * @Deprecated Use {@link com.android.internal.os.AtomicFile} instead.  It would
+ * @deprecated Use {@link com.android.internal.os.AtomicFile} instead.  It would
  * be nice to update all existing uses of this to switch to AtomicFile, but since
  * their on-file semantics are slightly different that would run the risk of losing
  * data if at the point of the platform upgrade to the new code it would need to
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index b00e1ab..f64ad7d 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -45,10 +45,9 @@
     FontStyle resolved = resolvedFace->fStyle;
 
     /* Prepare minikin FontStyle */
-    SkString langStr = paint->getPaintOptionsAndroid().getLanguage().getTag();
-    FontLanguage minikinLang(langStr.c_str(), langStr.size());
-    SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
-    FontVariant minikinVariant = var == SkPaintOptionsAndroid::kElegant_Variant ? VARIANT_ELEGANT
+    std::string lang = paint->getTextLocale();
+    FontLanguage minikinLang(lang.c_str(), lang.size());
+    FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT
             : VARIANT_COMPACT;
     FontStyle minikinStyle(minikinLang, minikinVariant, resolved.getWeight(), resolved.getItalic());
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index f6ced09..4bb31fc 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -365,27 +365,19 @@
         char langTag[ULOC_FULLNAME_CAPACITY];
         toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
 
-        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
-        paintOpts.setLanguage(langTag);
-        obj->setPaintOptionsAndroid(paintOpts);
+        obj->setTextLocale(langTag);
     }
 
     static jboolean isElegantTextHeight(JNIEnv* env, jobject paint) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         Paint* obj = GraphicsJNI::getNativePaint(env, paint);
-        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
-        return paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant;
+        return obj->getFontVariant() == VARIANT_ELEGANT;
     }
 
     static void setElegantTextHeight(JNIEnv* env, jobject paint, jboolean aa) {
         NPE_CHECK_RETURN_VOID(env, paint);
         Paint* obj = GraphicsJNI::getNativePaint(env, paint);
-        SkPaintOptionsAndroid::FontVariant variant =
-            aa ? SkPaintOptionsAndroid::kElegant_Variant :
-            SkPaintOptionsAndroid::kDefault_Variant;
-        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
-        paintOpts.setFontVariant(variant);
-        obj->setPaintOptionsAndroid(paintOpts);
+        obj->setFontVariant(aa ? VARIANT_ELEGANT : VARIANT_DEFAULT);
     }
 
     static jfloat getTextSize(JNIEnv* env, jobject paint) {
@@ -457,8 +449,7 @@
         // restore the original settings.
         paint->setTextSkewX(saveSkewX);
         paint->setFakeBoldText(savefakeBold);
-        SkPaintOptionsAndroid paintOpts = paint->getPaintOptionsAndroid();
-        if (paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant) {
+        if (paint->getFontVariant() == VARIANT_ELEGANT) {
             SkScalar size = paint->getTextSize();
             metrics->fTop = -size * kElegantTop / 2048;
             metrics->fBottom = -size * kElegantBottom / 2048;
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index efc65be..a20bb4b 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -20,6 +20,8 @@
 #include <SkPaint.h>
 #include <string>
 
+#include <minikin/FontFamily.h>
+
 namespace android {
 
 class Paint : public SkPaint {
@@ -51,9 +53,27 @@
         return mFontFeatureSettings;
     }
 
+    void setTextLocale(const std::string &textLocale) {
+        mTextLocale = textLocale;
+    }
+
+    std::string getTextLocale() const {
+        return mTextLocale;
+    }
+
+    void setFontVariant(FontVariant variant) {
+        mFontVariant = variant;
+    }
+
+    FontVariant getFontVariant() const {
+        return mFontVariant;
+    }
+
 private:
     float mLetterSpacing;
     std::string mFontFeatureSettings;
+    std::string mTextLocale;
+    FontVariant mFontVariant;
 };
 
 }  // namespace android
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index 05020d2..e1b539e 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -23,11 +23,11 @@
 namespace android {
 
 Paint::Paint() : SkPaint(),
-        mLetterSpacing(0), mFontFeatureSettings() {
+        mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
 }
 
 Paint::Paint(const Paint& paint) : SkPaint(paint),
-        mLetterSpacing(0), mFontFeatureSettings() {
+        mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
 }
 
 Paint::~Paint() {
@@ -37,13 +37,17 @@
     SkPaint::operator=(other);
     mLetterSpacing = other.mLetterSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
+    mTextLocale = other.mTextLocale;
+    mFontVariant = other.mFontVariant;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
             && a.mLetterSpacing == b.mLetterSpacing
-            && a.mFontFeatureSettings == b.mFontFeatureSettings;
+            && a.mFontFeatureSettings == b.mFontFeatureSettings
+            && a.mTextLocale == b.mTextLocale
+            && a.mFontVariant == b.mFontVariant;
 }
 
 }
diff --git a/core/res/res/anim/launch_task_behind_source.xml b/core/res/res/anim/launch_task_behind_source.xml
index 426ee5d..cd3e30a 100644
--- a/core/res/res/anim/launch_task_behind_source.xml
+++ b/core/res/res/anim/launch_task_behind_source.xml
@@ -37,20 +37,20 @@
         android:interpolator="@interpolator/fast_out_slow_in"
         android:duration="350" />
 
-    <alpha android:fromAlpha="1.0" android:toAlpha="1.6"
+    <alpha android:fromAlpha="1.0" android:toAlpha="1.6666666666"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:interpolator="@interpolator/decelerate_cubic"
         android:startOffset="433"
         android:duration="133"/>
 
-    <translate android:fromYDelta="0%" android:toYDelta="-10%"
+    <translate android:fromYDelta="0%" android:toYDelta="-8.8888888888%"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:interpolator="@interpolator/decelerate_cubic"
         android:startOffset="433"
         android:duration="350"/>
 
-    <scale android:fromXScale="1.0" android:toXScale="1.1"
-        android:fromYScale="1.0" android:toYScale="1.1"
+    <scale android:fromXScale="1.0" android:toXScale="1.1111111111"
+        android:fromYScale="1.0" android:toYScale="1.1111111111"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:pivotX="50%p" android:pivotY="50%p"
         android:interpolator="@interpolator/decelerate_cubic"
diff --git a/core/res/res/color/btn_default_material_dark.xml b/core/res/res/color/btn_default_material_dark.xml
index 59555ae..7c904cd 100644
--- a/core/res/res/color/btn_default_material_dark.xml
+++ b/core/res/res/color/btn_default_material_dark.xml
@@ -15,6 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_material_dark"/>
+    <item android:state_enabled="false"
+        android:alpha="@dimen/disabled_alpha_material"
+        android:color="@color/button_material_dark"/>
     <item android:color="@color/button_material_dark"/>
 </selector>
diff --git a/core/res/res/color/btn_default_material_light.xml b/core/res/res/color/btn_default_material_light.xml
index 6511a22..738f9ad 100644
--- a/core/res/res/color/btn_default_material_light.xml
+++ b/core/res/res/color/btn_default_material_light.xml
@@ -15,6 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_material_light"/>
+    <item android:state_enabled="false"
+        android:alpha="@dimen/disabled_alpha_material"
+        android:color="@color/button_material_light"/>
     <item android:color="@color/button_material_light"/>
 </selector>
diff --git a/core/res/res/color/primary_text_disable_only_material_dark.xml b/core/res/res/color/primary_text_disable_only_material_dark.xml
index cf7acaa..cdae790 100644
--- a/core/res/res/color/primary_text_disable_only_material_dark.xml
+++ b/core/res/res/color/primary_text_disable_only_material_dark.xml
@@ -15,6 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@android:color/bright_foreground_material_dark"/>
-    <item android:color="@android:color/bright_foreground_material_dark"/>
+    <item android:state_enabled="false"
+        android:alpha="@dimen/disabled_alpha_material"
+        android:color="@color/bright_foreground_material_dark"/>
+    <item android:color="@color/bright_foreground_material_dark"/>
 </selector>
diff --git a/core/res/res/color/primary_text_disable_only_material_light.xml b/core/res/res/color/primary_text_disable_only_material_light.xml
index bf5d2c0..0bf14d0 100644
--- a/core/res/res/color/primary_text_disable_only_material_light.xml
+++ b/core/res/res/color/primary_text_disable_only_material_light.xml
@@ -15,6 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@android:color/bright_foreground_material_light"/>
-    <item android:color="@android:color/bright_foreground_material_light"/>
+    <item android:state_enabled="false"
+        android:alpha="@dimen/disabled_alpha_material"
+        android:color="@color/bright_foreground_material_light"/>
+    <item android:color="@color/bright_foreground_material_light"/>
 </selector>
diff --git a/core/res/res/color/primary_text_material_dark.xml b/core/res/res/color/primary_text_material_dark.xml
index caef9d0..6ad837b 100644
--- a/core/res/res/color/primary_text_material_dark.xml
+++ b/core/res/res/color/primary_text_material_dark.xml
@@ -15,6 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/primary_text_default_material_dark"/>
+    <item android:state_enabled="false"
+        android:alpha="@dimen/disabled_alpha_material"
+        android:color="@color/primary_text_default_material_dark"/>
     <item android:color="@color/primary_text_default_material_dark"/>
 </selector>
diff --git a/core/res/res/color/primary_text_material_light.xml b/core/res/res/color/primary_text_material_light.xml
index 81a593b..4c19e12 100644
--- a/core/res/res/color/primary_text_material_light.xml
+++ b/core/res/res/color/primary_text_material_light.xml
@@ -15,6 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:alpha="0.5" android:color="@color/primary_text_default_material_light"/>
+    <item android:state_enabled="false"
+        android:alpha="@dimen/disabled_alpha_material"
+        android:color="@color/primary_text_default_material_light"/>
     <item android:color="@color/primary_text_default_material_light"/>
 </selector>
diff --git a/core/res/res/drawable-hdpi/cab_background_bottom_mtrl_alpha.9.png b/core/res/res/drawable-hdpi/cab_background_bottom_mtrl_alpha.9.png
new file mode 100644
index 0000000..92613b7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_background_bottom_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_background_top_mtrl_alpha.9.png b/core/res/res/drawable-hdpi/cab_background_top_mtrl_alpha.9.png
new file mode 100644
index 0000000..e51ef28
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_background_bottom_mtrl_alpha.9.png b/core/res/res/drawable-mdpi/cab_background_bottom_mtrl_alpha.9.png
new file mode 100644
index 0000000..df292a0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_background_bottom_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_background_top_mtrl_alpha.9.png b/core/res/res/drawable-mdpi/cab_background_top_mtrl_alpha.9.png
new file mode 100644
index 0000000..ae8cccd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cab_background_bottom_mtrl_alpha.9.png b/core/res/res/drawable-xhdpi/cab_background_bottom_mtrl_alpha.9.png
new file mode 100644
index 0000000..9a4abd0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/cab_background_bottom_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cab_background_top_mtrl_alpha.9.png b/core/res/res/drawable-xhdpi/cab_background_top_mtrl_alpha.9.png
new file mode 100644
index 0000000..ed8d341
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cab_background_bottom_mtrl_alpha.9.png b/core/res/res/drawable-xxhdpi/cab_background_bottom_mtrl_alpha.9.png
new file mode 100644
index 0000000..22bd8ce
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/cab_background_bottom_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cab_background_top_mtrl_alpha.9.png b/core/res/res/drawable-xxhdpi/cab_background_top_mtrl_alpha.9.png
new file mode 100644
index 0000000..1dd64b9
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/core/res/res/drawable/cab_background_bottom_material.xml b/core/res/res/drawable/cab_background_bottom_material.xml
new file mode 100644
index 0000000..cfbc854
--- /dev/null
+++ b/core/res/res/drawable/cab_background_bottom_material.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingMode="stack">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="?attr/colorBackground" />
+        </shape>
+    </item>
+    <item>
+        <nine-patch
+            android:src="@drawable/cab_background_bottom_mtrl_alpha"
+            android:tint="?attr/colorControlActivated" />
+    </item>
+</layer-list>
diff --git a/core/res/res/drawable/cab_background_top_material.xml b/core/res/res/drawable/cab_background_top_material.xml
new file mode 100644
index 0000000..09f7a61
--- /dev/null
+++ b/core/res/res/drawable/cab_background_top_material.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingMode="stack">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="?attr/colorBackground" />
+        </shape>
+    </item>
+    <item>
+        <nine-patch
+            android:src="@drawable/cab_background_top_mtrl_alpha"
+            android:tint="?attr/colorControlActivated" />
+    </item>
+</layer-list>
diff --git a/core/res/res/drawable/dialog_background_material.xml b/core/res/res/drawable/dialog_background_material.xml
index 7e5b003..2f8d1fa 100644
--- a/core/res/res/drawable/dialog_background_material.xml
+++ b/core/res/res/drawable/dialog_background_material.xml
@@ -14,12 +14,10 @@
      limitations under the License.
 -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-
-    <corners
-        android:radius="2dp" />
-    <solid
-        android:color="?attr/colorBackground" />
-
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:inset="16dp">
+    <shape android:shape="rectangle">
+        <corners android:radius="2dp" />
+        <solid android:color="?attr/colorBackground" />
+    </shape>
+</inset>
diff --git a/core/res/res/drawable/ic_corp_icon_badge.xml b/core/res/res/drawable/ic_corp_icon_badge.xml
index d20c431..834ae68 100644
--- a/core/res/res/drawable/ic_corp_icon_badge.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge.xml
@@ -20,13 +20,11 @@
         android:viewportHeight="64.0">
 
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
-        android:fillOpacity="0.2"/>
+        android:fillColor="#33000000"
+        android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/>
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
-        android:fillOpacity="0.2"/>
+        android:fillColor="#33000000"
+        android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/>
     <path
         android:pathData="M49.0,49.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
         android:fillColor="#FF5722"/>
diff --git a/core/res/res/drawable/switch_thumb_material_anim.xml b/core/res/res/drawable/switch_thumb_material_anim.xml
index 71f6cfd..30bc888 100644
--- a/core/res/res/drawable/switch_thumb_material_anim.xml
+++ b/core/res/res/drawable/switch_thumb_material_anim.xml
@@ -31,8 +31,7 @@
             android:src="@drawable/btn_switch_to_on_mtrl_00001"
             android:gravity="center"
             android:tintMode="multiply"
-            android:tint="?attr/colorButtonNormal"
-            android:alpha="?attr/disabledAlpha" />
+            android:tint="?attr/colorButtonNormal" />
     </item>
     <item
         android:state_checked="true"
diff --git a/core/res/res/drawable/switch_track_material.xml b/core/res/res/drawable/switch_track_material.xml
index b400644..0728055 100644
--- a/core/res/res/drawable/switch_track_material.xml
+++ b/core/res/res/drawable/switch_track_material.xml
@@ -18,12 +18,7 @@
     <item android:state_enabled="false" android:state_checked="true">
         <nine-patch android:src="@drawable/switch_track_mtrl_alpha"
             android:tint="?attr/colorControlActivated"
-            android:alpha="0.15" />
-    </item>
-    <item android:state_enabled="false">
-        <nine-patch android:src="@drawable/switch_track_mtrl_alpha"
-            android:tint="?attr/colorButtonNormal"
-            android:alpha="0.15" />
+            android:alpha="0.2" />
     </item>
     <item android:state_checked="true">
         <nine-patch android:src="@drawable/switch_track_mtrl_alpha"
diff --git a/core/res/res/raw/color_fade_frag.frag b/core/res/res/raw/color_fade_frag.frag
new file mode 100644
index 0000000..a66a5a7
--- /dev/null
+++ b/core/res/res/raw/color_fade_frag.frag
@@ -0,0 +1,42 @@
+#extension GL_OES_EGL_image_external : require
+
+precision mediump float;
+uniform samplerExternalOES texUnit;
+uniform float opacity;
+uniform float saturation;
+uniform float gamma;
+varying vec2 UV;
+
+vec3 rgb2hsl(vec3 rgb)
+{
+    float e = 1.0e-7;
+
+    vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0);
+    vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);
+
+    float v = q.x;
+    float c = v - min(q.w, q.y);
+    float h = abs((q.w - q.y) / (6.0 * c + e) + q.z);
+    float l = v - c * 0.5;
+    float s = c / (1.0 - abs(2.0 * l - 1.0) + e);
+    return clamp(vec3(h, s, l), 0.0, 1.0);
+}
+
+vec3 hsl2rgb(vec3 hsl)
+{
+    vec3 h = vec3(hsl.x * 6.0);
+    vec3 p = abs(h - vec3(3.0, 2.0, 4.0));
+    vec3 q = 2.0 - p;
+
+    vec3 rgb = clamp(vec3(p.x - 1.0, q.yz), 0.0, 1.0);
+    float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
+    return (rgb - vec3(0.5)) * c + hsl.z;
+}
+
+void main()
+{
+    vec4 color = texture2D(texUnit, UV);
+    vec3 hsl = rgb2hsl(color.xyz);
+    vec3 rgb = pow(hsl2rgb(vec3(hsl.x, hsl.y * saturation, hsl.z * opacity)), vec3(gamma));
+    gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/core/res/res/raw/color_fade_vert.vert b/core/res/res/raw/color_fade_vert.vert
new file mode 100644
index 0000000..d17437f
--- /dev/null
+++ b/core/res/res/raw/color_fade_vert.vert
@@ -0,0 +1,13 @@
+uniform mat4 proj_matrix;
+uniform mat4 tex_matrix;
+uniform float scale;
+attribute vec2 position;
+attribute vec2 uv;
+varying vec2 UV;
+
+void main()
+{
+    vec4 transformed_uv = tex_matrix * vec4(uv.x, uv.y, 1.0, 1.0);
+    UV = transformed_uv.st / transformed_uv.q;
+    gl_Position = vec4(scale, scale, 1.0, 1.0) * (proj_matrix * vec4(position.x, position.y, 0.0, 1.0));
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2baa599..f6c0d71 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4260,8 +4260,6 @@
         <attr name="internalLayout" format="reference"  />
         <!-- @hide The layout of the legacy DatePicker. -->
         <attr name="legacyLayout" />
-        <!-- @hide Enables or disable the use of the legacy layout for the DatePicker. -->
-        <attr name="legacyMode" />
         <!-- The background color for the date selector 's day of week. -->
         <attr name="dayOfWeekBackgroundColor" format="color" />
         <!-- The text color for the date selector's day of week. -->
@@ -4288,6 +4286,15 @@
              if the text color does not explicitly have a color set for the
              selected state. -->
         <attr name="calendarSelectedTextColor" format="color" />
+        <!-- Defines the look of the widget. Prior to the L release, the only choice was
+             spinner. As of L, with the Material theme selected, the default layout is calendar,
+             but this attribute can be used to force spinner to be used instead. -->
+        <attr name="datePickerMode">
+            <!-- Date picker with spinner controls to select the date. -->
+            <enum name="spinner" value="1" />
+            <!-- Date picker with calendar to select the date. -->
+            <enum name="calendar" value="2" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="TwoLineListItem">
@@ -4557,8 +4564,6 @@
     </declare-styleable>
 
     <declare-styleable name="TimePicker">
-        <!-- @hide Enables or disable the use of the legacy layout for the TimePicker. -->
-        <attr name="legacyMode" format="boolean" />
         <!-- @hide The layout of the legacy time picker. -->
         <attr name="legacyLayout" format="reference" />
         <!-- @hide The layout of the time picker. -->
@@ -4587,6 +4592,15 @@
         <attr name="amPmSelectedBackgroundColor" format="color" />
         <!-- The color for the hours/minutes selector of the TimePicker. -->
         <attr name="numbersSelectorColor" format="color" />
+        <!-- Defines the look of the widget. Prior to the L release, the only choice was
+             spinner. As of L, with the Material theme selected, the default layout is clock,
+             but this attribute can be used to force spinner to be used instead. -->
+        <attr name="timePickerMode">
+            <!-- Time picker with spinner controls to select the time. -->
+            <enum name="spinner" value="1" />
+            <!-- Time picker with clock face to select the time. -->
+            <enum name="clock" value="2" />
+        </attr>
     </declare-styleable>
 
     <!-- ========================= -->
@@ -4912,6 +4926,7 @@
     <declare-styleable name="InsetDrawable">
         <attr name="visible" />
         <attr name="drawable" />
+        <attr name="inset"  format="dimension"/>
         <attr name="insetLeft" format="dimension" />
         <attr name="insetRight" format="dimension" />
         <attr name="insetTop" format="dimension" />
@@ -5194,13 +5209,13 @@
         <attr name="name" />
         <!-- The width a path stroke -->
         <attr name="strokeWidth" format="float" />
-        <!-- The opacity of a path stroke -->
+        <!-- The opacity of a path stroke @hide-->
         <attr name="strokeOpacity" format="float" />
         <!-- The color to stroke the path if not defined implies no stroke-->
         <attr name="strokeColor" format="color" />
         <!-- The color to fill the path if not defined implies no fill-->
         <attr name="fillColor" format="color" />
-        <!-- The level of opacity of the filled area of the path -->
+        <!-- The level of opacity of the filled area of the path @hide-->
         <attr name="fillOpacity" format="float" />
         <!-- The specification of the operations that define the path  -->
         <attr name="pathData" format="string" />
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index bae8e8d..c21dc59 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -68,4 +68,7 @@
     <dimen name="control_padding_material">4dp</dimen>
     <!-- Default rounded corner for controls -->
     <dimen name="control_corner_material">2dp</dimen>
+
+    <!-- Default alpha value for disabled elements. -->
+    <item name="disabled_alpha_material" format="float" type="dimen">0.26</item>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 08398f0..67352d7 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2269,6 +2269,12 @@
   <public type="attr" name="windowReenterTransition" />
   <public type="attr" name="windowSharedElementReturnTransition" />
   <public type="attr" name="windowSharedElementReenterTransition" />
+  <public type="attr" name="contentRatingSystemXml"/>
+  <public type="attr" name="datePickerMode"/>
+  <public type="attr" name="timePickerMode"/>
+  <public type="attr" name="inset" />
+  <public type="attr" name="letterSpacing" />
+  <public type="attr" name="fontFeatureSettings" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
@@ -2561,5 +2567,22 @@
   <!-- WebView error page for when domain lookup fails. @hide @SystemApi -->
   <public type="raw" name="nodomain"/>
 
-  <public type="attr" name="contentRatingSystemXml"/>
+  <!-- Base text appearance for SystemUI elements -->
+  <public type="style" name="TextAppearance.StatusBar.Material" />
+  <!-- Base text appearance for notifications -->
+  <public type="style" name="TextAppearance.StatusBar.Material.EventContent" />
+  <!-- Notification text appearance: title -->
+  <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Title" />
+  <!-- Notification text appearance: additional line of text sandwiched
+       between the title and content -->
+  <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Line2" />
+  <!-- Notification text appearance: an annotation, e.g. the
+       number of messages in your inbox -->
+  <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Info" />
+  <!-- Notification text appearance: timestamp -->
+  <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Time" />
+  <!-- Notification text appearance: a way to highlight a bit of text (for
+       example, to separate the sender from the subject of an email if they
+       are all on the same line) -->
+  <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Emphasis" />
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4761f1a..dd1d433 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -154,6 +154,10 @@
     <string name="ClipMmi">Incoming Caller ID</string>
     <!-- Displayed as the title for a success/failure report enabling/disabling caller ID. -->
     <string name="ClirMmi">Outgoing Caller ID</string>
+    <!-- Displayed as the title for a success/failure report enabling/disabling connected line ID. -->
+    <string name="ColpMmi">Connected Line ID</string>
+    <!-- Displayed as the title for a success/failure report enabling/disabling connected line ID restriction. -->
+    <string name="ColrMmi">Connected Line ID Restriction</string>
     <!-- Displayed as the title for a success/failure report enabling/disabling call forwarding. -->
     <string name="CfMmi">Call forwarding</string>
     <!-- Displayed as the title for a success/failure report enabling/disabling call waiting. -->
@@ -2540,15 +2544,19 @@
     Contact your wireless service provider for another SIM card.</string>
 
     <!-- Shown on transport control of lockscreen. Pressing button goes to previous track. -->
-    <string name="lockscreen_transport_prev_description">Previous track button</string>
+    <string name="lockscreen_transport_prev_description">Previous track</string>
     <!-- Shown on transport control of lockscreen. Pressing button goes to next track. -->
-    <string name="lockscreen_transport_next_description">Next track button</string>
+    <string name="lockscreen_transport_next_description">Next track</string>
     <!-- Shown on transport control of lockscreen. Pressing button pauses playback -->
-    <string name="lockscreen_transport_pause_description">Pause button</string>
+    <string name="lockscreen_transport_pause_description">Pause</string>
     <!-- Shown on transport control of lockscreen. Pressing button pauses playback -->
-    <string name="lockscreen_transport_play_description">Play button</string>
+    <string name="lockscreen_transport_play_description">Play</string>
     <!-- Shown on transport control of lockscreen. Pressing button pauses playback -->
-    <string name="lockscreen_transport_stop_description">Stop button</string>
+    <string name="lockscreen_transport_stop_description">Stop</string>
+    <!-- Shown on transport control screens. Pressing button rewinds playback [CHAR LIMIT=NONE]-->
+    <string name="lockscreen_transport_rew_description">Rewind</string>
+    <!-- Shown on transport control screens. Pressing button fast forwards playback [CHAR LIMIT=NONE]-->
+    <string name="lockscreen_transport_ffw_description">Fast forward</string>
 
     <!-- Shown in the lock screen when there is emergency calls only mode. -->
     <string name="emergency_calls_only" msgid="2485604591272668370">Emergency calls only</string>
@@ -4134,6 +4142,10 @@
     <string name="time_picker_increment_set_pm_button">Set PM</string>
     <!-- Description of the button to decrease the TimePicker's set AM value. [CHAR LIMIT=NONE] -->
     <string name="time_picker_decrement_set_am_button">Set AM</string>
+    <!-- Label for the TimePicker's PM button. [CHAR LIMIT=2] -->
+    <string name="time_picker_pm_label">PM</string>
+    <!-- Label for the TimePicker's AM button. [CHAR LIMIT=2] -->
+    <string name="time_picker_am_label">AM</string>
 
     <!-- DatePicker - accessibility support -->
     <!-- Description of the button to increase the DatePicker's month value. [CHAR LIMIT=NONE] -->
@@ -5061,5 +5073,5 @@
     <!-- TV content rating system strings for ZA TV -->
 
     <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
-    <string name="battery_saver_description">To help improve battery life, battery saver will reduce your device’s performance and restrict background data.  Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
+    <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 4a70952..e693d91 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -568,12 +568,12 @@
     </style>
 
     <style name="Widget.TimePicker">
-        <item name="legacyMode">true</item>
+        <item name="timePickerMode">spinner</item>
         <item name="legacyLayout">@layout/time_picker_legacy</item>
     </style>
 
     <style name="Widget.DatePicker">
-        <item name="legacyMode">true</item>
+        <item name="datePickerMode">spinner</item>
         <item name="legacyLayout">@layout/date_picker_legacy</item>
         <item name="calendarViewShown">false</item>
     </style>
@@ -1105,26 +1105,32 @@
 
     <style name="MediaButton.Previous">
         <item name="src">@drawable/ic_media_previous</item>
+        <item name="contentDescription">@string/lockscreen_transport_prev_description</item>
     </style>
 
     <style name="MediaButton.Next">
         <item name="src">@drawable/ic_media_next</item>
+        <item name="contentDescription">@string/lockscreen_transport_next_description</item>
     </style>
 
     <style name="MediaButton.Play">
         <item name="src">@drawable/ic_media_play</item>
+        <item name="contentDescription">@string/lockscreen_transport_play_description</item>
     </style>
 
     <style name="MediaButton.Ffwd">
         <item name="src">@drawable/ic_media_ff</item>
+        <item name="contentDescription">@string/lockscreen_transport_ffw_description</item>
     </style>
 
     <style name="MediaButton.Rew">
         <item name="src">@drawable/ic_media_rew</item>
+        <item name="contentDescription">@string/lockscreen_transport_rew_description</item>
     </style>
 
     <style name="MediaButton.Pause">
         <item name="src">@drawable/ic_media_pause</item>
+        <item name="contentDescription">@string/lockscreen_transport_pause_description</item>
     </style>
 
     <style name="ZoomControls">
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index d79e46d..2a54ccf 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -462,7 +462,7 @@
     </style>
 
     <style name="Widget.Holo.TimePicker" parent="Widget.TimePicker">
-        <item name="legacyMode">true</item>
+        <item name="timePickerMode">spinner</item>
         <item name="legacyLayout">@layout/time_picker_legacy_holo</item>
         <!-- Attributes for new-style TimePicker. -->
         <item name="internalLayout">@layout/time_picker_holo</item>
@@ -479,7 +479,7 @@
     </style>
 
     <style name="Widget.Holo.DatePicker" parent="Widget.DatePicker">
-        <item name="legacyMode">true</item>
+        <item name="datePickerMode">spinner</item>
         <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
         <item name="internalLayout">@layout/date_picker_holo</item>
         <item name="calendarViewShown">true</item>
@@ -886,7 +886,7 @@
     <style name="Widget.Holo.Light.NumberPicker" parent="Widget.Holo.NumberPicker" />
 
     <style name="Widget.Holo.Light.TimePicker" parent="Widget.TimePicker">
-        <item name="legacyMode">true</item>
+        <item name="timePickerMode">spinner</item>
         <item name="legacyLayout">@layout/time_picker_legacy_holo</item>
         <!-- Non-legacy styling -->
         <item name="internalLayout">@layout/time_picker_holo</item>
@@ -903,7 +903,7 @@
     </style>
 
     <style name="Widget.Holo.Light.DatePicker" parent="Widget.DatePicker">
-        <item name="legacyMode">true</item>
+        <item name="datePickerMode">spinner</item>
         <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
         <item name="internalLayout">@layout/date_picker_holo</item>
         <item name="calendarViewShown">true</item>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index a899c4d..256ef00 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -23,12 +23,12 @@
     </style>
 
     <style name="Widget.Leanback.TimePicker" parent="Widget.Material.TimePicker">
-        <item name="legacyMode">true</item>
+        <item name="timePickerMode">spinner</item>
         <item name="legacyLayout">@layout/time_picker_legacy_leanback</item>
     </style>
 
     <style name="Widget.Leanback.DatePicker" parent="Widget.Material.DatePicker">
-        <item name="legacyMode">true</item>
+        <item name="datePickerMode">spinner</item>
     </style>
 
     <style name="Widget.Leanback.NumberPicker" parent="Widget.Material.NumberPicker">
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 2dc0438..97d4bf6 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -587,7 +587,7 @@
     </style>
 
     <style name="Widget.Material.TimePicker" parent="Widget.TimePicker">
-        <item name="legacyMode">false</item>
+        <item name="timePickerMode">clock</item>
         <item name="legacyLayout">@layout/time_picker_legacy_holo</item>
         <!-- Attributes for new-style TimePicker. -->
         <item name="internalLayout">@layout/time_picker_holo</item>
@@ -604,7 +604,7 @@
     </style>
 
     <style name="Widget.Material.DatePicker" parent="Widget.DatePicker">
-        <item name="legacyMode">false</item>
+        <item name="datePickerMode">calendar</item>
         <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
         <!-- Attributes for new-style DatePicker. -->
         <item name="internalLayout">@layout/date_picker_holo</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 84bc62c..250711e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -382,6 +382,8 @@
   <java-symbol type="string" name="CfMmi" />
   <java-symbol type="string" name="ClipMmi" />
   <java-symbol type="string" name="ClirMmi" />
+  <java-symbol type="string" name="ColpMmi" />
+  <java-symbol type="string" name="ColrMmi" />
   <java-symbol type="string" name="CwMmi" />
   <java-symbol type="string" name="Midnight" />
   <java-symbol type="string" name="Noon" />
@@ -976,6 +978,8 @@
   <java-symbol type="string" name="ssl_ca_cert_noti_by_unknown" />
   <java-symbol type="string" name="ssl_ca_cert_noti_managed" />
   <java-symbol type="string" name="ssl_ca_cert_warning" />
+  <java-symbol type="string" name="lockscreen_transport_play_description" />
+  <java-symbol type="string" name="lockscreen_transport_pause_description" />
 
   <java-symbol type="plurals" name="abbrev_in_num_days" />
   <java-symbol type="plurals" name="abbrev_in_num_hours" />
@@ -1262,6 +1266,8 @@
   <java-symbol type="xml" name="default_zen_mode_config" />
   <java-symbol type="xml" name="tv_content_rating_systems" />
 
+  <java-symbol type="raw" name="color_fade_vert" />
+  <java-symbol type="raw" name="color_fade_frag" />
   <java-symbol type="raw" name="accessibility_gestures" />
   <java-symbol type="raw" name="incognito_mode_start_page" />
   <java-symbol type="raw" name="loaderror" />
@@ -1969,9 +1975,7 @@
   <java-symbol type="array" name="config_cdma_home_system" />
   <java-symbol type="attr" name="headerSelectedTextColor" />
   <java-symbol type="attr" name="amPmSelectedBackgroundColor" />
-
-  <!--From SmsMessage-->
-  <!--Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet
-     string that's stored in 8-bit unpacked format) characters.-->
   <java-symbol type="bool" name="config_sms_decode_gsm_8bit_data" />
+  <java-symbol type="string" name="time_picker_am_label" />
+  <java-symbol type="string" name="time_picker_pm_label" />
 </resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index e7001c3..1376dfa 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -46,7 +46,7 @@
         <item name="colorForegroundInverse">@color/bright_foreground_material_light</item>
         <item name="colorBackground">@color/background_material_dark</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
-        <item name="disabledAlpha">0.5</item>
+        <item name="disabledAlpha">@dimen/disabled_alpha_material</item>
         <item name="backgroundDimAmount">0.6</item>
 
         <!-- Text styles -->
@@ -304,8 +304,8 @@
         <item name="actionButtonStyle">@style/Widget.Material.ActionButton</item>
         <item name="actionOverflowButtonStyle">@style/Widget.Material.ActionButton.Overflow</item>
         <item name="actionOverflowMenuStyle">@style/Widget.Material.PopupMenu.Overflow</item>
-        <item name="actionModeBackground">?attr/colorPrimaryDark</item>
-        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
+        <item name="actionModeBackground">@drawable/cab_background_top_material</item>
+        <item name="actionModeSplitBackground">@drawable/cab_background_bottom_material</item>
         <item name="actionModeCloseDrawable">@drawable/ic_cab_done_material</item>
         <item name="actionBarTabStyle">@style/Widget.Material.ActionBar.TabView</item>
         <item name="actionBarTabBarStyle">@style/Widget.Material.ActionBar.TabBar</item>
@@ -387,7 +387,7 @@
         <item name="colorForegroundInverse">@color/bright_foreground_material_dark</item>
         <item name="colorBackground">@color/background_material_light</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
-        <item name="disabledAlpha">0.5</item>
+        <item name="disabledAlpha">@dimen/disabled_alpha_material</item>
         <item name="backgroundDimAmount">0.6</item>
 
         <!-- Text styles -->
@@ -650,9 +650,8 @@
         <item name="actionButtonStyle">@style/Widget.Material.Light.ActionButton</item>
         <item name="actionOverflowButtonStyle">@style/Widget.Material.Light.ActionButton.Overflow</item>
         <item name="actionOverflowMenuStyle">@style/Widget.Material.Light.PopupMenu.Overflow</item>
-        <item name="actionBarPopupTheme">@null</item>
-        <item name="actionModeBackground">@drawable/cab_background_top_holo_light</item>
-        <item name="actionModeSplitBackground">@drawable/cab_background_bottom_holo_light</item>
+        <item name="actionModeBackground">@drawable/cab_background_top_material</item>
+        <item name="actionModeSplitBackground">@drawable/cab_background_bottom_material</item>
         <item name="actionModeCloseDrawable">@drawable/ic_cab_done_material</item>
         <item name="actionBarTabStyle">@style/Widget.Material.Light.ActionBar.TabView</item>
         <item name="actionBarTabBarStyle">@style/Widget.Material.Light.ActionBar.TabBar</item>
@@ -663,6 +662,7 @@
         <item name="actionBarSize">@dimen/action_bar_default_height_material</item>
         <item name="actionModePopupWindowStyle">@style/Widget.Material.Light.PopupWindow.ActionMode</item>
         <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarPopupTheme">@null</item>
         <item name="actionBarTheme">@style/ThemeOverlay.Material.ActionBar</item>
         <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
 
@@ -960,16 +960,8 @@
     </style>
 
     <!-- Theme for the search input bar. -->
-
-    <style name="Theme.Material.SearchBar" parent="Theme.Material.Panel">
-        <item name="actionModeBackground">@drawable/cab_background_top_holo_dark</item>
-        <item name="actionModeSplitBackground">@drawable/cab_background_bottom_holo_light</item>
-    </style>
-
-    <style name="Theme.Material.Light.SearchBar" parent="Theme.Material.Light.Panel">
-        <item name="actionModeBackground">@drawable/cab_background_top_holo_light</item>
-        <item name="actionModeSplitBackground">@drawable/cab_background_bottom_holo_light</item>
-    </style>
+    <style name="Theme.Material.SearchBar" parent="Theme.Material.Panel" />
+    <style name="Theme.Material.Light.SearchBar" parent="Theme.Material.Light.Panel" />
 
     <!-- Menu Themes -->
     <eat-comment />
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
new file mode 100644
index 0000000..a4d4906
--- /dev/null
+++ b/data/fonts/fonts.xml
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="utf-8"?>
+<familyset version="22">
+    <!-- first font is default -->
+    <family name="sans-serif">
+        <font weight="100" style="normal">Roboto-Thin.ttf</font>
+        <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
+        <font weight="300" style="normal">Roboto-Light.ttf</font>
+        <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
+        <font weight="400" style="normal">Roboto-Regular.ttf</font>
+        <font weight="400" style="italic">Roboto-Italic.ttf</font>
+        <font weight="500" style="normal">Roboto-Medium.ttf</font>
+        <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
+        <font weight="700" style="normal">Roboto-Bold.ttf</font>
+        <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
+        <font weight="900" style="normal">Roboto-Black.ttf</font>
+        <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
+    </family>
+
+    <!-- Note that aliases must come after the fonts they reference. -->
+    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+    <alias name="sans-serif-light" to="sans-serif" weight="300" />
+    <alias name="sans-serif-black" to="sans-serif" weight="900" />
+    <alias name="arial" to="sans-serif" />
+    <alias name="helvetica" to="sans-serif" />
+    <alias name="tahoma" to="sans-serif" />
+    <alias name="verdana" to="sans-serif" />
+
+    <family name="sans-serif-condensed">
+        <font weight="300" style="normal">RobotoCondensed-Light.ttf</font>
+        <font weight="300" style="italic">RobotoCondensed-LightItalic.ttf</font>
+        <font weight="400" style="normal">RobotoCondensed-Regular.ttf</font>
+        <font weight="400" style="italic">RobotoCondensed-Italic.ttf</font>
+        <font weight="700" style="normal">RobotoCondensed-Bold.ttf</font>
+        <font weight="700" style="italic">RobotoCondensed-BoldItalic.ttf</font>
+    </family>
+    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+
+    <family name="serif">
+        <font weight="400" style="normal">NotoSerif-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+    </family>
+    <alias name="times" to="serif" />
+    <alias name="times new roman" to="serif" />
+    <alias name="palatino" to="serif" />
+    <alias name="georgia" to="serif" />
+    <alias name="baskerville" to="serif" />
+    <alias name="goudy" to="serif" />
+    <alias name="fantasy" to="serif" />
+    <alias name="ITC Stone Serif" to="serif" />
+
+    <family name="monospace">
+        <font weight="400" style="normal">DroidSansMono.ttf</font>
+    </family>
+    <alias name="courier" to="monospace" />
+    <alias name="courier new" to="monospace" />
+    <alias name="monaco" to="monospace" />
+
+    <family name="casual">
+        <font weight="400" style="normal">ComingSoon.ttf</font>
+    </family>
+
+    <family name="cursive">
+        <font weight="400" style="normal">DancingScript-Regular.ttf</font>
+        <font weight="700" style="normal">DancingScript-Bold.ttf</font>
+    </family>
+
+    <family name="sans-serif-smallcaps">
+        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+    </family>
+
+    <!-- fallback fonts -->
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoNaskh-Regular.ttf</font>
+        <font weight="700" style="normal">NotoNaskh-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoNaskhUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoNaskhUI-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansThai-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansGeorgian-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansDevanagari-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansDevanagari-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansDevanagariUI-Bold.ttf</font>
+    </family>
+    <!-- Gujarati should come after Devanagari -->
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansGujarati-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+    </family>
+    <!-- Gurmukhi should come after Devanagari -->
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansGurmukhi-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansGurmukhi-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansGurmukhiUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansTamil-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansTamil-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansTamilUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansMalayalam-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansMalayalam-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansMalayalamUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansBengali-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansBengali-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansBengaliUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansTelugu-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansTelugu-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansTeluguUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansKannada-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansKannada-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansKannadaUI-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSinhala-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansSinhala-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansKhmer-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansKhmer-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+    </family>
+    <family variant="elegant">
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.ttf</font>
+    </family>
+    <family variant="compact">
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansYi-Regular.ttf</font>
+    </family>
+    <family lang="zh-Hans">
+        <font weight="400" style="normal">NotoSansHans-Regular.otf</font>
+    </family>
+    <family lang="zh-Hant">
+        <font weight="400" style="normal">NotoSansHant-Regular.otf</font>
+    </family>
+    <family lang="ja">
+        <font weight="400" style="normal">NotoSansJP-Regular.otf</font>
+    </family>
+    <family lang="ko">
+        <font weight="400" style="normal">NotoSansKR-Regular.otf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NanumGothic.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+    </family>
+    <family lang="ja">
+        <font weight="400" style="normal">MTLmr3m.ttf</font>
+    </family>
+</familyset>
diff --git a/docs/html/images/tools/signadt3.png b/docs/html/images/tools/signadt3.png
new file mode 100644
index 0000000..3f5650b
--- /dev/null
+++ b/docs/html/images/tools/signadt3.png
Binary files differ
diff --git a/docs/html/images/tools/signadt4.png b/docs/html/images/tools/signadt4.png
new file mode 100644
index 0000000..2adfff0
--- /dev/null
+++ b/docs/html/images/tools/signadt4.png
Binary files differ
diff --git a/docs/html/images/tools/signadt5.png b/docs/html/images/tools/signadt5.png
new file mode 100644
index 0000000..e1861e8
--- /dev/null
+++ b/docs/html/images/tools/signadt5.png
Binary files differ
diff --git a/docs/html/images/tools/signstudio1.png b/docs/html/images/tools/signstudio1.png
new file mode 100644
index 0000000..374e04f
--- /dev/null
+++ b/docs/html/images/tools/signstudio1.png
Binary files differ
diff --git a/docs/html/images/tools/signstudio10.png b/docs/html/images/tools/signstudio10.png
new file mode 100644
index 0000000..5b9ae6d
--- /dev/null
+++ b/docs/html/images/tools/signstudio10.png
Binary files differ
diff --git a/docs/html/images/tools/signstudio11.png b/docs/html/images/tools/signstudio11.png
new file mode 100644
index 0000000..0bf0d51
--- /dev/null
+++ b/docs/html/images/tools/signstudio11.png
Binary files differ
diff --git a/docs/html/images/tools/signstudio2.png b/docs/html/images/tools/signstudio2.png
new file mode 100644
index 0000000..5e8e416
--- /dev/null
+++ b/docs/html/images/tools/signstudio2.png
Binary files differ
diff --git a/docs/html/images/tools/signstudio3.png b/docs/html/images/tools/signstudio3.png
new file mode 100644
index 0000000..f25bf00
--- /dev/null
+++ b/docs/html/images/tools/signstudio3.png
Binary files differ
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index 864e82e..851827c 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -1,8 +1,8 @@
 page.title=Installing the Eclipse Plugin
-adt.zip.version=23.0.2
-adt.zip.download=ADT-23.0.2.zip
-adt.zip.bytes=103287135
-adt.zip.checksum=cde1d0a463b5ccce844b63161cfa1cb9
+adt.zip.version=23.0.3
+adt.zip.download=ADT-23.0.3.zip
+adt.zip.bytes=103321934
+adt.zip.checksum=ab2f5e2fbbdddeeb7dfd02cd4046538a
 
 @jd:body
 
diff --git a/docs/html/tools/publishing/app-signing.jd b/docs/html/tools/publishing/app-signing.jd
index 1de1fd7..53e6e65 100644
--- a/docs/html/tools/publishing/app-signing.jd
+++ b/docs/html/tools/publishing/app-signing.jd
@@ -4,33 +4,26 @@
 <div id="qv-wrapper">
 <div id="qv">
 
-<h2>Quickview</h2>
-
-<ul>
-<li>All Android apps <em>must</em> be signed</li>
-<li>You can sign with a self-signed key</li>
-<li>How you sign your apps is critical &mdash; read this document carefully</li>
-<li>Determine your signing strategy early in the development process</li>
-</ul>
-
 <h2>In this document</h2>
 
 <ol>
-<li><a href="#signing">Signing Process</a></li>
-<li><a href="#strategies">Signing Strategies</a></li>
-<li><a href="#setup">Basic Setup for Signing</a></li>
-<li><a href="#debugmode">Signing in Debug Mode</a></li>
-<li><a href="#releasemode">Signing Release Mode</a>
-    <ol>
-    <li><a href="#cert">Obtain a suitable private key</a></li>
-    <li><a href="#releasecompile">Compile the application in release mode</a></li>
-    <li><a href="#signapp">Sign your application with your private key</a></li>
-    <li><a href="#align">Align the final APK package</a></li>
-    <li><a href="#ExportWizard">Compile and sign with Eclipse ADT</a></li>
-    </ol>
+<li><a href="#overview">Signing Overview</a>
+  <ol>
+  	<li><a href="#debug-mode">Signing in Debug Mode</a></li>
+  	<li><a href="#release-mode">Signing in Release Mode</a></li>
+  	<li><a href="#wear-apps">Signing Android Wear Apps</a></li>
+  </ol>
 </li>
+<li><a href="#studio">Signing Your App in Android Studio</a>
+  <ol>
+  	<li><a href="sign-auto">Automatically Signing Your App</a></li>
+  </ol>
+</li>
+<li><a href="#adt">Signing Your App with the ADT plugin for Eclipse</a></li>
+<li><a href="#considerations">Signing Considerations</a></li>
 <li><a href="#secure-key">Securing Your Private Key</a></li>
-
+<li><a href="#expdebug">Expiry of the Debug Certificate</a></li>
+<li><a href="#signing-manually">Signing Your App Manually</a></li>
 </ol>
 
 <h2>See also</h2>
@@ -43,588 +36,288 @@
 </div>
 </div>
 
-<p>The Android system requires that all installed applications be digitally signed with a
-certificate whose private key is held by the application's developer. The Android system uses the
-certificate as a means of identifying the author of an application and establishing trust
-relationships between applications. The certificate is not used to control which applications the
-user can install. The certificate does not need to be signed by a certificate authority: it is
-perfectly allowable, and typical, for Android applications to use self-signed certificates.</p>
 
-<p>The important points to understand about signing Android applications are:</p>
-
-<ul>
-  <li>All applications <em>must</em> be signed. The system will not install an application
-on an emulator or a device if it is not signed.</li>
-  <li>To test and debug your application, the build tools sign your application with a special debug
-    key that is created by the Android SDK build tools.</li>
-  <li>When you are ready to release your application for end-users, you must sign it with a suitable
-    private key. You cannot publish an application that is signed with the debug key generated
-    by the SDK tools.</li>
-  <li>You can use self-signed certificates to sign your applications. No certificate authority is
-    needed.</li>
-  <li>The system tests a signer certificate's expiration date only at install time. If an
-application's signer certificate expires after the application is installed, the application
-will continue to function normally.</li>
-  <li>You can use standard tools &mdash; Keytool and Jarsigner &mdash; to generate keys and
-sign your application {@code .apk} files.</li>
-  <li>After you sign your application for release, we recommend that you use the
-    <code>zipalign</code> tool to optimize the final APK package.</li>
-</ul>
-
-<p>The Android system will not install or run an application that is not signed appropriately. This
-applies wherever the Android system is run, whether on an actual device or on the emulator.
-For this reason, you must <a href="#setup">set up signing</a> for your application before you can
-run it or debug it on an emulator or device.</p>
-
-<h2 id="signing">Signing Process</h3>
-
-<p>The Android build process signs your application differently depending on which build mode you
-use to build your application. There are two build modes: <em>debug mode</em> and <em>release
-mode</em>. You use debug mode when you are developing and testing your application. You use
-release mode when you want to build a release version of your application that you can
-distribute directly to users or publish on an application marketplace such as Google Play.</p>
-
-<p>When you build in <em>debug mode</em> the Android SDK build tools use the Keytool utility
-(included in the JDK) to create a debug key. Because the SDK build tools created the debug key,
-they know the debug key's alias and password. Each time you compile your application in debug mode,
-the build tools use the debug key along with the Jarsigner utility (also included in the JDK) to
-sign your application's <code>.apk</code> file. Because the alias and password are known to the SDK
-build tools, the tools don't need to prompt you for the debug key's alias and password each time
-you compile.</p>
-
-<p>When you build in <em>release mode</em> you use your own private key to sign your application. If
-you don't have a private key, you can use the Keytool utility to create one for you. When you
-compile your application in release mode, the build tools use your private key along with the
-Jarsigner utility to sign your application's <code>.apk</code> file. Because the certificate and
-private key you use are your own, you must provide the password for the keystore and key alias.</p>
-
-<p>The debug signing process happens automatically when you run or debug your application using
-Eclipse with the ADT plugin. Debug signing also happens automatically when you use the Ant build
-script with the <code>debug</code> option. You can automate the release signing process by using the
-Eclipse Export Wizard or by modifying the Ant build script and building with the
-<code>release</code> option.</p>
-
-<h2 id="strategies">Signing Strategies</h2>
-
-<p>Some aspects of application signing may affect how you approach the development
-of your application, especially if you are planning to release multiple
-applications. </p>
-
-<p>In general, the recommended strategy for all developers is to sign
-all of your applications with the same certificate, throughout the expected
-lifespan of your applications. There are several reasons why you should do so: </p>
-
-<ul>
-<li>Application upgrade &ndash; As you release updates to your application, you
-must continue to sign the updates with the same certificate or set of certificates,
-if you want users to be able to upgrade seamlessly to the new version. When
-the system is installing an update to an application, it compares the
-certificate(s) in the new version with those in the existing version. If the
-certificates match exactly, including both the certificate data and order, then
-the system allows the update. If you sign the new version without using matching
-certificates, you must also assign a different package name to the
-application &mdash; in this case, the user installs the new version as a
-completely new application. </li>
-
-<li>Application modularity &ndash; The Android system allows applications that
-are signed by the same certificate to run in the same process, if the
-applications so requests, so that the system treats them as a single application.
-In this way you can deploy your application in modules, and users can update
-each of the modules independently if needed.</li>
-
-<li>Code/data sharing through permissions &ndash; The Android system provides
-signature-based permissions enforcement, so that an application can expose
-functionality to another application that is signed with a specified
-certificate. By signing multiple applications with the same certificate and
-using signature-based permissions checks, your applications can share code and
-data in a secure manner. </li>
-
-</ul>
-
-<p>Another important consideration in determining your signing strategy is
-how to set the validity period of the key that you will use to sign your
-applications.</p>
-
-<ul>
-<li>If you plan to support upgrades for a single application, you should ensure
-that your key has a validity period that exceeds the expected lifespan of
-that application. A validity period of 25 years or more is recommended.
-When your key's validity period expires, users will no longer be
-able to seamlessly upgrade to new versions of your application.</li>
-
-<li>If you will sign multiple distinct applications with the same key,
-you should ensure that your key's validity period exceeds the expected
-lifespan of <em>all versions of all of the applications</em>, including
-dependent applications that may be added to the suite in the future. </li>
-
-<li>If you plan to publish your application(s) on Google Play, the
-key you use to sign the application(s) must have a validity period
-ending after 22 October 2033. Google Play enforces this requirement
-to ensure that users can seamlessly upgrade applications when
-new versions are available. </li>
-</ul>
-
-<p>As you design your application, keep these points in mind and make sure to
-use a <a href="#cert">suitable certificate</a> to sign your applications. </p>
-
-<h2 id="setup">Basic Setup for Signing</h2>
-
-<p>Before you begin, make sure that the Keytool utility and Jarsigner utility are available to
-the SDK build tools. Both of these tools are available in the JDK. In most cases, you can tell
-the SDK build tools how to find these utilities by setting your <code>JAVA_HOME</code> environment
-variable so it references a suitable JDK. Alternatively, you can add the JDK version of Keytool and
-Jarsigner to your <code>PATH</code> variable.</p>
-
-<p>If you are developing on a version of Linux that originally came with GNU Compiler for
-Java, make sure that the system is using the JDK version of Keytool, rather than the gcj
-version. If Keytool is already in your <code>PATH</code>, it might be pointing to a symlink at
-<code>/usr/bin/keytool</code>. In this case, check the symlink target to be sure it points
-to the Keytool in the JDK.</p>
-
-<h2 id="debugmode">Signing in Debug Mode</h2>
-
-<p>The Android build tools provide a debug signing mode that makes it easier for you
-to develop and debug your application, while still meeting the Android system
-requirement for signing your APK.
-When using debug mode to build your app, the SDK tools invoke Keytool to automatically create
-a debug keystore and key. This debug key is then used to automatically sign the APK, so
-you do not need to sign the package with your own key.</p>
-
-<p>The SDK tools create the debug keystore/key with predetermined names/passwords:</p>
-<ul>
-<li>Keystore name: "debug.keystore"</li>
-<li>Keystore password: "android"</li>
-<li>Key alias: "androiddebugkey"</li>
-<li>Key password: "android"</li>
-<li>CN: "CN=Android Debug,O=Android,C=US"</li>
-</ul>
-
-<p>If necessary, you can change the location/name of the debug keystore/key or
-supply a custom debug keystore/key to use. However, any custom debug
-keystore/key must use the same keystore/key names and passwords as the default
-debug key (as described above). (To do so in Eclipse/ADT, go to
-<strong>Windows</strong> &gt; <strong>Preferences</strong> &gt;
-<strong>Android</strong> &gt; <strong>Build</strong>.) </p>
-
-<p class="caution"><strong>Caution:</strong> You <em>cannot</em> release your application
-to the public when signed with the debug certificate.</p>
-
-<h3>Eclipse Users</h3>
-
-<p>If you are developing in Eclipse/ADT (and have set up Keytool and Jarsigner as described above in
-<a href="#setup">Basic Setup for Signing</a>),
-signing in debug mode is enabled by default. When you run or debug your
-application, ADT signs the {@code .apk} file with the debug certificate, runs {@code zipalign} on
-the package, then installs it on
-the selected emulator or connected device. No specific action on your part is needed,
-provided ADT has access to Keytool.</p>
-
-<h3>Ant Users</h3>
-
-<p>If you are using Ant to build your {@code .apk} file, debug signing mode
-is enabled by using the <code>debug</code> option with the <code>ant</code> command
-(assuming that you are using a <code>build.xml</code> file generated by the
-<code>android</code> tool). When you run <code>ant debug</code> to
-compile your app, the build script generates a keystore/key and signs the APK for you.
-The script then also aligns the APK with the <code>zipalign</code> tool.
-No other action on your part is needed. Read
-<a href="{@docRoot}tools/building/building-cmdline.html#DebugMode">Building and Running Apps
-on the Command Line</a> for more information.</p>
+<p>Android requires that all apps be digitally signed with a certificate before they can be
+installed. Android uses this certificate to identify the author of an app, and the certificate
+does not need to be signed by a certificate authority. Android apps often use self-signed
+certificates. The app developer holds the certificate's private key.</p>
 
 
-<h3 id="debugexpiry">Expiry of the Debug Certificate</h3>
+<h2 id="overview">Signing Overview</h2>
 
-<p>The self-signed certificate used to sign your application in debug mode (the default on
-Eclipse/ADT and Ant builds) will have an expiration date of 365 days from its creation date.</p>
+<p>You can sign an app in debug or release mode. You sign your app in debug mode during development
+and in release mode when you are ready to distribute your app. The Android SDK generates a
+certificate to sign apps in debug mode. To sign apps in release mode, you need to generate
+your own certificate.</p>
 
-<p>When the certificate expires, you will get a build error. On Ant builds, the error
-looks like this:</p>
+<h3 id="debug-mode">Signing in Debug Mode</h3>
 
-<pre>debug:
-[echo] Packaging bin/samples-debug.apk, and signing it with a debug key...
-[exec] Debug Certificate expired on 8/4/08 3:43 PM</pre>
+<p>In debug mode, you sign your app with a debug certificate generated by the Android SDK tools.
+This certificate has a private key with a known password, so you can run and debug your app
+without typing the password every time you make a change to your project.</p>
 
-<p>In Eclipse/ADT, you will see a similar error in the Android console.</p>
+<p>Android Studio and the ADT plugin for Eclipse sign your app in debug mode automatically when
+you run or debug your project from the IDE.</p>
 
-<p>To fix this problem, simply delete the <code>debug.keystore</code> file.
-The default storage location for AVDs is in <code>~/.android/</code> on OS X and Linux,
-in <code>C:\Documents and Settings\&lt;user>\.android\</code> on Windows XP, and in
-<code>C:\Users\&lt;user>\.android\</code> on Windows Vista and Windows 7.</p>
+<p>You can run and debug an app signed in debug mode on the emulator and on devices connected
+to your development manchine through USB, but you cannot distribute an app signed in debug
+mode.</p>
 
+<p>For more information about how to build and run apps in debug mode, see
+<a href="{@docRoot}tools/building/index.html">Building and Running</a>.</p>
 
-<p>The next time you build, the build tools will regenerate a new keystore and debug key.</p>
+<h3 id="release-mode">Signing in Release Mode</h3>
 
-<p>Note that, if your development machine is using a non-Gregorian locale, the build
-tools may erroneously generate an already-expired debug certificate, so that you get an
-error when trying to compile your application. For workaround information, see the
-troubleshooting topic <a href="{@docRoot}resources/faq/troubleshooting.html#signingcalendar">
-I&nbsp;can't&nbsp;compile my app because the build tools generated an expired debug
-certificate</a>. </p>
+<p>In release mode, you sign your app with your own certificate:</p>
 
-
-<h2 id="releasemode">Signing in Release Mode</h2>
-
-<p>When your application is ready for release to other users, you must:</p>
 <ol>
-  <li><a href="#cert">Obtain a suitable private key</a></li>
-  <li><a href="#releasecompile">Compile the application in release mode</a></li>
-  <li><a href="#signapp">Sign your application with your private key</a></li>
-  <li><a href="#align">Align the final APK package</a></li>
+<li><em>Create a keystore.</em> A <strong>keystore</strong> is a binary file that contains a
+set of private keys. You must keep your keystore in a safe and secure place.</li>
+<li><em>Create a private key.</em> A <strong>private key</strong> represents the entity to
+be identified with the app, such as a person or a company.</li>
+<li><em>Build your project</em>. Generate an unsigned APK for your app.</li>
+<li><em>Sign your app.</em> Use your private key to generate a signed version of your APK.</li>
 </ol>
 
-<p>If you are developing in Eclipse with the ADT plugin, you can use the Export Wizard
-to perform the compile, sign, and align procedures. The Export Wizard even allows you to
-generate a new keystore and private key in the process. So if you use Eclipse, you can
-skip to <a href="#ExportWizard">Compile and sign with Eclipse ADT</a>.</p>
+<p>After you complete this process, you can distribute your app and publish it on Google Play.</p>
 
-
-
-<h3 id="cert">1. Obtain a suitable private key</h3>
-
-<p>In preparation for signing your application, you must first ensure that
-you have a suitable private key with which to sign. A suitable private
-key is one that:</p>
-
-<ul>
-<li>Is in your possession</li>
-<li>Represents the personal, corporate, or organizational entity to be identified
-with the application</li>
-<li>Has a validity period that exceeds the expected lifespan of the application
-or application suite. A validity period of more than 25 years is recommended.
-<p>If you plan to publish your application(s) on Google Play, note that a
-validity period ending after 22 October 2033 is a requirement. You can not upload an
-application if it is signed with a key whose validity expires before that date.
-</p></li>
-<li>Is not the debug key generated by the Android SDK tools. </li>
-</ul>
-
-<p>The key may be self-signed. If you do not have a suitable key, you must
-generate one using Keytool. Make sure that you have Keytool available, as described
-in <a href="#setup">Basic Setup</a>.</p>
-
-<p>To generate a self-signed key with Keytool, use the <code>keytool</code>
-command and pass any of the options listed below (and any others, as
-needed). </p>
-
-<p class="warning"><strong>Warning:</strong> Keep your private key secure.
-Before you run Keytool, make sure to read
-<a href="#secure-key">Securing Your Private Key</a> for a discussion of how to keep
-your key secure and why doing so is critically important to you and to users. In
-particular, when you are generating your key, you should select strong passwords
-for both the keystore and key.</p>
-
-<p class="warning"><strong>Warning:</strong> Keep the keystore file you generate with Keytool
-in a safe, secure place. You must use the same key to sign future versions of your application. If
-you republish your app with a new key, Google Play will consider it a new app. For more information
-on settings that must remain constant over the life of your app, see the Android Developer Blog post
-<a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Things
-That Cannot Change</a>.</p>
-
-<table>
-<tr>
-<th>Keytool Option</th>
-<th>Description</th>
-</tr>
-<tr>
-<td><code>-genkey</code></td><td>Generate a key pair (public and private
-keys)</td>
-</tr>
-<tr>
-<td><code>-v</code></td><td>Enable verbose output.</td>
-</tr>
-<tr>
-<td><code>-alias &lt;alias_name&gt;</code></td><td>An alias for the key. Only
-the first 8 characters of the alias are used.</td>
-</tr>
-<tr>
-<td><code>-keyalg &lt;alg&gt;</code></td><td>The encryption algorithm to use
-when generating the key. Both DSA and RSA are supported.</td>
-</tr>
-<tr>
-<td><code>-keysize &lt;size&gt;</code></td><td>The size of each generated key
-(bits). If not supplied, Keytool uses a default key size of 1024 bits. In
-general, we recommend using a key size of 2048 bits or higher. </td>
-</tr>
-<tr>
-<td><code>-dname &lt;name&gt;</code></td><td><p>A Distinguished Name that describes
-who created the key. The value is used as the issuer and subject fields in the
-self-signed certificate. </p><p>Note that you do not need to specify this option
-in the command line. If not supplied, Jarsigner prompts you to enter each
-of the Distinguished Name fields (CN, OU, and so on).</p></td>
-</tr>
-<tr>
-<td><code>-keypass &lt;password&gt;</code></td><td><p>The password for the
-key.</p> <p>As a security precaution, do not include this option in your command
-line. If not supplied, Keytool prompts you to enter the password. In this way,
-your password is not stored in your shell history.</p></td>
-</tr>
-<tr>
-<td><code>-validity &lt;valdays&gt;</code></td><td><p>The validity period for the
-key, in days. </p><p><strong>Note:</strong> A value of 10000 or greater is recommended.</p></td>
-</tr>
-<tr>
-<td><code>-keystore&nbsp;&lt;keystore-name&gt;.keystore</code></td><td>A name
-for the keystore containing the private key.</td>
-</tr>
-<tr>
-<td><code>-storepass &lt;password&gt;</code></td><td><p>A password for the
-keystore.</p><p>As a security precaution, do not include this option in your
-command line. If not supplied, Keytool prompts you to enter the password. In
-this way, your password is not stored in your shell history.</p></td>
-</tr>
-</table>
-
-<p>Here's an example of a Keytool command that generates a private key:</p>
-
-<pre>$ keytool -genkey -v -keystore my-release-key.keystore
--alias alias_name -keyalg RSA -keysize 2048 -validity 10000</pre>
-
-<p>Running the example command above, Keytool prompts you to provide
-passwords for the keystore and key, and to provide the Distinguished
-Name fields for your key. It then generates the keystore as a file called
-<code>my-release-key.keystore</code>. The keystore and key are
-protected by the passwords you entered. The keystore contains
-a single key, valid for 10000 days. The alias is a name that you &mdash;
-will use later, to refer to this keystore when signing your application. </p>
-
-<p>For more information about Keytool, see the documentation at
-<a
-href="http://docs.oracle.com/javase/6/docs/technotes/tools/windows/keytool.html">
-http://docs.oracle.com/javase/6/docs/technotes/tools/windows/keytool.html</a></p>
-
-
-
-<h3 id="releasecompile">2. Compile the application in release mode</h3>
-
-<p>In order to release your application to users, you must compile it in release mode.
-In release mode, the compiled application is not signed by default and you will need
-to sign it with your private key.</p>
-
-<p class="caution"><strong>Caution:</strong>
-You can not release your application unsigned, or signed with the debug key.</p>
-
-<h4>With Eclipse</h4>
-
-<p>To export an <em>unsigned</em> APK from Eclipse, right-click the project in the Package
-Explorer and select <strong>Android Tools</strong> > <strong>Export Unsigned Application
-Package</strong>. Then specify the file location for the unsigned APK.
-(Alternatively, open your <code>AndroidManifest.xml</code> file in Eclipse, select
-the <strong>Manifest</strong> tab, and click <strong>Export an unsigned APK</strong>.)</p>
-
-<p>Note that you can combine the compiling and signing steps with the Export Wizard. See
-<a href="#ExportWizard">Compiling and signing with Eclipse ADT</a>.</p>
-
-<h4>With Ant</h4>
-
-<p>If you are using Ant, you can enable release mode by using the <code>release</code> option
-with the <code>ant</code> command. For example, if you are running Ant from the
-directory containing your {@code build.xml} file, the command would look like this:</p>
-
-<pre>$ ant release</pre>
-
-<p>By default, the build script compiles the application APK without signing it. The output file
-in your project {@code bin/} will be <code><em>&lt;your_project_name></em>-unsigned.apk</code>.
-Because the application APK is still unsigned, you must manually sign it with your private
-key and then align it using {@code zipalign}.</p>
-
-<p>However, the Ant build script can also perform the signing
-and aligning for you, if you have provided the path to your keystore and the name of
-your key alias in the project's {@code ant.properties} file. With this information provided,
-the build script will prompt you for your keystore and alias password when you perform
-<code>ant release</code>, it will sign the package and then align it. The final output
-file in {@code bin/} will instead be
-<code><em>&lt;your_project_name></em>-release.apk</code>. With these steps
-automated for you, you're able to skip the manual procedures below (steps 3 and 4).
-To learn how to specify your keystore and alias in the {@code ant.properties} file,
-see <a href="{@docRoot}tools/building/building-cmdline.html#ReleaseMode">
-Building and Running Apps on the Command Line</a>.</p>
-
-
-
-<h3 id="signapp">3. Sign your application with your private key</h3>
-
-<p>When you have an application package that is ready to be signed, you can do sign it
-using the Jarsigner tool. Make sure that you have Jarsigner available on your
-machine, as described in <a href="#setup">Basic Setup</a>. Also, make sure that
-the keystore containing your private key is  available.</p>
-
-<p>To sign your application, you run Jarsigner, referencing both the
-application's APK and the keystore containing the private key with which to
-sign the APK. The table below shows the options you could use. </p>
-
-<table>
-<tr>
-<th>Jarsigner Option</th>
-<th>Description</th>
-</tr>
-<tr>
-<td><code>-keystore&nbsp;&lt;keystore-name&gt;.keystore</code></td><td>The name of
-the keystore containing your private key.</td>
-</tr>
-<tr>
-<td><code>-verbose</code></td><td>Enable verbose output.</td>
-</tr>
-<tr>
-<td><code>-sigalg</code></td><td>The name of the signature algorithim to use in signing the APK.
-Use the value {@code SHA1withRSA}.</td>
-</tr>
-<tr>
-<td><code>-digestalg</code></td><td>The message digest algorithim to use in processing the entries
-of an APK. Use the value {@code SHA1}.</td>
-</tr>
-<tr>
-<td><code>-storepass &lt;password&gt;</code></td><td><p>The password for the
-keystore. </p><p>As a security precaution, do not include this option
-in your command line unless you are working at a secure computer.
-If not supplied, Jarsigner prompts you to enter the password. In this
-way, your password is not stored in your shell history.</p></td>
-</tr>
-<tr>
-<td><code>-keypass &lt;password&gt;</code></td><td><p>The password for the private
-key. </p><p>As a security precaution, do not include this option
-in your command line unless you are working at a secure computer.
-If not supplied, Jarsigner prompts you to enter the password. In this
-way, your password is not stored in your shell history.</p></td>
-</tr>
-</table>
-
-<p>Here's how you would use Jarsigner to sign an application package called
-<code>my_application.apk</code>, using the example keystore created above.
-</p>
-
-<pre>$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore
-my_application.apk alias_name</pre>
-
-<p>Running the example command above, Jarsigner prompts you to provide
-passwords for the keystore and key. It then modifies the APK
-in-place, meaning the APK is now signed. Note that you can sign an
-APK multiple times with different keys.</p>
-
-<p class="caution"><strong>Caution:</strong> As of JDK 7, the default signing algorithim has
-changed, requiring you to specify the signature and digest algorithims ({@code -sigalg} and {@code
--digestalg}) when you sign an APK.</p>
-
-<p>To verify that your APK is signed, you can use a command like this:</p>
-
-<pre>$ jarsigner -verify my_signed.apk</pre>
-
-<p>If the APK is signed properly, Jarsigner prints "jar verified".
-If you want more details, you can try one of these commands:</p>
-
-<pre>$ jarsigner -verify -verbose my_application.apk</pre>
-
-<p>or</p>
-
-<pre>$ jarsigner -verify -verbose -certs my_application.apk</pre>
-
-<p>The command above, with the <code>-certs</code> option added, will show you the
-"CN=" line that describes who created the key.</p>
-
-<p class="note"><strong>Note:</strong> If you see "CN=Android Debug", this means the APK was
-signed with the debug key generated by the Android SDK. If you intend to release
-your application, you must sign it with your private key instead of the debug
+<p class="warning"><strong>Warning:</strong> Keep your keystore and private key in a safe and
+secure place, and ensure that you have secure backups of them. If you publish an app to Google
+Play and then lose the key with which you signed your app, you will not be able to publish
+any updates to your app, since you must always sign all versions of your app with the same
 key.</p>
 
-<p>For more information about Jarsigner, see the documentation at
-<a href="http://docs.oracle.com/javase/6/docs/technotes/tools/windows/jarsigner.html">
-http://docs.oracle.com/javase/6/docs/technotes/tools/windows/jarsigner.html</a></p>
+<p>The rest of this document provides detailed instructions about how to generate a private
+key and sign your apps in release mode with Android Studio and with the ADT plugin for Eclipse.</p>
+
+<h3 id="wear-apps">Signing Android Wear Apps</h3>
+
+<p>When publishing Android Wear apps, you package the wearable app inside of a handheld app,
+because users cannot browse and install apps directly on the wearable. Both apps must be signed.
+For more information on packaging and signing Android Wear apps, see
+<a href="{@docRoot}training/wearables/apps/packaging.html">Packaging Wearable Apps</a>.</p>
 
 
-<h3 id="align">4. Align the final APK package</h3>
+<h2 id="studio">Signing Your App in Android Studio</h2>
 
-<p>Once you have signed the APK with your private key, run <code>zipalign</code> on the file.
-This tool ensures that all uncompressed data starts with a particular byte alignment,
-relative to the start of the file. Ensuring alignment at 4-byte boundaries provides
-a performance optimization when installed on a device. When aligned, the Android
-system is able to read files with {@code mmap()}, even if
-they contain binary data with alignment restrictions, rather than copying all
-of the data from the package. The benefit is a reduction in the amount of
-RAM consumed by the running application.</p>
-
-<p>The <code>zipalign</code> tool is provided with the Android SDK, inside the
-<code>tools/</code> directory. To align your signed APK, execute:</p>
-
-<pre>$ zipalign -v 4 <em>your_project_name</em>-unaligned.apk <em>your_project_name</em>.apk</pre>
-
-<p>The {@code -v} flag turns on verbose output (optional). {@code 4} is the
-byte-alignment (don't use anything other than 4). The first file argument is
-your signed {@code .apk} file (the input) and the second file is the destination {@code .apk} file
-(the output). If you're overriding an existing APK, add the {@code -f} flag.</p>
-
-<p class="caution"><strong>Caution:</strong> Your input APK must be signed with your
-private key <strong>before</strong> you optimize the package with {@code zipalign}.
-If you sign it after using {@code zipalign}, it will undo the alignment.</p>
-
-<p>For more information, read about the
-<a href="{@docRoot}tools/help/zipalign.html">zipalign</a> tool.
-
-
-<h3 id="ExportWizard">Compile and sign with Eclipse ADT</h3>
-
-<p>If you are using Eclipse with the ADT plugin, you can use the Export Wizard to
-export a <em>signed</em> APK (and even create a new keystore,
-if necessary). The Export Wizard performs all the interaction with
-the Keytool and Jarsigner for you, which allows you to sign the package using a GUI
-instead of performing the manual procedures to compile, sign,
-and align, as discussed above. Once the wizard has compiled and signed your package,
-it will also perfom package alignment with {@code zipalign}.
-Because the Export Wizard uses both Keytool and Jarsigner, you should
-ensure that they are accessible on your computer, as described above
-in the <a href="#setup">Basic Setup for Signing</a>.</p>
-
-<p>To create a signed and aligned APK in Eclipse:</p>
+<p>To sign your app in release mode in Android Studio, follow these steps:</p>
 
 <ol>
-  <li>Select the project in the Package
-Explorer and select <strong>File > Export</strong>.</li>
-  <li>Open the Android folder, select Export Android Application,
-  and click <strong>Next</strong>.
-  <p>The Export Android Application wizard now starts, which will
-  guide you through the process of signing your application,
-  including steps for selecting the private key with which to sign the APK
-  (or creating a new keystore and private key).</p>
-  <li>Complete the Export Wizard and your application will be compiled,
-  signed, aligned, and ready for distribution.</li>
+<li>On the menu bar, click <strong>Build</strong> &gt; <strong>Generate Signed APK</strong>.</li>
+<li><p>On the <em>Generate Signed APK Wizard</em> window, click <strong>Create new</strong> to create
+a new keystore.</p><p>If you already have a keystore, go to step 4.</p></li>
+<li><p>On the <em>New Key Store</em> window, provide the required information as shown
+in figure 1.</p><p>Your key should be valid for at least 25 years, so you can sign app updates
+with the same key through the lifespan of your app.</p>
+<img src="{@docRoot}images/tools/signstudio2.png" alt=""
+     width="416" height="364" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 1</strong>. Create a new keystore in Android Studio.</p>
+</li>
+<li><p>On the <em>Generate Signed APK Wizard</em> window, select a keystore, a private key, and enter
+the passwords for both. Then click <strong>Next</strong>.</p>
+<img src="{@docRoot}images/tools/signstudio1.png" alt=""
+     width="349" height="232" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 2</strong>. Select a private key in Android Studio.</p>
+</li>
+<li><p>On the next window, select a destination for the signed APK and click
+<strong>Finish</strong>.</p>
+<img src="{@docRoot}images/tools/signstudio3.png" alt=""
+     width="350" height="175" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 3</strong>. Generate a signed APK in Android Studio.</p>
+</li>
 </ol>
 
+<h3 id="sign-auto">Automatically Signing Your App</h3>
+
+<p>In Android Studio, you can configure your project to sign your release APK automatically
+during the build process:</p>
+
+<ol>
+<li>On the project browser, right click on your app and select <strong>Open Module
+Settings</strong>.</li>
+<li>On the <em>Project Structure</em> window, select your app's module under <em>Modules</em>.</li>
+<li>Click on the <strong>Signing</strong> tab.</li>
+<li><p>Select your keystore file, enter a name for this signing configuration (as you may create
+more than one), and enter the required information.</p>
+<img src="{@docRoot}images/tools/signstudio10.png" alt=""
+     width="623" height="372" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 4</strong>. Create a signing configuration in Android Studio.</p>
+</li>
+<li>Click on the <strong>Build Types</strong> tab.</li>
+<li>Select the <strong>release</strong> build.</li>
+<li><p>Under <em>Signing Config</em>, select the signing configuration you just created.</p>
+<img src="{@docRoot}images/tools/signstudio11.png" alt=""
+     width="623" height="372" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 5</strong>. Select a signing configuration in Android Studio.</p>
+</li>
+<li>Click <strong>OK</strong>.</li>
+</ol>
+
+<p>You can also specify your signing settings in Gradle configuration files. For more information,
+see <a href="{@docRoot}sdk/installing/studio-build.html#configureSigning">Signing settings</a>.</p>
+
+
+<h2 id="adt">Signing Your App with the ADT Plugin for Eclipse</h2>
+
+<p>To sign your app in release mode in ADT, follow these steps:</p>
+
+<ol>
+<li>Select the project in the Package Explorer and select <strong>File</strong> >
+<strong>Export</strong>.</li>
+<li>On the <em>Export</em> window, select <strong>Export Android Application</strong> and click
+<strong>Next</strong>.</li>
+<li>On the <em>Export Android Application</em> window, select the project you want to sign and
+click <strong>Next</strong>.</li>
+<li>
+<p>On the next window, enter the location to create a keystore and a keystore password. If you
+already have a keystore, select <strong>Use existing keystore</strong>, enter your keystore's
+location and password, and go to step 6.</p>
+<img src="{@docRoot}images/tools/signadt3.png" alt=""
+     width="488" height="270" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 6</strong>. Select a keystore in ADT.</p>
+</li>
+<li><p>On the next window, provide the required information as shown in figure 5.<p>
+<p>Your key should be valid for at least 25 years, so you can sign app updates with the same key
+through the lifespan of your app.</p>
+<img src="{@docRoot}images/tools/signadt4.png" alt=""
+     width="488" height="448" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 7</strong>. Create a private key in ADT.</p>
+</li>
+<li><p>On the next window, select the location to export the signed APK.</p>
+<img src="{@docRoot}images/tools/signadt5.png" alt=""
+     width="488" height="217" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 8</strong>. Export the signed APK in ADT.</p>
+</li>
+</ol>
+
+
+<h2 id="considerations">Signing Considerations</h2>
+
+<p>You should sign all of your apps with the same certificate throughout the expected lifespan
+of your applications. There are several reasons why you should do so:</p>
+
+<ul>
+<li>App upgrade: When the system is installing an update to an app, it compares the certificate(s)
+in the new version with those in the existing version. The system allows the update if the
+certificates match. If you sign the new version with a different certificate, you must assign a
+different package name to the application&mdash;in this case, the user installs the new version as
+a completely new application.</li>
+<li>App modularity: Android allows apps signed by the same certificate to run in the same process,
+if the applications so requests, so that the system treats them as a single application. In this
+way you can deploy your app in modules, and users can update each of the modules independently.</li>
+<li>Code/data sharing through permissions: Android provides signature-based permissions
+enforcement, so that an app can expose functionality to another app that is signed with a
+specified certificate. By signing multiple apps with the same certificate and using
+signature-based permissions checks, your apps can share code and data in a secure manner.</li>
+</ul>
+
+<p>If you plan to support upgrades for an app, ensure that your key has a validity
+period that exceeds the expected lifespan of that app. A validity period of 25 years or more is
+recommended. When your key's validity period expires, users will no longer be able to seamlessly
+upgrade to new versions of your application.</p>
+
+<p>If you plan to publish your apps on Google Play, the key you use to sign these apps must have
+a validity period ending after 22 October 2033. Google Play enforces this requirement to ensure
+that users can seamlessly upgrade apps when new versions are available.</p>
 
 
 <h2 id="secure-key">Securing Your Private Key</h2>
 
-<p>Maintaining the security of your private key is of critical importance, both
-to you and to the user. If you allow someone to use your key, or if you leave
-your keystore and passwords in an unsecured location such that a third-party
-could find and use them, your authoring identity and the trust of the user
-are compromised. </p>
+<p>Maintaining the security of your private key is of critical importance, both to you and to
+the user. If you allow someone to use your key, or if you leave your keystore and passwords in
+an unsecured location such that a third-party could find and use them, your authoring identity
+and the trust of the user are compromised.</p>
 
-<p>If a third party should manage to take your key without your knowledge or
-permission, that person could sign and distribute applications that maliciously
-replace your authentic applications or corrupt them. Such a person could also
-sign and distribute applications under your identity that attack other
-applications or the system itself, or corrupt or steal user data. </p>
+<p>If a third party should manage to take your key without your knowledge or permission, that
+person could sign and distribute apps that maliciously replace your authentic apps or corrupt
+them. Such a person could also sign and distribute apps under your identity that attack
+other apps or the system itself, or corrupt or steal user data.</p>
 
-<p>Your private key is required for signing all future versions of your application. If you lose or
-misplace your key, you will not be able to publish updates to your existing application. You cannot
+<p>Your private key is required for signing all future versions of your app. If you lose or
+misplace your key, you will not be able to publish updates to your existing appn. You cannot
 regenerate a previously generated key.</p>
 
-<p>Your reputation as a developer entity depends on your securing your private
-key properly, at all times, until the key is expired. Here are some tips for
-keeping your key secure: </p>
+<p>Your reputation as a developer entity depends on your securing your private key properly, at
+all times, until the key is expired. Here are some tips for keeping your key secure:</p>
 
 <ul>
 <li>Select strong passwords for the keystore and key.</li>
-<li>When you generate your key with Keytool, <em>do not</em> supply the
-<code>-storepass</code> and <code>-keypass</code> options at the command line.
-If you do so, your passwords will be available in your shell history,
-which any user on your computer could access.</li>
-<li>Similarly, when signing your applications with Jarsigner,
-<em>do not</em> supply the <code>-storepass</code> and <code>-keypass</code>
-options at the command line. </li>
-<li>Do not give or lend anyone your private key, and do not let unauthorized
-persons know your keystore and key passwords.</li>
-<li>Keep the keystore file containing your private key that you <a href="#cert">generate with the
-Keytool</a> in a safe, secure place.</li>
+<li>Do not give or lend anyone your private key, and do not let unauthorized persons know your
+keystore and key passwords.</li>
+<li>Keep the keystore file containing your private key in a safe, secure place.</li>
 </ul>
 
-<p>In general, if you follow common-sense precautions when generating, using,
-and storing your key, it will remain secure. </p>
+<p>In general, if you follow common-sense precautions when generating, using, and storing
+your key, it will remain secure.</p>
+
+
+<h2 id="expdebug">Expiry of the Debug Certificate</h2>
+
+<p>The self-signed certificate used to sign your application in debug mode has an expiration date
+of 365 days from its creation date. When the certificate expires, you will get a build error.</p>
+
+<p>To fix this problem, simply delete the <code>debug.keystore</code> file. The default storage
+location is in <code>~/.android/</code> on OS X and Linux, in <code>C:\Documents and
+Settings\&lt;user&gt;\.android\</code> on Windows XP, and in
+<code>C:\Users\&lt;user&gt;\.android\</code> on Windows Vista and Windows 7.</p>
+
+<p>The next time you build, the build tools will regenerate a new keystore and debug key.</p>
+
+<p>Note that, if your development machine is using a non-Gregorian locale, the build tools may
+erroneously generate an already-expired debug certificate, so that you get an error when trying
+to compile your application. For workaround information, see the troubleshooting topic
+<a href="{@docRoot}resources/faq/troubleshooting.html#signingcalendar">I can't compile my app
+because the build tools generated an expired debug certificate</a>.</p>
+
+
+<h2 id="signing-manually">Signing Your App Manually</h2>
+
+<p>You do not need Android Studio or the ADT plugin for Eclipse to sign your app. You can sign
+your app from the command line using standard tools from the Android SDK and the JDK. To sign
+an app in release mode from the command line:</p>
+
+<ol>
+<li>
+  <p>Generate a private key using
+  <code><a href="http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html">keytool</a></code>.
+  For example:</p>
+<pre>
+$ keytool -genkey -v -keystore my-release-key.keystore
+-alias alias_name -keyalg RSA -keysize 2048 -validity 10000
+</pre>
+  <p>This example prompts you for passwords for the keystore and key, and to provide the
+  Distinguished Name fields for your key. It then generates the keystore as a file called
+  <code>my-release-key.keystore</code>. The keystore contains a single key, valid for 10000 days.
+  The alias is a name that you will use later when signing your app.</p>
+</li>
+<li style="margin-top:18px">
+  <p>Compile your app in release mode to obtain an unsigned APK.</p>
+</li>
+<li style="margin-top:18px">
+  <p>Sign your app with your private key using
+  <code><a href="http://docs.oracle.com/javase/6/docs/technotes/tools/windows/jarsigner.html">jarsigner</a></code>:
+  </p>
+<pre>
+$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1
+-keystore my-release-key.keystore my_application.apk alias_name
+</pre>
+  <p>This example prompts you for passwords for the keystore and key. It then modifies the APK
+  in-place to sign it. Note that you can sign an APK multiple times with different keys.</p>
+</li>
+<li style="margin-top:18px">
+  <p>Verify that your APK is signed. For example:</p>
+<pre>
+$ jarsigner -verify -verbose -certs my_application.apk
+</pre>
+</li>
+<li style="margin-top:18px">
+  <p>Align the final APK package using
+  <code><a href="{@docRoot}tools/help/zipalign.html">zipalign</a></code>.</p>
+<pre>
+$ zipalign -v 4 your_project_name-unaligned.apk your_project_name.apk
+</pre>
+  <p><code>zipalign</code> ensures that all uncompressed data starts with a particular byte
+  alignment relative to the start of the file, which reduces the amount of RAM consumed by an
+  app.</p>
+</li>
+</ol>
diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd
index 5d04098..cf33200 100644
--- a/docs/html/tools/sdk/eclipse-adt.jd
+++ b/docs/html/tools/sdk/eclipse-adt.jd
@@ -56,6 +56,42 @@
 <div class="toggle-content opened">
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+      alt=""/>ADT 23.0.3</a> <em>(August 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+<dl>
+  <dt>Dependencies:</dt>
+
+  <dd>
+    <ul>
+      <li>Java 7 or higher is required if you are targeting the L Developer Preview.</li>
+      <li>Java 1.6 or higher is required if you are targeting other releases.</li>
+      <li>Eclipse Indigo (Version 3.7.2) or higher is required.</li>
+      <li>This version of ADT is designed for use with
+        <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r23.0.2</a>.
+        If you haven't already installed SDK Tools r23.0.2 into your SDK, use the
+        Android SDK Manager to do so.</li>
+    </ul>
+  </dd>
+
+  <dt>General Notes:</dt>
+  <dd>
+    <ul>
+      <li>Fixed an issue where ADT displayed a <code>NullPointerException</code> warning dialog
+          when a valid SDK was not configured. (<a href="http://b.android.com/73313">Issue
+          73313</a>)</li>
+      <li>Fixed a minor issue with RenderScript support.</li>
+      <li>Disabled APK compression.</li>
+    </ul>
+  </dd>
+</dl>
+</div>
+</div>
+
+<div class="toggle-content closed">
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
       alt=""/>ADT 23.0.2</a> <em>(July 2014)</em>
   </p>
 
diff --git a/docs/html/tools/support-library/features.jd b/docs/html/tools/support-library/features.jd
index 65148bf..78946ee 100644
--- a/docs/html/tools/support-library/features.jd
+++ b/docs/html/tools/support-library/features.jd
@@ -225,8 +225,7 @@
 
 <p>This library provides {@link android.support.v7.media.MediaRouter}, {@link
 android.support.v7.media.MediaRouteProvider}, and related media classes that
-support the <a href="https://developers.google.com/cast/">Google Cast
-developer preview</a>. </p>
+support <a href="https://developers.google.com/cast/docs/android_sender">Google Cast</a>. </p>
 
 <p>In general, the APIs in the v7 mediarouter library provide a means of
 controlling the routing of media channels and streams from the current device to
@@ -258,9 +257,8 @@
 
 <p class="caution">The v7 mediarouter library APIs introduced in Support Library
 r18 are subject to change in later revisions of the Support Library. At this
-time, we recommend using the library only in connection with the <a
-href="https://developers.google.com/cast/">Google Cast
-developer preview</a>. </p>
+time, we recommend using the library only in connection with <a
+href="https://developers.google.com/cast/docs/android_sender">Google Cast</a>. </p>
 
 
 <h2 id="v8">v8 Support Library</h2>
diff --git a/docs/html/training/articles/security-gms-provider.jd b/docs/html/training/articles/security-gms-provider.jd
new file mode 100644
index 0000000..0d3cf1e
--- /dev/null
+++ b/docs/html/training/articles/security-gms-provider.jd
@@ -0,0 +1,298 @@
+page.title=Updating Your Security Provider to Protect Against SSL Exploits
+page.tags="network","certificates"
+
+page.article=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>In this document</h2>
+<ol class="nolist">
+  <li><a href="#patching">Patching the Security Provider with
+      ProviderInstaller</a></li>
+  <li><a href="#example_sync">Patching Synchronously</a></li>
+  <li><a href="#example_async">Patching Asynchronously</a></li>
+
+</ol>
+
+
+<h2>See also</h2>
+<ul>
+  <li><a href="{@docRoot}google/play-services/">Google Play Services</a></li>
+  <li><a href="https://www.openssl.org/news/secadv_20140605.txt">OpenSSL
+   Security Advisory [05 Jun 2014]: SSL/TLS MITM vulnerability
+   (CVE-2014-0224)</a></li>
+  <li><a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0224">
+    Vulnerability Summary for CVE-2014-0224</a></li>
+</ul>
+</div>
+</div>
+
+
+<p> Android relies on a security {@link java.security.Provider Provider} to
+provide secure network communications. However, from time to time,
+vulnerabilities are found in the default security provider. To protect against
+these vulnerabilities, <a href="{@docRoot}google/play-services/">Google Play
+services</a> provides a way to automatically update a device's security provider
+to protect against known exploits. By calling Google Play services methods, your
+app can ensure that it's running on a device that has the latest updates to
+protect against known exploits.</p>
+
+<p>For example, a vulnerability was discovered in OpenSSL
+(<a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0224">CVE-2014-0224</a>)
+that can leave apps open to a "man-in-the-middle" attack that decrypts
+secure traffic without either side knowing. With Google Play services version
+5.0, a fix is available, but apps must ensure that this fix is installed. By
+using the Google Play services methods, your app can ensure that it's running
+on a device that's secured against that attack.</p>
+
+<p class="caution"><strong>Caution: </strong>Updating a device's security {@link
+java.security.Provider Provider} does <em>not</em> update {@link
+android.net.SSLCertificateSocketFactory
+android.net.SSLCertificateSocketFactory}. Rather than using this class, we
+encourage app developers to use high-level methods for interacting with
+cryptography. Most apps can use APIs like {@link
+javax.net.ssl.HttpsURLConnection}, {@link org.apache.http.client.HttpClient},
+and {@link android.net.http.AndroidHttpClient} without needing to set a custom
+{@link javax.net.ssl.TrustManager} or create an {@link
+android.net.SSLCertificateSocketFactory}.</p>
+
+<h2 id="patching">Patching the Security Provider with ProviderInstaller</h2>
+
+<p>To update a device's security provider, use the
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+class. You can verify that the security provider is up-to-date (and update it,
+if necessary) by calling
+that class's <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>
+(or <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>)
+method.</p>
+
+<p>When you call <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>, the
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+does the following:</p>
+
+<ul>
+  <li>If the device's {@link java.security.Provider Provider} is successfully
+    updated (or is already up-to-date), the method returns normally.</li>
+  <li>If the device's Google Play services library is out of date, the method
+    throws
+    <a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesRepairableException.html">{@code GooglePlayServicesRepairableException}</a>.
+    The app can then catch this exception and show
+    the user an appropriate dialog box to update Google Play services.</li>
+    <li>If a non-recoverable error occurs, the method throws
+    <a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesNotAvailableException.html">{@code GooglePlayServicesNotAvailableException}</a>
+    to indicate that it is unable to update the {@link java.security.Provider
+    Provider}. The app can then catch the exception and choose an appropriate
+    course of action, such as displaying the standard
+    <a href="{@docRoot}reference/com/google/android/gms/common/SupportErrorDialogFragment.html">fix-it flow diagram</a>.</li>
+</ul>
+
+<p>The
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>
+method behaves similarly, except that instead of
+throwing exceptions, it calls the appropriate callback method to indicate
+success or failure.</p>
+
+<p>If <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>
+needs to install a new {@link java.security.Provider Provider}, this can take
+anywhere from 30-50 milliseconds (on more recent devices) to 350 ms (on older
+devices). If the security provider is already up-to-date, the method takes a
+negligible amount of time. To avoid affecting user experience:</p>
+
+<ul>
+  <li>Call
+  <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>
+  from background networking threads immediately when the threads are loaded,
+  instead of waiting for the thread to try to use the network. (There's no harm
+  in calling the method multiple times, since it returns immediately if the
+  security provider doesn't need updating.)</li>
+
+  <li>If user experience will be affected by the thread blocking--for example,
+  if the call is from an activity in the UI thread--call the asynchronous
+  version of the method,
+  <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>.
+  (Of course, if you do this, you need to wait for the operation to finish
+  before you attempt any secure communications. The
+  <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+  calls your listener's <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.ProviderInstallListener.html#onProviderInstalled()">{@code onProviderInstalled()}</a>
+  method to signal success.)</li>
+</ul>
+
+<p class="warning"><strong>Warning:</strong> If the
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+is unable to install an updated {@link java.security.Provider Provider},
+your device's security provider might be vulnerable  to known exploits. Your app
+should behave as if all HTTP communication is unencrypted.</p>
+
+<p>Once the {@link java.security.Provider Provider} is updated, all calls to
+security APIs (including SSL APIs) are routed through it.
+(However, this does not apply to  {@link android.net.SSLCertificateSocketFactory
+android.net.SSLCertificateSocketFactory}, which remains vulnerable to such
+exploits as
+<a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0224">CVE-2014-0224</a>.)</p>
+
+<h2 id="example_sync">Patching Synchronously</h2>
+
+<p>The simplest way to patch the security provider is to call the synchronous
+method <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>.
+This is appropriate if user experience won't be affected by the thread blocking
+while it waits for the operation to finish.</p>
+
+<p>For example, here's an implementation of a <a href="{@docRoot}training/sync-adapters">sync adapter</a> that updates the security provider. Since a sync
+adapter runs in the background, it's okay if the thread blocks while waiting
+for the security provider to be updated. The sync adapter calls
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a> to
+update the security provider. If the method returns normally, the sync adapter
+knows the security provider is up-to-date. If the method throws an exception,
+the sync adapter can take appropriate action (such as prompting the user to
+update Google Play services).</p>
+
+<pre>/**
+ * Sample sync adapter using {&#64;link ProviderInstaller}.
+ */
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+
+  ...
+
+  // This is called each time a sync is attempted; this is okay, since the
+  // overhead is negligible if the security provider is up-to-date.
+  &#64;Override
+  public void onPerformSync(Account account, Bundle extras, String authority,
+      ContentProviderClient provider, SyncResult syncResult) {
+    try {
+      ProviderInstaller.installIfNeeded(getContext());
+    } catch (GooglePlayServicesRepairableException e) {
+
+      // Indicates that Google Play services is out of date, disabled, etc.
+
+      // Prompt the user to install/update/enable Google Play services.
+      GooglePlayServicesUtil.showErrorNotification(
+          e.getConnectionStatusCode(), getContext());
+
+      // Notify the SyncManager that a soft error occurred.
+      syncResult.stats.numIOExceptions++;
+      return;
+
+    } catch (GooglePlayServicesNotAvailableException e) {
+      // Indicates a non-recoverable error; the ProviderInstaller is not able
+      // to install an up-to-date Provider.
+
+      // Notify the SyncManager that a hard error occurred.
+      syncResult.stats.numAuthExceptions++;
+      return;
+    }
+
+    // If this is reached, you know that the provider was already up-to-date,
+    // or was successfully updated.
+  }
+}</pre>
+
+<h2 id="example_async">Patching Asynchronously</h2>
+
+<p>Updating the security provider can take as much as 350 milliseconds (on
+older devices). If you're doing the update on a thread that directly affects
+user experience, such as the UI thread, you don't want to make a synchronous
+call to update the provider, since that can result in the app or device
+freezing until the operation finishes. Instead, you should use the asynchronous
+method
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>.
+That method indicates its success or failure by calling callbacks.</p>
+
+<p>For example, here's some code that updates the security provider in an
+activity in the UI thread. The activity calls <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>
+to update the provider, and designates itself as the listener to receive success
+or failure notifications. If the security provider is up-to-date or is
+successfully updated, the activity's
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.ProviderInstallListener.html#onProviderInstalled()">{@code onProviderInstalled()}</a>
+method is called, and the activity knows communication is secure. If the
+provider cannot be updated, the activity's
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.ProviderInstallListener.html#onProviderInstallFailed(int, android.content.Intent)">{@code onProviderInstallFailed()}</a>
+method is called, and the activity can take appropriate action (such as
+prompting the user to update Google Play services).</p>
+
+<pre>/**
+ * Sample activity using {&#64;link ProviderInstaller}.
+ */
+public class MainActivity extends Activity
+    implements ProviderInstaller.ProviderInstallListener {
+
+  private static final int ERROR_DIALOG_REQUEST_CODE = 1;
+
+  private boolean mRetryProviderInstall;
+
+  //Update the security provider when the activity is created.
+  &#64;Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ProviderInstaller.installIfNeededAsync(this, this);
+  }
+
+  /**
+   * This method is only called if the provider is successfully updated
+   * (or is already up-to-date).
+   */
+  &#64;Override
+  protected void onProviderInstalled() {
+    // Provider is up-to-date, app can make secure network calls.
+  }
+
+  /**
+   * This method is called if updating fails; the error code indicates
+   * whether the error is recoverable.
+   */
+  &#64;Override
+  protected void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
+    if (GooglePlayServicesUtil.isUserRecoverableError(errorCode)) {
+      // Recoverable error. Show a dialog prompting the user to
+      // install/update/enable Google Play services.
+      GooglePlayServicesUtil.showErrorDialogFragment(
+          errorCode,
+          this,
+          ERROR_DIALOG_REQUEST_CODE,
+          new DialogInterface.OnCancelListener() {
+            &#64;Override
+            public void onCancel(DialogInterface dialog) {
+              // The user chose not to take the recovery action
+              onProviderInstallerNotAvailable();
+            }
+          });
+    } else {
+      // Google Play services is not available.
+      onProviderInstallerNotAvailable();
+    }
+  }
+
+  &#64;Override
+  protected void onActivityResult(int requestCode, int resultCode,
+      Intent data) {
+    super.onActivityResult(requestCode, resultCode, data);
+    if (requestCode == ERROR_DIALOG_REQUEST_CODE) {
+      // Adding a fragment via GooglePlayServicesUtil.showErrorDialogFragment
+      // before the instance state is restored throws an error. So instead,
+      // set a flag here, which will cause the fragment to delay until
+      // onPostResume.
+      mRetryProviderInstall = true;
+    }
+  }
+
+  /**
+   * On resume, check to see if we flagged that we need to reinstall the
+   * provider.
+   */
+  &#64;Override
+  protected void onPostResume() {
+    super.onPostResult();
+    if (mRetryProviderInstall) {
+      // We can now safely retry installation.
+      ProviderInstall.installIfNeededAsync(this, this);
+    }
+    mRetryProviderInstall = false;
+  }
+
+  private void onProviderInstallerNotAvailable() {
+    // This is reached if the provider cannot be updated for some reason.
+    // App should consider all HTTP communication to be vulnerable, and take
+    // appropriate action.
+  }
+}
+</pre>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 4407d30..4dd57ab 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -1487,6 +1487,14 @@
           >Security with HTTPS and SSL</a>
       </li>
 
+      <li>
+        <a href="<?cs var:toroot ?>training/articles/security-gms-provider.html"
+           description=
+           "How to use and update Google Play services security provider, to
+           protect against SSL exploits."
+          >Updating Your Security Provider to Protect Against SSL Exploits</a>
+      </li>
+
       <li class="nav-section">
         <div class="nav-section-header">
           <a href="<?cs var:toroot ?>training/enterprise/index.html"
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index ca8d736..1c76d9c 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1267,7 +1267,6 @@
      * is 0.
      *
      * @return         the paint's letter-spacing for drawing text.
-     * @hide
      */
     public float getLetterSpacing() {
         return native_getLetterSpacing(mNativePaint);
@@ -1279,7 +1278,6 @@
      * expansion will be around 0.05.  Negative values tighten text.
      *
      * @param letterSpacing set the paint's letter-spacing for drawing text.
-     * @hide
      */
     public void setLetterSpacing(float letterSpacing) {
         native_setLetterSpacing(mNativePaint, letterSpacing);
@@ -1289,7 +1287,6 @@
      * Get font feature settings.  Default is null.
      *
      * @return the paint's currently set font feature settings.
-     * @hide
      */
     public String getFontFeatureSettings() {
         return mFontFeatureSettings;
@@ -1302,7 +1299,6 @@
      * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
      *
      * @param settings the font feature settings string to use, may be null.
-     * @hide
      */
     public void setFontFeatureSettings(String settings) {
         if (settings != null && settings.equals("")) {
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index ee5fe2e..588e776 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -119,20 +119,40 @@
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
 
-        final Drawable dr = a.getDrawable(R.styleable.InsetDrawable_drawable);
-        if (dr != null) {
-            state.mDrawable = dr;
-            dr.setCallback(this);
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            final int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.InsetDrawable_drawable:
+                    final Drawable dr = a.getDrawable(attr);
+                    if (dr != null) {
+                        state.mDrawable = dr;
+                        dr.setCallback(this);
+                    }
+                    break;
+                case R.styleable.InsetDrawable_inset:
+                    final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE);
+                    if (inset != Integer.MIN_VALUE) {
+                        state.mInsetLeft = inset;
+                        state.mInsetTop = inset;
+                        state.mInsetRight = inset;
+                        state.mInsetBottom = inset;
+                    }
+                    break;
+                case R.styleable.InsetDrawable_insetLeft:
+                    state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft);
+                    break;
+                case R.styleable.InsetDrawable_insetTop:
+                    state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop);
+                    break;
+                case R.styleable.InsetDrawable_insetRight:
+                    state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight);
+                    break;
+                case R.styleable.InsetDrawable_insetBottom:
+                    state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom);
+                    break;
+            }
         }
-
-        state.mInsetLeft = a.getDimensionPixelOffset(
-                R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
-        state.mInsetTop = a.getDimensionPixelOffset(
-                R.styleable.InsetDrawable_insetTop, state.mInsetTop);
-        state.mInsetRight = a.getDimensionPixelOffset(
-                R.styleable.InsetDrawable_insetRight, state.mInsetRight);
-        state.mInsetBottom = a.getDimensionPixelOffset(
-                R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 6ebb8b5..a6dbcb0 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -626,13 +626,18 @@
             mAutoMirrored = autoMirror;
 
             // Sanity check for valid padding when we have optical insets.
-            if (mPadding.left < mOpticalInsets.left) {
-                mPadding.left = mOpticalInsets.left;
-                mPadding.right = mOpticalInsets.right;
-            }
-            if (mPadding.top < mOpticalInsets.top) {
-                mPadding.top = mOpticalInsets.top;
-                mPadding.bottom = mOpticalInsets.bottom;
+            if (!opticalInsets.isEmpty()) {
+                if (mPadding == null) {
+                    mPadding = new Rect();
+                }
+                if (mPadding.left < opticalInsets.left) {
+                    mPadding.left = opticalInsets.left;
+                    mPadding.right = opticalInsets.right;
+                }
+                if (mPadding.top < opticalInsets.top) {
+                    mPadding.top = opticalInsets.top;
+                    mPadding.bottom = opticalInsets.bottom;
+                }
             }
         }
 
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 6f21f2e..be2241b 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -276,7 +276,7 @@
     public void getBounds(Rect bounds) {
         final int outerX = (int) mOuterX;
         final int outerY = (int) mOuterY;
-        final int r = (int) mOuterRadius;
+        final int r = (int) mOuterRadius + 1;
         bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
     }
 
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index d404ccd..93df648 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -264,7 +264,7 @@
     public void getBounds(Rect bounds) {
         final int outerX = (int) mOuterX;
         final int outerY = (int) mOuterY;
-        final int r = (int) mOuterRadius;
+        final int r = (int) mOuterRadius + 1;
         bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
     }
 
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 813797f..2b5823e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -104,6 +104,8 @@
  * <dt><code>android:translateY</code></dt>
  * <dd>The amount of translation on the Y coordinate.
  * This is defined in the viewport space.</dd>
+ * <dt><code>android:alpha</code></dt>
+ * <dd>The amount of transparency.</dd>
  * </dl></dd>
  * </dl>
  *
@@ -117,15 +119,11 @@
  * <dd>Defines path string. This is using exactly same format as "d" attribute
  * in the SVG's path data. This is defined in the viewport space.</dd>
  * <dt><code>android:fillColor</code></dt>
- * <dd>Defines the color to fill the path (none if not present).</dd>
+ * <dd>Defines the color to fill the path (black if not present).</dd>
  * <dt><code>android:strokeColor</code></dt>
  * <dd>Defines the color to draw the path outline (none if not present).</dd>
  * <dt><code>android:strokeWidth</code></dt>
  * <dd>The width a path stroke.</dd>
- * <dt><code>android:strokeOpacity</code></dt>
- * <dd>The opacity of a path stroke.</dd>
- * <dt><code>android:fillOpacity</code></dt>
- * <dd>The opacity to fill the path with.</dd>
  * <dt><code>android:trimPathStart</code></dt>
  * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
  * <dt><code>android:trimPathEnd</code></dt>
@@ -1241,10 +1239,8 @@
 
         int mStrokeColor = 0;
         float mStrokeWidth = 0;
-        float mStrokeOpacity = Float.NaN;
         int mFillColor = Color.BLACK;
         int mFillRule;
-        float mFillOpacity = Float.NaN;
         float mTrimPathStart = 0;
         float mTrimPathEnd = 1;
         float mTrimPathOffset = 0;
@@ -1263,10 +1259,8 @@
 
             mStrokeColor = copy.mStrokeColor;
             mStrokeWidth = copy.mStrokeWidth;
-            mStrokeOpacity = copy.mStrokeOpacity;
             mFillColor = copy.mFillColor;
             mFillRule = copy.mFillRule;
-            mFillOpacity = copy.mFillOpacity;
             mTrimPathStart = copy.mTrimPathStart;
             mTrimPathEnd = copy.mTrimPathEnd;
             mTrimPathOffset = copy.mTrimPathOffset;
@@ -1327,8 +1321,6 @@
 
             mFillColor = a.getColor(R.styleable.VectorDrawablePath_fillColor,
                     mFillColor);
-            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity,
-                    mFillOpacity);
             mStrokeLineCap = getStrokeLineCap(a.getInt(
                     R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
             mStrokeLineJoin = getStrokeLineJoin(a.getInt(
@@ -1337,8 +1329,6 @@
                     R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
             mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_strokeColor,
                     mStrokeColor);
-            mStrokeOpacity = a.getFloat(R.styleable.VectorDrawablePath_strokeOpacity,
-                    mStrokeOpacity);
             mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
                     mStrokeWidth);
             mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
@@ -1347,8 +1337,6 @@
                     R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
             mTrimPathStart = a.getFloat(
                     R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
-
-            updateColorAlphas();
         }
 
         @Override
@@ -1363,16 +1351,6 @@
             a.recycle();
         }
 
-        private void updateColorAlphas() {
-            if (!Float.isNaN(mFillOpacity)) {
-                mFillColor = applyAlpha(mFillColor, mFillOpacity);
-            }
-
-            if (!Float.isNaN(mStrokeOpacity)) {
-                mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
-            }
-        }
-
         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
         @SuppressWarnings("unused")
         int getStroke() {
@@ -1395,16 +1373,6 @@
         }
 
         @SuppressWarnings("unused")
-        float getStrokeOpacity() {
-            return mStrokeOpacity;
-        }
-
-        @SuppressWarnings("unused")
-        void setStrokeOpacity(float strokeOpacity) {
-            mStrokeOpacity = strokeOpacity;
-        }
-
-        @SuppressWarnings("unused")
         int getFill() {
             return mFillColor;
         }
@@ -1415,16 +1383,6 @@
         }
 
         @SuppressWarnings("unused")
-        float getFillOpacity() {
-            return mFillOpacity;
-        }
-
-        @SuppressWarnings("unused")
-        void setFillOpacity(float fillOpacity) {
-            mFillOpacity = fillOpacity;
-        }
-
-        @SuppressWarnings("unused")
         float getTrimPathStart() {
             return mTrimPathStart;
         }
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 79a2f61..acfa98e 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -69,7 +69,9 @@
 class PlaybackStateStruct {
 protected:
     PlaybackStateStruct(OpenGLRenderer& renderer, int replayFlags, LinearAllocator* allocator)
-            : mRenderer(renderer), mReplayFlags(replayFlags), mAllocator(allocator){}
+            : mRenderer(renderer)
+            , mReplayFlags(replayFlags)
+            , mAllocator(allocator) {}
 
 public:
     OpenGLRenderer& mRenderer;
@@ -78,6 +80,15 @@
     // Allocator with the lifetime of a single frame.
     // replay uses an Allocator owned by the struct, while defer shares the DeferredDisplayList's Allocator
     LinearAllocator * const mAllocator;
+
+    SkPath* allocPathForFrame() {
+        mTempPaths.push_back();
+        return &mTempPaths.back();
+    }
+
+private:
+    // Paths kept alive for the duration of the frame
+    std::vector<SkPath> mTempPaths;
 };
 
 class DeferStateStruct : public PlaybackStateStruct {
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 6883cc5..c6d3db7 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1505,20 +1505,18 @@
 class DrawShadowOp : public DrawOp {
 public:
     DrawShadowOp(const mat4& transformXY, const mat4& transformZ,
-            float casterAlpha, const SkPath* casterOutline, const SkPath* revealClip)
-            : DrawOp(NULL), mTransformXY(transformXY), mTransformZ(transformZ),
-            mCasterAlpha(casterAlpha) {
-        mOutline = *casterOutline;
-        if (revealClip) {
-            // intersect the outline with the convex reveal clip
-            Op(mOutline, *revealClip, kIntersect_PathOp, &mOutline);
-        }
+            float casterAlpha, const SkPath* casterOutline)
+        : DrawOp(NULL)
+        , mTransformXY(transformXY)
+        , mTransformZ(transformZ)
+        , mCasterAlpha(casterAlpha)
+        , mCasterOutline(casterOutline) {
     }
 
     virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
             const DeferredDisplayState& state) {
         renderer.getCaches().tessellationCache.precacheShadows(&state.mMatrix,
-                renderer.getLocalClipBounds(), isCasterOpaque(), &mOutline,
+                renderer.getLocalClipBounds(), isCasterOpaque(), mCasterOutline,
                 &mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius());
     }
 
@@ -1527,7 +1525,7 @@
         Matrix4 drawTransform;
         renderer.getMatrix(&drawTransform);
         renderer.getCaches().tessellationCache.getShadowBuffers(&drawTransform,
-                renderer.getLocalClipBounds(), isCasterOpaque(), &mOutline,
+                renderer.getLocalClipBounds(), isCasterOpaque(), mCasterOutline,
                 &mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius(),
                 buffers);
 
@@ -1546,7 +1544,7 @@
     const mat4 mTransformXY;
     const mat4 mTransformZ;
     const float mCasterAlpha;
-    SkPath mOutline;
+    const SkPath* mCasterOutline;
 };
 
 class DrawLayerOp : public DrawOp {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e00d2e3..396c7f3 100755
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -58,11 +58,6 @@
 // Defines
 ///////////////////////////////////////////////////////////////////////////////
 
-#define RAD_TO_DEG (180.0f / 3.14159265f)
-#define MIN_ANGLE 0.001f
-
-#define ALPHA_THRESHOLD 0
-
 static GLenum getFilter(const SkPaint* paint) {
     if (!paint || paint->getFilterLevel() != SkPaint::kNone_FilterLevel) {
         return GL_LINEAR;
@@ -692,7 +687,7 @@
             (fboLayer && clip.isEmpty())) {
         mSnapshot->empty = fboLayer;
     } else {
-        mSnapshot->invisible = mSnapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer);
+        mSnapshot->invisible = mSnapshot->invisible || (alpha <= 0 && fboLayer);
     }
 }
 
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index 209341c..e30ac19 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -49,6 +49,7 @@
 #include "Matrix.h"
 #include "Vector.h"
 #include "Vertex.h"
+#include "utils/MathUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -56,12 +57,11 @@
 #define OUTLINE_REFINE_THRESHOLD_SQUARED (0.5f * 0.5f)
 #define ROUND_CAP_THRESH 0.25f
 #define PI 3.1415926535897932f
+#define MAX_DEPTH 15
 
-// temporary error thresholds
-#define ERROR_DEPTH 20
-#define ERROR_SCALE 1e10
-#define ERROR_SQR_INV_THRESH 1e-20
-
+/**
+ * Extracts the x and y scale from the transform as positive values, and clamps them
+ */
 void PathTessellator::extractTessellationScales(const Matrix4& transform,
         float* scaleX, float* scaleY) {
     if (CC_LIKELY(transform.isPureTranslate())) {
@@ -72,11 +72,8 @@
         float m01 = transform.data[Matrix4::kSkewY];
         float m10 = transform.data[Matrix4::kSkewX];
         float m11 = transform.data[Matrix4::kScaleY];
-        *scaleX = sqrt(m00 * m00 + m01 * m01);
-        *scaleY = sqrt(m10 * m10 + m11 * m11);
-
-        LOG_ALWAYS_FATAL_IF(*scaleX > ERROR_SCALE || *scaleY > ERROR_SCALE,
-                "scales %e x %e too large for tessellation", *scaleX, *scaleY);
+        *scaleX = MathUtils::clampTessellationScale(sqrt(m00 * m00 + m01 * m01));
+        *scaleY = MathUtils::clampTessellationScale(sqrt(m10 * m10 + m11 * m11));
     }
 }
 
@@ -109,8 +106,8 @@
         } else {
             float scaleX, scaleY;
             PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
-            inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
-            inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
+            inverseScaleX = 1.0f / scaleX;
+            inverseScaleY = 1.0f / scaleY;
         }
 
         if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
@@ -914,9 +911,6 @@
         Vector<Vertex>& outputVertices) {
     ATRACE_CALL();
 
-    LOG_ALWAYS_FATAL_IF(sqrInvScaleX < ERROR_SQR_INV_THRESH || sqrInvScaleY < ERROR_SQR_INV_THRESH,
-            "Invalid scale factors used for approx %e, %e", sqrInvScaleX, sqrInvScaleY);
-
     // TODO: to support joins other than sharp miter, join vertices should be labelled in the
     // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
     SkPath::Iter iter(path, forceClose);
@@ -975,9 +969,6 @@
         float p2x, float p2y, float c2x, float c2y,
         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
         Vector<Vertex>& outputVertices, int depth) {
-    LOG_ALWAYS_FATAL_IF(depth >= ERROR_DEPTH, "ERROR DEPTH exceeded: cubic approx, invscale %e x %e, vertcount %d",
-            sqrInvScaleX, sqrInvScaleY, outputVertices.size());
-
     float dx = p2x - p1x;
     float dy = p2y - p1y;
     float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
@@ -985,7 +976,8 @@
     float d = d1 + d2;
 
     // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
-    if (d * d < thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+    if (depth >= MAX_DEPTH
+            || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
         // below thresh, draw line by adding endpoint
         pushToVector(outputVertices, p2x, p2y);
     } else {
@@ -1023,14 +1015,13 @@
         float cx, float cy,
         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
         Vector<Vertex>& outputVertices, int depth) {
-    LOG_ALWAYS_FATAL_IF(depth >= ERROR_DEPTH, "ERROR_DEPTH exceeded: quadratic approx, invscale %e x %e, vertcount %d",
-            sqrInvScaleX, sqrInvScaleY, outputVertices.size());
-
     float dx = bx - ax;
     float dy = by - ay;
     float d = (cx - bx) * dy - (cy - by) * dx;
 
-    if (d * d < thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+    // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
+    if (depth >= MAX_DEPTH
+            || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
         // below thresh, draw line by adding endpoint
         pushToVector(outputVertices, bx, by);
     } else {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index f48b774..23940ee 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -534,6 +534,7 @@
     inline void endMark() {}
     inline int level() { return mLevel; }
     inline int replayFlags() { return mDeferStruct.mReplayFlags; }
+    inline SkPath* allocPathForFrame() { return mDeferStruct.allocPathForFrame(); }
 
 private:
     DeferStateStruct& mDeferStruct;
@@ -564,6 +565,7 @@
     }
     inline int level() { return mLevel; }
     inline int replayFlags() { return mReplayStruct.mReplayFlags; }
+    inline SkPath* allocPathForFrame() { return mReplayStruct.allocPathForFrame(); }
 
 private:
     ReplayStateStruct& mReplayStruct;
@@ -612,14 +614,24 @@
     mat4 shadowMatrixZ(transformFromParent);
     applyViewPropertyTransforms(shadowMatrixZ, true);
 
-    const SkPath* outlinePath = properties().getOutline().getPath();
+    const SkPath* casterOutlinePath = properties().getOutline().getPath();
     const SkPath* revealClipPath = properties().getRevealClip().getPath();
     if (revealClipPath && revealClipPath->isEmpty()) return;
 
     float casterAlpha = properties().getAlpha() * properties().getOutline().getAlpha();
+
+    const SkPath* outlinePath = casterOutlinePath;
+    if (revealClipPath) {
+        // if we can't simply use the caster's path directly, create a temporary one
+        SkPath* frameAllocatedPath = handler.allocPathForFrame();
+
+        // intersect the outline with the convex reveal clip
+        Op(*casterOutlinePath, *revealClipPath, kIntersect_PathOp, frameAllocatedPath);
+        outlinePath = frameAllocatedPath;
+    }
+
     DisplayListOp* shadowOp  = new (handler.allocator()) DrawShadowOp(
-            shadowMatrixXY, shadowMatrixZ, casterAlpha,
-            outlinePath, revealClipPath);
+            shadowMatrixXY, shadowMatrixZ, casterAlpha, outlinePath);
     handler(shadowOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
 }
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 41f48cd..0c8d07f 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -313,7 +313,6 @@
     }
 
     bool setScaleX(float scaleX) {
-        LOG_ALWAYS_FATAL_IF(scaleX > 1000000, "invalid scaleX %e", scaleX);
         return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleX, scaleX);
     }
 
@@ -322,7 +321,6 @@
     }
 
     bool setScaleY(float scaleY) {
-        LOG_ALWAYS_FATAL_IF(scaleY > 1000000, "invalid scaleY %e", scaleY);
         return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleY, scaleY);
     }
 
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
index 66bc127..6fb0411 100644
--- a/libs/hwui/utils/MathUtils.h
+++ b/libs/hwui/utils/MathUtils.h
@@ -35,6 +35,9 @@
         return value >= NON_ZERO_EPSILON;
     }
 
+    /**
+     * Clamps alpha value, and snaps when very near 0 or 1
+     */
     inline static float clampAlpha(float alpha) {
         if (alpha <= ALPHA_EPSILON) {
             return 0;
@@ -45,6 +48,20 @@
         }
     }
 
+    /*
+     * Clamps positive tessellation scale values
+     */
+    inline static float clampTessellationScale(float scale) {
+        const float MIN_SCALE = 0.0001;
+        const float MAX_SCALE = 1e10;
+        if (scale < MIN_SCALE) {
+            return MIN_SCALE;
+        } else if (scale > MAX_SCALE) {
+            return MAX_SCALE;
+        }
+        return scale;
+    }
+
     inline static bool areEqual(float valueA, float valueB) {
         return isZero(valueA - valueB);
     }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 705d9c0..a41d4da 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -4573,6 +4573,17 @@
                 outDevice = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
                 setWiredDeviceConnectionState(outDevice, state, params);
             } else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
+                // FIXME Does not yet handle the case where the setting is changed
+                // after device connection.  Ideally we should handle the settings change
+                // in SettingsObserver. Here we should log that a USB device is connected
+                // and disconnected with its address (card , device) and force the
+                // connection or disconnection when the setting changes.
+                int isDisabled = Settings.System.getInt(mContentResolver,
+                        Settings.System.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
+                if (isDisabled != 0) {
+                    return;
+                }
+
                 state = intent.getIntExtra("state", 0);
 
                 int alsaCard = intent.getIntExtra("card", -1);
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 40602c5..8bc2498 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -479,7 +479,7 @@
                                  int channelConfig, int audioFormat, int mode) {
         //--------------
         // sample rate, note these values are subject to change
-        if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
+        if (sampleRateInHz < SAMPLE_RATE_HZ_MIN || sampleRateInHz > SAMPLE_RATE_HZ_MAX) {
             throw new IllegalArgumentException(sampleRateInHz
                     + "Hz is not a supported sample rate.");
         }
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 6e1b80a..b786f94 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -77,9 +77,9 @@
      * data.</p>
      *
      * @param width
-     *            The width in pixels of the Images that this reader will produce.
+     *            The default width in pixels of the Images that this reader will produce.
      * @param height
-     *            The height in pixels of the Images that this reader will produce.
+     *            The default height in pixels of the Images that this reader will produce.
      * @param format
      *            The format of the Image that this reader will produce. This
      *            must be one of the {@link android.graphics.ImageFormat} or
@@ -130,39 +130,43 @@
     }
 
     /**
-     * The width of each {@link Image}, in pixels.
+     * The default width of {@link Image Images}, in pixels.
      *
-     * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with
-     * {@link #acquireNextImage}) will have the same dimensions as specified in
-     * {@link #newInstance}.</p>
+     * <p>The width may be overridden by the producer sending buffers to this
+     * ImageReader's Surface. If so, the actual width of the images can be
+     * found using {@link Image#getWidth}.</p>
      *
-     * @return the width of an Image
+     * @return the expected width of an Image
      */
     public int getWidth() {
         return mWidth;
     }
 
     /**
-     * The height of each {@link Image}, in pixels.
+     * The default height of {@link Image Images}, in pixels.
      *
-     * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with
-     * {@link #acquireNextImage}) will have the same dimensions as specified in
-     * {@link #newInstance}.</p>
+     * <p>The height may be overridden by the producer sending buffers to this
+     * ImageReader's Surface. If so, the actual height of the images can be
+     * found using {@link Image#getHeight}.</p>
      *
-     * @return the height of an Image
+     * @return the expected height of an Image
      */
     public int getHeight() {
         return mHeight;
     }
 
     /**
-     * The {@link ImageFormat image format} of each Image.
+     * The default {@link ImageFormat image format} of {@link Image Images}.
      *
-     * <p>ImageReader guarantees that all {@link Image Images} acquired from ImageReader
-     *  (for example, with {@link #acquireNextImage}) will have the same format as specified in
-     * {@link #newInstance}.</p>
+     * <p>Some color formats may be overridden by the producer sending buffers to
+     * this ImageReader's Surface if the default color format allows. ImageReader
+     * guarantees that all {@link Image Images} acquired from ImageReader
+     * (for example, with {@link #acquireNextImage}) will have a "compatible"
+     * format to what was specified in {@link #newInstance}.
+     * As of now, each format is only compatible to itself.
+     * The actual format of the images can be found using {@link Image#getFormat}.</p>
      *
-     * @return the format of an Image
+     * @return the expected format of an Image
      *
      * @see ImageFormat
      */
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index bbf6a3b..74f7a96 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -190,8 +190,8 @@
 
     private static final String[] PREFERRED_DESCRIPTION_ORDER = {
             METADATA_KEY_TITLE,
-            METADATA_KEY_ALBUM,
             METADATA_KEY_ARTIST,
+            METADATA_KEY_ALBUM,
             METADATA_KEY_ALBUM_ARTIST,
             METADATA_KEY_WRITER,
             METADATA_KEY_AUTHOR,
@@ -215,7 +215,6 @@
     private static final int METADATA_TYPE_TEXT = 1;
     private static final int METADATA_TYPE_BITMAP = 2;
     private static final int METADATA_TYPE_RATING = 3;
-    private static final int METADATA_TYPE_URI = 4;
     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
 
     static {
@@ -236,16 +235,16 @@
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_URI);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_URI);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
         METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_URI);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
     }
 
     private static final SparseArray<String> EDITOR_KEY_MAPPING;
@@ -406,7 +405,6 @@
      * Returns a simple description of this metadata for display purposes.
      *
      * @return A simple description of this metadata.
-     * @hide
      */
     public @NonNull Description getDescription() {
         if (mDescription != null) {
@@ -673,43 +671,86 @@
 
     /**
      * A simple form of the metadata that can be used for display.
-     *
-     * @hide
      */
     public final class Description {
         /**
          * A primary title suitable for display or null.
          */
-        public final CharSequence title;
+        private final CharSequence mTitle;
         /**
          * A subtitle suitable for display or null.
          */
-        public final CharSequence subtitle;
+        private final CharSequence mSubtitle;
         /**
          * A description suitable for display or null.
          */
-        public final CharSequence description;
+        private final CharSequence mDescription;
         /**
          * A bitmap icon suitable for display or null.
          */
-        public final Bitmap icon;
+        private final Bitmap mIcon;
         /**
          * A Uri for an icon suitable for display or null.
          */
-        public final Uri iconUri;
+        private final Uri mIconUri;
+
+        /**
+         * Returns the best available title or null.
+         *
+         * @return A title or null.
+         */
+        public @Nullable CharSequence getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Returns the best available subtitle or null.
+         *
+         * @return A subtitle or null.
+         */
+        public @Nullable CharSequence getSubtitle() {
+            return mSubtitle;
+        }
+
+        /**
+         * Returns the best available description or null.
+         *
+         * @return A description or null.
+         */
+        public @Nullable CharSequence getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Returns the best available icon or null.
+         *
+         * @return An icon or null.
+         */
+        public @Nullable Bitmap getIcon() {
+            return mIcon;
+        }
+
+        /**
+         * Returns the best available icon Uri or null.
+         *
+         * @return An icon uri or null.
+         */
+        public @Nullable Uri getIconUri() {
+            return mIconUri;
+        }
 
         private Description(CharSequence title, CharSequence subtitle, CharSequence description,
                 Bitmap icon, Uri iconUri) {
-            this.title = title;
-            this.subtitle = subtitle;
-            this.description = description;
-            this.icon = icon;
-            this.iconUri = iconUri;
+            mTitle = title;
+            mSubtitle = subtitle;
+            mDescription = description;
+            mIcon = icon;
+            mIconUri = iconUri;
         }
 
         @Override
         public String toString() {
-            return title + ", " + subtitle + ", " + description;
+            return mTitle + ", " + mSubtitle + ", " + mDescription;
         }
     }
 
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 96c66c5..2a0fd83 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -970,8 +970,7 @@
     public final static int RCSE_ID_UNREGISTERED = -1;
 
     // USE_SESSIONS
-    private MediaSession.TransportControlsCallback mTransportListener
-            = new MediaSession.TransportControlsCallback() {
+    private MediaSession.Callback mTransportListener = new MediaSession.Callback() {
 
         @Override
         public void onSeekTo(long pos) {
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index dd84ad32..3d25aa6 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -25,7 +25,7 @@
     boolean canProjectAudio();
     boolean canProjectVideo();
     boolean canProjectSecureVideo();
-    int getVirtualDisplayFlags();
+    int applyVirtualDisplayFlags(int flags);
     void addCallback(IMediaProjectionCallback callback);
     void removeCallback(IMediaProjectionCallback callback);
 }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 7c03171..861039d 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -93,6 +93,19 @@
     }
 
     /**
+     * @hide
+     */
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
+            @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+        DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
+        return dm.createVirtualDisplay(this, name, width, height, dpi, surface,
+                    flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callbacks, handler);
+    }
+
+    /**
      * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
      * contents of the screen.
      *
@@ -105,9 +118,8 @@
      * than 0.
      * @param surface The surface to which the content of the virtual display
      * should be rendered, or null if there is none initially.
-     * @param isSecure Whether the display should be considered a secure
-     * display. This typically requires special permissions not available to
-     * third party applications.
+     * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full
+     * list of flags.
      * @param callbacks Callbacks to call when the virtual display's state
      * changes, or null if none.
      * @param handler The {@link android.os.Handler} on which the callback should be
@@ -118,10 +130,9 @@
      * String, int, int, int, int, Surface, VirtualDisplay.Callbacks, Handler)
      */
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
-            int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
+            int width, int height, int dpi, int flags, @Nullable Surface surface,
             @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
         DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
         return dm.createVirtualDisplay(
                     this, name, width, height, dpi, surface, flags, callbacks, handler);
     }
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index e3c198e..f6e189a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -179,7 +179,8 @@
     }
 
     /**
-     * Get the current play queue for this session.
+     * Get the current play queue for this session if one is set. If you only
+     * care about the current item {@link #getMetadata()} should be used.
      *
      * @return The current play queue or null.
      */
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index cf8e3dd..cf73c2a 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -21,12 +21,10 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
-import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
@@ -43,11 +41,11 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.KeyEvent;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -64,10 +62,8 @@
  * create a {@link MediaController} to interact with the session.
  * <p>
  * To receive commands, media keys, and other events a {@link Callback} must be
- * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
- * setActive(true)} must be called. To receive transport control commands a
- * {@link TransportControlsCallback} must be set with
- * {@link #addTransportControlsCallback}.
+ * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called.
  * <p>
  * When an app is finished performing playback it must call {@link #release()}
  * to clean up the session and notify any controllers.
@@ -85,8 +81,7 @@
 
     /**
      * Set this flag on the session to indicate that it handles transport
-     * control commands through a {@link TransportControlsCallback}.
-     * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
+     * control commands through its {@link Callback}.
      */
     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
 
@@ -124,12 +119,9 @@
     private final ISession mBinder;
     private final CallbackStub mCbStub;
 
-    private final ArrayList<CallbackMessageHandler> mCallbacks
-            = new ArrayList<CallbackMessageHandler>();
-    private final ArrayList<TransportMessageHandler> mTransportCallbacks
-            = new ArrayList<TransportMessageHandler>();
-
+    private CallbackMessageHandler mCallback;
     private VolumeProvider mVolumeProvider;
+    private PlaybackState mPlaybackState;
 
     private boolean mActive = false;
 
@@ -177,30 +169,35 @@
     }
 
     /**
-     * Add a callback to receive updates on for the MediaSession. This includes
-     * media button and volume events. The caller's thread will be used to post
-     * events.
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button events and transport controls. The caller's thread will be
+     * used to post updates.
+     * <p>
+     * Set the callback to null to stop receiving updates.
      *
      * @param callback The callback object
      */
-    public void addCallback(@NonNull Callback callback) {
-        addCallback(callback, null);
+    public void setCallback(@Nullable Callback callback) {
+        setCallback(callback, null);
     }
 
     /**
-     * Add a callback to receive updates for the MediaSession. This includes
-     * media button and volume events.
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button events and transport controls.
+     * <p>
+     * Set the callback to null to stop receiving updates.
      *
      * @param callback The callback to receive updates on.
      * @param handler The handler that events should be posted on.
      */
-    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
+    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
         if (callback == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
+            mCallback = null;
+            return;
         }
         synchronized (mLock) {
-            if (getHandlerForCallbackLocked(callback) != null) {
-                Log.w(TAG, "Callback is already added, ignoring");
+            if (mCallback != null && mCallback.mCallback == callback) {
+                Log.w(TAG, "Tried to set same callback, ignoring");
                 return;
             }
             if (handler == null) {
@@ -208,18 +205,7 @@
             }
             CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
                     callback);
-            mCallbacks.add(msgHandler);
-        }
-    }
-
-    /**
-     * Remove a callback. It will no longer receive updates.
-     *
-     * @param callback The callback to remove.
-     */
-    public void removeCallback(@NonNull Callback callback) {
-        synchronized (mLock) {
-            removeCallbackLocked(callback);
+            mCallback = msgHandler;
         }
     }
 
@@ -421,63 +407,12 @@
     }
 
     /**
-     * Add a callback to receive transport controls on, such as play, rewind, or
-     * fast forward.
-     *
-     * @param callback The callback object
-     */
-    public void addTransportControlsCallback(@NonNull TransportControlsCallback callback) {
-        addTransportControlsCallback(callback, null);
-    }
-
-    /**
-     * Add a callback to receive transport controls on, such as play, rewind, or
-     * fast forward. The updates will be posted to the specified handler. If no
-     * handler is provided they will be posted to the caller's thread.
-     *
-     * @param callback The callback to receive updates on
-     * @param handler The handler to post the updates on
-     */
-    public void addTransportControlsCallback(@NonNull TransportControlsCallback callback,
-            @Nullable Handler handler) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        synchronized (mLock) {
-            if (getTransportControlsHandlerForCallbackLocked(callback) != null) {
-                Log.w(TAG, "Callback is already added, ignoring");
-                return;
-            }
-            if (handler == null) {
-                handler = new Handler();
-            }
-            TransportMessageHandler msgHandler = new TransportMessageHandler(handler.getLooper(),
-                    callback);
-            mTransportCallbacks.add(msgHandler);
-        }
-    }
-
-    /**
-     * Stop receiving transport controls on the specified callback. If an update
-     * has already been posted you may still receive it after this call returns.
-     *
-     * @param callback The callback to stop receiving updates on
-     */
-    public void removeTransportControlsCallback(@NonNull TransportControlsCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        synchronized (mLock) {
-            removeTransportControlsCallbackLocked(callback);
-        }
-    }
-
-    /**
      * Update the current playback state.
      *
      * @param state The current state of playback
      */
     public void setPlaybackState(@Nullable PlaybackState state) {
+        mPlaybackState = state;
         try {
             mBinder.setPlaybackState(state);
         } catch (RemoteException e) {
@@ -566,138 +501,78 @@
     }
 
     private void dispatchPlay() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY);
+        postToCallback(CallbackMessageHandler.MSG_PLAY);
     }
 
     private void dispatchPlayUri(Uri uri, Bundle extras) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_URI, uri, extras);
+        postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
     }
 
     private void dispatchPlayFromSearch(String query, Bundle extras) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_SEARCH, query, extras);
+        postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
     }
 
     private void dispatchSkipToTrack(long id) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_SKIP_TO_TRACK, id);
+        postToCallback(CallbackMessageHandler.MSG_SKIP_TO_TRACK, id);
     }
 
     private void dispatchPause() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE);
+        postToCallback(CallbackMessageHandler.MSG_PAUSE);
     }
 
     private void dispatchStop() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_STOP);
+        postToCallback(CallbackMessageHandler.MSG_STOP);
     }
 
     private void dispatchNext() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_NEXT);
+        postToCallback(CallbackMessageHandler.MSG_NEXT);
     }
 
     private void dispatchPrevious() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PREVIOUS);
+        postToCallback(CallbackMessageHandler.MSG_PREVIOUS);
     }
 
     private void dispatchFastForward() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_FAST_FORWARD);
+        postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD);
     }
 
     private void dispatchRewind() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_REWIND);
+        postToCallback(CallbackMessageHandler.MSG_REWIND);
     }
 
     private void dispatchSeekTo(long pos) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_SEEK_TO, pos);
+        postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos);
     }
 
     private void dispatchRate(Rating rating) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_RATE, rating);
+        postToCallback(CallbackMessageHandler.MSG_RATE, rating);
     }
 
     private void dispatchCustomAction(String action, Bundle args) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_CUSTOM_ACTION, action, args);
+        postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
     }
 
-    private TransportMessageHandler getTransportControlsHandlerForCallbackLocked(
-            TransportControlsCallback callback) {
-        for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-            TransportMessageHandler handler = mTransportCallbacks.get(i);
-            if (callback == handler.mCallback) {
-                return handler;
-            }
-        }
-        return null;
+    private void dispatchMediaButton(Intent mediaButtonIntent) {
+        postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
     }
 
-    private boolean removeTransportControlsCallbackLocked(TransportControlsCallback callback) {
-        for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-            if (callback == mTransportCallbacks.get(i).mCallback) {
-                mTransportCallbacks.remove(i);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void postToTransportCallbacks(int what, Object obj) {
-        synchronized (mLock) {
-            for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-                mTransportCallbacks.get(i).post(what, obj);
-            }
-        }
-    }
-
-    private void postToTransportCallbacks(int what, Object obj, Bundle args) {
-        synchronized (mLock) {
-            for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-                mTransportCallbacks.get(i).post(what, obj, args);
-            }
-        }
-    }
-
-    private void postToTransportCallbacks(int what) {
-        postToTransportCallbacks(what, null);
-    }
-
-    private CallbackMessageHandler getHandlerForCallbackLocked(Callback cb) {
-        if (cb == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            CallbackMessageHandler handler = mCallbacks.get(i);
-            if (cb == handler.mCallback) {
-                return handler;
-            }
-        }
-        return null;
-    }
-
-    private boolean removeCallbackLocked(Callback cb) {
-        if (cb == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            CallbackMessageHandler handler = mCallbacks.get(i);
-            if (cb == handler.mCallback) {
-                mCallbacks.remove(i);
-                return true;
-            }
-        }
-        return false;
+    private void postToCallback(int what) {
+        postToCallback(what, null);
     }
 
     private void postCommand(String command, Bundle args, ResultReceiver resultCb) {
         Command cmd = new Command(command, args, resultCb);
-        synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.MSG_COMMAND, cmd);
-            }
-        }
+        postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd);
     }
 
-    private void postMediaButton(Intent mediaButtonIntent) {
+    private void postToCallback(int what, Object obj) {
+        postToCallback(what, obj, null);
+    }
+
+    private void postToCallback(int what, Object obj, Bundle extras) {
         synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
+            if (mCallback != null) {
+                mCallback.post(what, obj, extras);
             }
         }
     }
@@ -791,30 +666,16 @@
     }
 
     /**
-     * Receives generic commands or updates from controllers and the system.
-     * Callbacks may be registered using {@link #addCallback}.
+     * Receives media buttons, transport controls, and commands from controllers
+     * and the system. A callback may be set using {@link #setCallback}.
      */
     public abstract static class Callback {
+        private MediaSession mSession;
 
         public Callback() {
         }
 
         /**
-         * Called when a media button is pressed and this session has the
-         * highest priority or a controller sends a media button event to the
-         * session. TODO determine if using Intents identical to the ones
-         * RemoteControlClient receives is useful
-         * <p>
-         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
-         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
-         *
-         * @param mediaButtonIntent an intent containing the KeyEvent as an
-         *            extra
-         */
-        public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
-        }
-
-        /**
          * Called when a controller has sent a command to this session.
          * The owner of the session may handle custom commands but is not
          * required to.
@@ -826,13 +687,81 @@
         public void onCommand(@NonNull String command, @Nullable Bundle args,
                 @Nullable ResultReceiver cb) {
         }
-    }
 
-    /**
-     * Receives transport control commands. Callbacks may be registered using
-     * {@link #addTransportControlsCallback}.
-     */
-    public static abstract class TransportControlsCallback {
+        /**
+         * Called when a media button is pressed and this session has the
+         * highest priority or a controller sends a media button event to the
+         * session. The default behavior will call the relevant method if the
+         * action for it was set.
+         * <p>
+         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+         *
+         * @param mediaButtonIntent an intent containing the KeyEvent as an
+         *            extra
+         */
+        public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+            if (mSession != null
+                    && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+                KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+                    PlaybackState state = mSession.mPlaybackState;
+                    long validActions = state == null ? 0 : state.getActions();
+                    switch (ke.getKeyCode()) {
+                        case KeyEvent.KEYCODE_MEDIA_PLAY:
+                            if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+                                onPlay();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PAUSE:
+                            if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+                                onPause();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_NEXT:
+                            if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+                                onSkipToNext();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+                            if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+                                onSkipToPrevious();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_STOP:
+                            if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+                                onStop();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+                            if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+                                onFastForward();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_REWIND:
+                            if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+                                onRewind();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+                        case KeyEvent.KEYCODE_HEADSETHOOK:
+                            boolean isPlaying = state == null ? false
+                                    : state.getState() == PlaybackState.STATE_PLAYING;
+                            boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+                                    | PlaybackState.ACTION_PLAY)) != 0;
+                            boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+                                    | PlaybackState.ACTION_PAUSE)) != 0;
+                            if (isPlaying && canPause) {
+                                onPause();
+                            } else if (!isPlaying && canPlay) {
+                                onPlay();
+                            }
+                            break;
+                    }
+                }
+            }
+            return false;
+        }
 
         /**
          * Override to handle requests to begin playback.
@@ -920,6 +849,10 @@
          */
         public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
         }
+
+        private void setSession(MediaSession session) {
+            mSession = session;
+        }
     }
 
     /**
@@ -946,7 +879,7 @@
             MediaSession session = mMediaSession.get();
             try {
                 if (session != null) {
-                    session.postMediaButton(mediaButtonIntent);
+                    session.dispatchMediaButton(mediaButtonIntent);
                 }
             } finally {
                 if (cb != null) {
@@ -1232,44 +1165,6 @@
         }
     }
 
-    private class CallbackMessageHandler extends Handler {
-        private static final int MSG_MEDIA_BUTTON = 1;
-        private static final int MSG_COMMAND = 2;
-
-        private MediaSession.Callback mCallback;
-
-        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
-            super(looper, null, true);
-            mCallback = callback;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mLock) {
-                if (mCallback == null) {
-                    return;
-                }
-                switch (msg.what) {
-                    case MSG_MEDIA_BUTTON:
-                        mCallback.onMediaButtonEvent((Intent) msg.obj);
-                        break;
-                    case MSG_COMMAND:
-                        Command cmd = (Command) msg.obj;
-                        mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
-                        break;
-                }
-            }
-        }
-
-        public void post(int what, Object obj) {
-            obtainMessage(what, obj).sendToTarget();
-        }
-
-        public void post(int what, Object obj, int arg1) {
-            obtainMessage(what, arg1, 0, obj).sendToTarget();
-        }
-    }
-
     private static final class Command {
         public final String command;
         public final Bundle extras;
@@ -1282,7 +1177,8 @@
         }
     }
 
-    private class TransportMessageHandler extends Handler {
+    private class CallbackMessageHandler extends Handler {
+
         private static final int MSG_PLAY = 1;
         private static final int MSG_PLAY_URI = 2;
         private static final int MSG_PLAY_SEARCH = 3;
@@ -1296,12 +1192,14 @@
         private static final int MSG_SEEK_TO = 11;
         private static final int MSG_RATE = 12;
         private static final int MSG_CUSTOM_ACTION = 13;
+        private static final int MSG_MEDIA_BUTTON = 14;
+        private static final int MSG_COMMAND = 15;
 
-        private TransportControlsCallback mCallback;
+        private MediaSession.Callback mCallback;
 
-        public TransportMessageHandler(Looper looper, TransportControlsCallback cb) {
-            super(looper);
-            mCallback = cb;
+        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
+            super(looper, null, true);
+            mCallback = callback;
         }
 
         public void post(int what, Object obj, Bundle bundle) {
@@ -1318,6 +1216,10 @@
             post(what, null);
         }
 
+        public void post(int what, Object obj, int arg1) {
+            obtainMessage(what, arg1, 0, obj).sendToTarget();
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -1359,6 +1261,13 @@
                 case MSG_CUSTOM_ACTION:
                     mCallback.onCustomAction((String) msg.obj, msg.getData());
                     break;
+                case MSG_MEDIA_BUTTON:
+                    mCallback.onMediaButtonEvent((Intent) msg.obj);
+                    break;
+                case MSG_COMMAND:
+                    Command cmd = (Command) msg.obj;
+                    mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+                    break;
             }
         }
     }
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index f075ded..a182982 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -29,6 +29,9 @@
 import android.media.MediaMetadata;
 import android.media.MediaMetadataEditor;
 import android.media.MediaMetadataRetriever;
+import android.media.Rating;
+import android.media.RemoteControlClient;
+import android.media.RemoteControlClient.MetadataEditor;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -229,8 +232,7 @@
         }
     }
 
-    public void addRccListener(PendingIntent pi,
-            MediaSession.TransportControlsCallback listener) {
+    public void addRccListener(PendingIntent pi, MediaSession.Callback listener) {
         if (pi == null) {
             Log.w(TAG, "Pending intent was null, can't add rcc listener.");
             return;
@@ -247,10 +249,7 @@
                 // This is already the registered listener, ignore
                 return;
             }
-            // Otherwise it changed so we need to switch to the new one
-            holder.mSession.removeTransportControlsCallback(holder.mRccListener);
         }
-        holder.mSession.addTransportControlsCallback(listener, mHandler);
         holder.mRccListener = listener;
         holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
         holder.mSession.setFlags(holder.mFlags);
@@ -266,7 +265,6 @@
         }
         SessionHolder holder = getHolder(pi, false);
         if (holder != null && holder.mRccListener != null) {
-            holder.mSession.removeTransportControlsCallback(holder.mRccListener);
             holder.mRccListener = null;
             holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
             holder.mSession.setFlags(holder.mFlags);
@@ -288,8 +286,7 @@
             return;
         }
         if (holder.mMediaButtonListener != null) {
-            // Already have this listener registered, but update it anyway as
-            // the extras may have changed.
+            // Already have this listener registered
             if (DEBUG) {
                 Log.d(TAG, "addMediaButtonListener already added " + pi);
             }
@@ -300,11 +297,8 @@
         // set this flag
         holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
         holder.mSession.setFlags(holder.mFlags);
-        holder.mSession.addTransportControlsCallback(holder.mMediaButtonListener, mHandler);
-
-        holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context);
-        holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler);
         holder.mSession.setMediaButtonReceiver(pi);
+        holder.update();
         if (DEBUG) {
             Log.d(TAG, "addMediaButtonListener added " + pi);
         }
@@ -316,13 +310,10 @@
         }
         SessionHolder holder = getHolder(pi, false);
         if (holder != null && holder.mMediaButtonListener != null) {
-            holder.mSession.removeTransportControlsCallback(holder.mMediaButtonListener);
             holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
             holder.mSession.setFlags(holder.mFlags);
             holder.mMediaButtonListener = null;
 
-            holder.mSession.removeCallback(holder.mMediaButtonReceiver);
-            holder.mMediaButtonReceiver = null;
             holder.update();
             if (DEBUG) {
                 Log.d(TAG, "removeMediaButtonListener removed " + pi);
@@ -387,22 +378,7 @@
         }
     }
 
-    private static final class MediaButtonReceiver extends MediaSession.Callback {
-        private final PendingIntent mPendingIntent;
-        private final Context mContext;
-
-        public MediaButtonReceiver(PendingIntent pi, Context context) {
-            mPendingIntent = pi;
-            mContext = context;
-        }
-
-        @Override
-        public void onMediaButtonEvent(Intent mediaButtonIntent) {
-            MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
-        }
-    }
-
-    private static final class MediaButtonListener extends MediaSession.TransportControlsCallback {
+    private static final class MediaButtonListener extends MediaSession.Callback {
         private final PendingIntent mPendingIntent;
         private final Context mContext;
 
@@ -412,6 +388,12 @@
         }
 
         @Override
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+            MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
+            return true;
+        }
+
+        @Override
         public void onPlay() {
             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
         }
@@ -468,10 +450,11 @@
         public final MediaSession mSession;
         public final PendingIntent mPi;
         public MediaButtonListener mMediaButtonListener;
-        public MediaButtonReceiver mMediaButtonReceiver;
-        public MediaSession.TransportControlsCallback mRccListener;
+        public MediaSession.Callback mRccListener;
         public int mFlags;
 
+        public SessionCallback mCb;
+
         public SessionHolder(MediaSession session, PendingIntent pi) {
             mSession = session;
             mPi = pi;
@@ -479,8 +462,87 @@
 
         public void update() {
             if (mMediaButtonListener == null && mRccListener == null) {
+                mSession.setCallback(null);
                 mSession.release();
+                mCb = null;
                 mSessions.remove(mPi);
+            } else if (mCb == null) {
+                mCb = new SessionCallback();
+                mSession.setCallback(mCb);
+            }
+        }
+
+        private class SessionCallback extends MediaSession.Callback {
+
+            @Override
+            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
+                }
+                return true;
+            }
+
+            @Override
+            public void onPlay() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onPlay();
+                }
+            }
+
+            @Override
+            public void onPause() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onPause();
+                }
+            }
+
+            @Override
+            public void onSkipToNext() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onSkipToNext();
+                }
+            }
+
+            @Override
+            public void onSkipToPrevious() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onSkipToPrevious();
+                }
+            }
+
+            @Override
+            public void onFastForward() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onFastForward();
+                }
+            }
+
+            @Override
+            public void onRewind() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onRewind();
+                }
+            }
+
+            @Override
+            public void onStop() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onStop();
+                }
+            }
+
+            @Override
+            public void onSeekTo(long pos) {
+                if (mRccListener != null) {
+                    mRccListener.onSeekTo(pos);
+                }
+            }
+
+            @Override
+            public void onSetRating(Rating rating) {
+                if (mRccListener != null) {
+                    mRccListener.onSetRating(rating);
+                }
             }
         }
     }
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 65bd677..2ad8eae 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -845,20 +845,23 @@
         }
 
         /**
-         * Add a custom action to the playback state. Actions can be used to expose additional
-         * functionality to {@link MediaController MediaControllers} beyond what is offered by the
-         * standard transport controls.
+         * Add a custom action to the playback state. Actions can be used to
+         * expose additional functionality to {@link MediaController
+         * MediaControllers} beyond what is offered by the standard transport
+         * controls.
          * <p>
-         * e.g. start a radio station based on the current item or skip ahead by 30 seconds.
+         * e.g. start a radio station based on the current item or skip ahead by
+         * 30 seconds.
          *
-         * @param action An identifier for this action. It will be sent back to the
-         *               {@link MediaSession} through
-         *               {@link
-         *               MediaSession.TransportControlsCallback#onCustomAction(String, Bundle)}.
-         * @param name The display name for the action. If text is shown with the action or used
-         *             for accessibility, this is what should be used.
-         * @param icon The resource action of the icon that should be displayed for the action. The
-         *             resource should be in the package of the {@link MediaSession}.
+         * @param action An identifier for this action. It can be sent back to
+         *            the {@link MediaSession} through
+         *            {@link MediaController.TransportControls#sendCustomAction(String, Bundle)}.
+         * @param name The display name for the action. If text is shown with
+         *            the action or used for accessibility, this is what should
+         *            be used.
+         * @param icon The resource action of the icon that should be displayed
+         *            for the action. The resource should be in the package of
+         *            the {@link MediaSession}.
          * @return this
          */
         public Builder addCustomAction(String action, String name, int icon) {
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index c98a48d..7a853d1 100644
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -35,6 +35,6 @@
     // For hardware TvInputService
     void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo);
     void notifyHardwareRemoved(in TvInputHardwareInfo hardwareInfo);
-    void notifyHdmiCecDeviceAdded(in HdmiDeviceInfo deviceInfo);
-    void notifyHdmiCecDeviceRemoved(in HdmiDeviceInfo deviceInfo);
+    void notifyHdmiDeviceAdded(in HdmiDeviceInfo deviceInfo);
+    void notifyHdmiDeviceRemoved(in HdmiDeviceInfo deviceInfo);
 }
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index df648e7..de5d56f 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -24,7 +24,7 @@
  * @hide
  */
 oneway interface ITvInputServiceCallback {
-    void addHardwareTvInput(in int deviceID, in TvInputInfo inputInfo);
-    void addHdmiCecTvInput(in int logicalAddress, in TvInputInfo inputInfo);
+    void addHardwareTvInput(in int deviceId, in TvInputInfo inputInfo);
+    void addHdmiTvInput(in int logicalAddress, in TvInputInfo inputInfo);
     void removeTvInput(in String inputId);
 }
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 2e4d031..18136e9 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -19,7 +19,6 @@
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.text.TextUtils;
-import android.util.Log;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -963,8 +962,6 @@
  * </table>
  */
 public final class TvContentRating {
-    private static final String TAG = "TvContentRating";
-
     /** @hide */
     public static final Uri SYSTEM_CONTENT_RATING_SYSTEM_XML = Uri.parse(
             "android.resource://system/" + com.android.internal.R.xml.tv_content_rating_systems);
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 5a0ea0d..97a6b83 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -53,13 +53,25 @@
 
     private static final String PATH_CHANNEL = "channel";
     private static final String PATH_PROGRAM = "program";
-    private static final String PATH_INPUT = "input";
     private static final String PATH_PASSTHROUGH = "passthrough";
 
     /**
+     * An optional query, update or delete URI parameter that allows the caller to specify TV input
+     * ID to filter channels.
+     * @hide
+     */
+    public static final String PARAM_INPUT = "input";
+
+    /**
+     * An optional query, update or delete URI parameter that allows the caller to specify channel
+     * ID to filter programs.
+     * @hide
+     */
+    public static final String PARAM_CHANNEL = "channel";
+
+    /**
      * An optional query, update or delete URI parameter that allows the caller to specify start
      * time (in milliseconds since the epoch) to filter programs.
-     *
      * @hide
      */
     public static final String PARAM_START_TIME = "start_time";
@@ -67,7 +79,6 @@
     /**
      * An optional query, update or delete URI parameter that allows the caller to specify end time
      * (in milliseconds since the epoch) to filter programs.
-     *
      * @hide
      */
     public static final String PARAM_END_TIME = "end_time";
@@ -76,7 +87,6 @@
      * A query, update or delete URI parameter that allows the caller to operate on all or
      * browsable-only channels. If set to "true", the rows that contain non-browsable channels are
      * not affected.
-     *
      * @hide
      */
     public static final String PARAM_BROWSABLE_ONLY = "browsable_only";
@@ -84,7 +94,6 @@
     /**
      * A optional query, update or delete URI parameter that allows the caller to specify canonical
      * genre to filter programs.
-     *
      * @hide
      */
     public static final String PARAM_CANONICAL_GENRE = "canonical_genre";
@@ -116,17 +125,7 @@
      */
     public static final Uri buildChannelUriForPassthroughTvInput(String inputId) {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
-                .appendPath(PATH_PASSTHROUGH).build();
-    }
-
-    /**
-     * Returns true, if {@code channelUri} is a channel URI for a passthrough TV input.
-     * @hide
-     */
-    @SystemApi
-    public static final boolean isChannelUriForPassthroughTvInput(Uri channelUri) {
-        return channelUri.toString().endsWith(PATH_PASSTHROUGH);
+                .appendPath(PATH_PASSTHROUGH).appendPath(inputId).build();
     }
 
     /**
@@ -144,7 +143,7 @@
      * @param channelUri The URI of the channel whose logo is pointed to.
      */
     public static final Uri buildChannelLogoUri(Uri channelUri) {
-        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+        if (!isChannelUriForTunerTvInput(channelUri)) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
         return Uri.withAppendedPath(channelUri, Channels.Logo.CONTENT_DIRECTORY);
@@ -169,8 +168,8 @@
      * @hide
      */
     public static final Uri buildChannelsUriForInput(String inputId, boolean browsableOnly) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
+        return Channels.CONTENT_URI.buildUpon()
+                .appendQueryParameter(PARAM_INPUT, inputId)
                 .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
     }
 
@@ -194,8 +193,7 @@
 
         Uri uri;
         if (inputId == null) {
-            uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                    .appendPath(PATH_CHANNEL).build();
+            uri = Channels.CONTENT_URI;
         } else {
             uri = buildChannelsUriForInput(inputId, browsableOnly);
         }
@@ -217,9 +215,8 @@
      * @param channelId The ID of the channel to return programs for.
      */
     public static final Uri buildProgramsUriForChannel(long channelId) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_CHANNEL).appendPath(String.valueOf(channelId))
-                .appendPath(PATH_PROGRAM).build();
+        return Programs.CONTENT_URI.buildUpon()
+                .appendQueryParameter(PARAM_CHANNEL, String.valueOf(channelId)).build();
     }
 
     /**
@@ -228,7 +225,7 @@
      * @param channelUri The URI of the channel to return programs for.
      */
     public static final Uri buildProgramsUriForChannel(Uri channelUri) {
-        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+        if (!isChannelUriForTunerTvInput(channelUri)) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
         return buildProgramsUriForChannel(ContentUris.parseId(channelUri));
@@ -263,7 +260,7 @@
      */
     public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
             long endTime) {
-        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+        if (!isChannelUriForTunerTvInput(channelUri)) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
         return buildProgramsUriForChannel(ContentUris.parseId(channelUri), startTime, endTime);
@@ -279,41 +276,47 @@
         return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
     }
 
-    /**
-     * Extracts the {@link Channels#COLUMN_INPUT_ID} from a given URI.
-     *
-     * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(String)},
-     *            {@link #buildChannelsUriForInput(String, boolean)}, or
-     *            {@link #buildChannelsUriForCanonicalGenre(String, String, boolean)}.
-     * @hide
-     */
-    public static final String getInputId(Uri channelsUri) {
-        final List<String> paths = channelsUri.getPathSegments();
-        if (paths.size() < 3) {
-            throw new IllegalArgumentException("Not channels: " + channelsUri);
-        }
-        if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(2))) {
-            throw new IllegalArgumentException("Not channels: " + channelsUri);
-        }
-        return paths.get(1);
+    private static final boolean isTvUri(Uri uri) {
+        return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+                && AUTHORITY.equals(uri.getAuthority());
+    }
+
+    private static final boolean isTwoSegmentUriStartingWith(Uri uri, String pathSegment) {
+        List<String> pathSegments = uri.getPathSegments();
+        return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0));
     }
 
     /**
-     * Extracts the {@link Channels#_ID} from a given URI.
-     *
-     * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
-     *            {@link #buildProgramsUriForChannel(Uri, long, long)}.
+     * Returns true, if {@code uri} is a channel URI.
      * @hide
      */
-    public static final String getChannelId(Uri programsUri) {
-        final List<String> paths = programsUri.getPathSegments();
-        if (paths.size() < 3) {
-            throw new IllegalArgumentException("Not programs: " + programsUri);
-        }
-        if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
-            throw new IllegalArgumentException("Not programs: " + programsUri);
-        }
-        return paths.get(1);
+    public static final boolean isChannelUri(Uri uri) {
+        return isChannelUriForTunerTvInput(uri) || isChannelUriForPassthroughTvInput(uri);
+    }
+
+    /**
+     * Returns true, if {@code uri} is a channel URI for a tuner TV input.
+     * @hide
+     */
+    public static final boolean isChannelUriForTunerTvInput(Uri uri) {
+        return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_CHANNEL);
+    }
+
+    /**
+     * Returns true, if {@code uri} is a channel URI for a passthrough TV input.
+     * @hide
+     */
+    @SystemApi
+    public static final boolean isChannelUriForPassthroughTvInput(Uri uri) {
+        return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_PASSTHROUGH);
+    }
+
+    /**
+     * Returns true, if {@code uri} is a program URI.
+     * @hide
+     */
+    public static final boolean isProgramUri(Uri uri) {
+        return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_PROGRAM);
     }
 
 
@@ -684,10 +687,11 @@
          * The flag indicating whether this TV channel is searchable or not.
          * <p>
          * In some regions, it is not allowed to surface search results for a given channel without
-         * broadcaster's consent. This is used to impose such restriction. A value of 1 indicates
-         * the channel is searchable and can be included in search results, a value of 0 indicates
-         * the channel and its TV programs are hidden from search. If not specified, this value is
-         * set to 1 (searchable) by default.
+         * broadcaster's consent. This is used to impose such restriction. Channels marked with
+         * "not searchable" cannot be used by other services except for the system service that
+         * shows the TV content. A value of 1 indicates the channel is searchable and can be
+         * included in search results, a value of 0 indicates the channel and its TV programs are
+         * hidden from search. If not specified, this value is set to 1 (searchable) by default.
          * </p><p>
          * Type: INTEGER (boolean)
          * </p>
@@ -1103,7 +1107,6 @@
     /**
      * Column definitions for the TV programs that the user watched. Applications do not have access
      * to this table.
-     *
      * @hide
      */
     public static final class WatchedPrograms implements BaseTvColumns {
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 8feb7e6..8d0e986 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -103,10 +103,10 @@
 
     private static final String XML_START_TAG_NAME = "tv-input";
     private static final String DELIMITER_INFO_IN_ID = "/";
-    private static final String PREFIX_CEC_DEVICE = "CEC";
+    private static final String PREFIX_HDMI_DEVICE = "HDMI";
     private static final String PREFIX_HARDWARE_DEVICE = "HW";
-    private static final int LENGTH_CEC_PHYSICAL_ADDRESS = 4;
-    private static final int LENGTH_CEC_LOGICAL_ADDRESS = 2;
+    private static final int LENGTH_HDMI_PHYSICAL_ADDRESS = 4;
+    private static final int LENGTH_HDMI_LOGICAL_ADDRESS = 2;
 
     private final ResolveInfo mService;
     private final String mId;
@@ -155,7 +155,7 @@
      * instantiating it from the given Context, ResolveInfo, and HdmiDeviceInfo.
      *
      * @param service The ResolveInfo returned from the package manager about this TV input service.
-     * @param cecInfo The HdmiDeviceInfo for a HDMI CEC logical device.
+     * @param deviceInfo The HdmiDeviceInfo for a HDMI CEC logical device.
      * @param parentId The ID of this TV input's parent input. {@code null} if none exists.
      * @param iconUri The {@link android.net.Uri} to load the icon image.
      *        {@see android.content.ContentResolver#openInputStream}. If it is null, the application
@@ -166,12 +166,12 @@
      */
     @SystemApi
     public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
-            HdmiDeviceInfo cecInfo, String parentId, String label, Uri iconUri)
+            HdmiDeviceInfo deviceInfo, String parentId, String label, Uri iconUri)
                     throws XmlPullParserException, IOException {
-        boolean isConnectedToHdmiSwitch = (cecInfo.getPhysicalAddress() & 0x0FFF) != 0;
-        return createTvInputInfo(context, service, generateInputIdForHdmiCec(
+        boolean isConnectedToHdmiSwitch = (deviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
+        return createTvInputInfo(context, service, generateInputIdForHdmiDevice(
                 new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
-                cecInfo), parentId, TYPE_HDMI, label, iconUri, isConnectedToHdmiSwitch);
+                deviceInfo), parentId, TYPE_HDMI, label, iconUri, isConnectedToHdmiSwitch);
     }
 
     /**
@@ -497,16 +497,16 @@
      * Used to generate an input id from a ComponentName and HdmiDeviceInfo.
      *
      * @param name the component name for generating an input id.
-     * @param cecInfo HdmiDeviceInfo describing this TV input.
-     * @return the generated input id for the given {@code name} and {@code cecInfo}.
+     * @param deviceInfo HdmiDeviceInfo describing this TV input.
+     * @return the generated input id for the given {@code name} and {@code deviceInfo}.
      */
-    private static final String generateInputIdForHdmiCec(
-            ComponentName name, HdmiDeviceInfo cecInfo) {
-        // Example of the format : "/CEC%04X%02X"
-        String format = String.format("%s%s%%0%sX%%0%sX", DELIMITER_INFO_IN_ID, PREFIX_CEC_DEVICE,
-                LENGTH_CEC_PHYSICAL_ADDRESS, LENGTH_CEC_LOGICAL_ADDRESS);
+    private static final String generateInputIdForHdmiDevice(
+            ComponentName name, HdmiDeviceInfo deviceInfo) {
+        // Example of the format : "/HDMI%04X%02X"
+        String format = String.format("%s%s%%0%sX%%0%sX", DELIMITER_INFO_IN_ID, PREFIX_HDMI_DEVICE,
+                LENGTH_HDMI_PHYSICAL_ADDRESS, LENGTH_HDMI_LOGICAL_ADDRESS);
         return name.flattenToShortString() + String.format(format,
-                cecInfo.getPhysicalAddress(), cecInfo.getLogicalAddress());
+                deviceInfo.getPhysicalAddress(), deviceInfo.getLogicalAddress());
     }
 
     /**
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6a41c61..408ee7b 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -28,7 +28,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -42,7 +41,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
-import android.view.SurfaceView;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.CaptioningManager;
@@ -92,7 +90,7 @@
      * Handler instance to handle request from TV Input Manager Service. Should be run in the main
      * looper to be synchronously run with {@code Session.mHandler}.
      */
-    private Handler mServiceHandler = new ServiceHandler();
+    private final Handler mServiceHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
             new RemoteCallbackList<ITvInputServiceCallback>();
 
@@ -142,15 +140,15 @@
             }
 
             @Override
-            public void notifyHdmiCecDeviceAdded(HdmiDeviceInfo cecDeviceInfo) {
-                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_CEC_TV_INPUT,
-                        cecDeviceInfo).sendToTarget();
+            public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
+                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_TV_INPUT,
+                        deviceInfo).sendToTarget();
             }
 
             @Override
-            public void notifyHdmiCecDeviceRemoved(HdmiDeviceInfo cecDeviceInfo) {
-                mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_CEC_TV_INPUT,
-                        cecDeviceInfo).sendToTarget();
+            public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
+                mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_TV_INPUT,
+                        deviceInfo).sendToTarget();
             }
         };
     }
@@ -203,27 +201,27 @@
 
     /**
      * Returns a new {@link TvInputInfo} object if this service is responsible for
-     * {@code cecDeviceInfo}; otherwise, return {@code null}. Override to modify default behavior
-     * of ignoring all HDMI CEC logical input device.
+     * {@code deviceInfo}; otherwise, return {@code null}. Override to modify default behavior of
+     * ignoring all HDMI logical input device.
      *
-     * @param cecDeviceInfo {@link HdmiDeviceInfo} object just added.
+     * @param deviceInfo {@link HdmiDeviceInfo} object just added.
      * @hide
      */
     @SystemApi
-    public TvInputInfo onHdmiCecDeviceAdded(HdmiDeviceInfo cecDeviceInfo) {
+    public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
         return null;
     }
 
     /**
-     * Returns the input ID for {@code logicalAddress} if it is handled by this service;
-     * otherwise, return {@code null}. Override to modify default behavior of ignoring all HDMI CEC
-     * logical input device.
+     * Returns the input ID for {@code logicalAddress} if it is handled by this service; otherwise,
+     * return {@code null}. Override to modify default behavior of ignoring all HDMI logical input
+     * device.
      *
-     * @param cecDeviceInfo {@link HdmiDeviceInfo} object just removed.
+     * @param deviceInfo {@link HdmiDeviceInfo} object just removed.
      * @hide
      */
     @SystemApi
-    public String onHdmiCecDeviceRemoved(HdmiDeviceInfo cecDeviceInfo) {
+    public String onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
         return null;
     }
 
@@ -1164,8 +1162,8 @@
         private static final int DO_NOTIFY_SESSION_CREATED = 2;
         private static final int DO_ADD_HARDWARE_TV_INPUT = 3;
         private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4;
-        private static final int DO_ADD_HDMI_CEC_TV_INPUT = 5;
-        private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 6;
+        private static final int DO_ADD_HDMI_TV_INPUT = 5;
+        private static final int DO_REMOVE_HDMI_TV_INPUT = 6;
 
         private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
             int n = mCallbacks.beginBroadcast();
@@ -1179,12 +1177,11 @@
             mCallbacks.finishBroadcast();
         }
 
-        private void broadcastAddHdmiCecTvInput(
-                int logicalAddress, TvInputInfo inputInfo) {
+        private void broadcastAddHdmiTvInput(int logicalAddress, TvInputInfo inputInfo) {
             int n = mCallbacks.beginBroadcast();
             for (int i = 0; i < n; ++i) {
                 try {
-                    mCallbacks.getBroadcastItem(i).addHdmiCecTvInput(logicalAddress, inputInfo);
+                    mCallbacks.getBroadcastItem(i).addHdmiTvInput(logicalAddress, inputInfo);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error while broadcasting.", e);
                 }
@@ -1286,17 +1283,17 @@
                     }
                     return;
                 }
-                case DO_ADD_HDMI_CEC_TV_INPUT: {
-                    HdmiDeviceInfo cecDeviceInfo = (HdmiDeviceInfo) msg.obj;
-                    TvInputInfo inputInfo = onHdmiCecDeviceAdded(cecDeviceInfo);
+                case DO_ADD_HDMI_TV_INPUT: {
+                    HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
+                    TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
                     if (inputInfo != null) {
-                        broadcastAddHdmiCecTvInput(cecDeviceInfo.getLogicalAddress(), inputInfo);
+                        broadcastAddHdmiTvInput(deviceInfo.getLogicalAddress(), inputInfo);
                     }
                     return;
                 }
-                case DO_REMOVE_HDMI_CEC_TV_INPUT: {
-                    HdmiDeviceInfo cecDeviceInfo = (HdmiDeviceInfo) msg.obj;
-                    String inputId = onHdmiCecDeviceRemoved(cecDeviceInfo);
+                case DO_REMOVE_HDMI_TV_INPUT: {
+                    HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
+                    String inputId = onHdmiDeviceRemoved(deviceInfo);
                     if (inputId != null) {
                         broadcastRemoveTvInput(inputId);
                     }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index d033f76..f1e1099 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -58,12 +58,17 @@
     EVENT_SET_CALLBACK = 2,
 };
 
-struct CryptoErrorCodes {
+static struct CryptoErrorCodes {
     jint cryptoErrorNoKey;
     jint cryptoErrorKeyExpired;
     jint cryptoErrorResourceBusy;
 } gCryptoErrorCodes;
 
+static struct CodecActionCodes {
+    jint codecActionTransient;
+    jint codecActionRecoverable;
+} gCodecActionCodes;
+
 struct fields_t {
     jfieldID context;
     jmethodID postEventFromNativeID;
@@ -101,10 +106,11 @@
             PRIORITY_FOREGROUND);
 
     if (nameIsType) {
-        mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
+        mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus);
     } else {
-        mCodec = MediaCodec::CreateByComponentName(mLooper, name);
+        mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus);
     }
+    CHECK((mCodec != NULL) != (mInitStatus != OK));
 }
 
 void JMediaCodec::cacheJavaObjects(JNIEnv *env) {
@@ -147,7 +153,7 @@
 }
 
 status_t JMediaCodec::initCheck() const {
-    return mCodec != NULL ? OK : NO_INIT;
+    return mInitStatus;
 }
 
 void JMediaCodec::registerSelf() {
@@ -158,6 +164,7 @@
     if (mCodec != NULL) {
         mCodec->release();
         mCodec.clear();
+        mInitStatus = NO_INIT;
     }
 
     if (mLooper != NULL) {
@@ -554,6 +561,34 @@
     }
 }
 
+static jthrowable createCodecException(
+        JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/media/MediaCodec$CodecException"));
+    CHECK(clazz.get() != NULL);
+
+    const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V");
+    CHECK(ctor != NULL);
+
+    ScopedLocalRef<jstring> msgObj(
+            env, env->NewStringUTF(msg != NULL ? msg : String8::format("Error %#x", err)));
+
+    // translate action code to Java equivalent
+    switch (actionCode) {
+    case ACTION_CODE_TRANSIENT:
+        actionCode = gCodecActionCodes.codecActionTransient;
+        break;
+    case ACTION_CODE_RECOVERABLE:
+        actionCode = gCodecActionCodes.codecActionRecoverable;
+        break;
+    default:
+        actionCode = 0;  // everything else is fatal
+        break;
+    }
+
+    return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get());
+}
+
 void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
     int32_t arg1, arg2 = 0;
     jobject obj = NULL;
@@ -605,19 +640,8 @@
             CHECK(msg->findInt32("err", &err));
             CHECK(msg->findInt32("actionCode", &actionCode));
 
-            // use Integer object to pass the action code
-            ScopedLocalRef<jclass> clazz(
-                    env, env->FindClass("android/media/MediaCodec$CodecException"));
-            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V");
-
-            AString str;
-            const char *detail = "Unknown error";
-            if (msg->findString("detail", &str)) {
-                detail = str.c_str();
-            }
-            jstring msgObj = env->NewStringUTF(detail);
-
-            obj = env->NewObject(clazz.get(), ctor, err, actionCode, msgObj);
+            // note that DRM errors could conceivably alias into a CodecException
+            obj = (jobject)createCodecException(env, err, actionCode);
 
             if (obj == NULL) {
                 if (env->ExceptionCheck()) {
@@ -705,6 +729,11 @@
     setMediaCodec(env, thiz, NULL);
 }
 
+static void throwCodecException(JNIEnv *env, status_t err, int32_t actionCode, const char *msg) {
+    jthrowable exception = createCodecException(env, err, actionCode, msg);
+    env->Throw(exception);
+}
+
 static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec$CryptoException"));
@@ -716,7 +745,7 @@
 
     jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error");
 
-    /* translate OS errors to Java API CryptoException errorCodes */
+    /* translate OS errors to Java API CryptoException errorCodes (which are positive) */
     switch (err) {
         case ERROR_DRM_NO_LICENSE:
             err = gCryptoErrorCodes.cryptoErrorNoKey;
@@ -727,7 +756,7 @@
         case ERROR_DRM_RESOURCE_BUSY:
             err = gCryptoErrorCodes.cryptoErrorResourceBusy;
             break;
-        default:
+        default:  /* Other negative DRM error codes go out as is. */
             break;
     }
 
@@ -738,13 +767,8 @@
 }
 
 static jint throwExceptionAsNecessary(
-        JNIEnv *env, status_t err, const char *msg = NULL) {
-    if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
-        // We'll throw our custom MediaCodec.CryptoException
-        throwCryptoException(env, err, msg);
-        return 0;
-    }
-
+        JNIEnv *env, status_t err, int32_t actionCode = ACTION_CODE_FATAL,
+        const char *msg = NULL) {
     switch (err) {
         case OK:
             return 0;
@@ -758,20 +782,18 @@
         case INFO_OUTPUT_BUFFERS_CHANGED:
             return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
 
-        case ERROR_DRM_NO_LICENSE:
-        case ERROR_DRM_LICENSE_EXPIRED:
-        case ERROR_DRM_RESOURCE_BUSY:
-            throwCryptoException(env, err, msg);
-            break;
+        case INVALID_OPERATION:
+            jniThrowException(env, "java/lang/IllegalStateException", msg);
+            return 0;
 
         default:
-        {
-            jniThrowException(env, "java/lang/IllegalStateException", msg);
-            break;
-        }
+            if (isCryptoError(err)) {
+                throwCryptoException(env, err, msg);
+                return 0;
+            }
+            throwCodecException(env, err, actionCode, msg);
+            return 0;
     }
-
-    return 0;
 }
 
 static void android_media_MediaCodec_native_setCallback(
@@ -781,7 +803,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -800,7 +822,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -842,7 +864,7 @@
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -865,13 +887,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", "no codec found");
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
     status_t err = codec->start();
 
-    throwExceptionAsNecessary(env, err, "start failed");
+    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, "start failed");
 }
 
 static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
@@ -880,7 +902,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -895,8 +917,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        // should never be here
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -904,7 +925,10 @@
     if (err != OK) {
         // treat all errors as fatal for now, though resource not available
         // errors could be treated as transient.
-        err = 0x80000000;
+        // we also should avoid sending INVALID_OPERATION here due to
+        // the transitory nature of reset(), it should not inadvertently
+        // trigger an IllegalStateException.
+        err = UNKNOWN_ERROR;
     }
     throwExceptionAsNecessary(env, err);
 }
@@ -915,7 +939,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -937,7 +961,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -947,7 +971,7 @@
             index, offset, size, timestampUs, flags, &errorDetailMsg);
 
     throwExceptionAsNecessary(
-            env, err, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+            env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
 }
 
 static void android_media_MediaCodec_queueSecureInputBuffer(
@@ -963,7 +987,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1005,7 +1029,7 @@
         err = -ERANGE;
     // subSamples array may silently overflow if number of samples are too large.  Use
     // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
-    } else if ( CC_UNLIKELY(numSubSamples >= INT32_MAX / sizeof(*subSamples)) ) {
+    } else if ( CC_UNLIKELY(numSubSamples >= (signed)(INT32_MAX / sizeof(*subSamples))) ) {
         err = -EINVAL;
     } else {
         jboolean isCopy;
@@ -1089,7 +1113,7 @@
     subSamples = NULL;
 
     throwExceptionAsNecessary(
-            env, err, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+            env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
 }
 
 static jint android_media_MediaCodec_dequeueInputBuffer(
@@ -1099,7 +1123,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return -1;
     }
 
@@ -1120,7 +1144,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return 0;
     }
 
@@ -1143,7 +1167,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1158,7 +1182,7 @@
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1174,7 +1198,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1197,7 +1221,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1220,7 +1244,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1246,7 +1270,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1272,7 +1296,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1298,7 +1322,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1321,7 +1345,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1340,7 +1364,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1409,13 +1433,25 @@
     CHECK(field != NULL);
     gCryptoErrorCodes.cryptoErrorResourceBusy =
         env->GetStaticIntField(clazz.get(), field);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$CodecException"));
+    CHECK(clazz.get() != NULL);
+    field = env->GetStaticFieldID(clazz.get(), "ACTION_TRANSIENT", "I");
+    CHECK(field != NULL);
+    gCodecActionCodes.codecActionTransient =
+        env->GetStaticIntField(clazz.get(), field);
+
+    field = env->GetStaticFieldID(clazz.get(), "ACTION_RECOVERABLE", "I");
+    CHECK(field != NULL);
+    gCodecActionCodes.codecActionRecoverable =
+        env->GetStaticIntField(clazz.get(), field);
 }
 
 static void android_media_MediaCodec_native_setup(
         JNIEnv *env, jobject thiz,
         jstring name, jboolean nameIsType, jboolean encoder) {
     if (name == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
         return;
     }
 
@@ -1427,19 +1463,23 @@
 
     sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
 
-    status_t err = codec->initCheck();
-
-    env->ReleaseStringUTFChars(name, tmp);
-    tmp = NULL;
-
-    if (err != OK) {
-        jniThrowException(
-                env,
-                "java/io/IOException",
-                "Failed to allocate component instance");
+    const status_t err = codec->initCheck();
+    if (err == NAME_NOT_FOUND) {
+        // fail and do not try again.
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                String8::format("Failed to initialize %s, error %#x", tmp, err));
+        env->ReleaseStringUTFChars(name, tmp);
+        return;
+    } else if (err != OK) {
+        // believed possible to try again
+        jniThrowException(env, "java/io/IOException",
+                String8::format("Failed to find matching codec %s, error %#x", tmp, err));
+        env->ReleaseStringUTFChars(name, tmp);
         return;
     }
 
+    env->ReleaseStringUTFChars(name, tmp);
+
     codec->registerSelf();
 
     setMediaCodec(env,thiz, codec);
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index f84a16a..9f2785a 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -135,6 +135,8 @@
 
     sp<AMessage> mCallbackNotification;
 
+    status_t mInitStatus;
+
     status_t createByteBufferFromABuffer(
             JNIEnv *env, bool readOnly, bool clearBuffer, const sp<ABuffer> &buffer,
             jobject *buf) const;
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index ed1eeb9..ecba02a 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -21,6 +21,8 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaCodecList.h>
+#include <media/IMediaCodecList.h>
+#include <media/MediaCodecInfo.h>
 
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
@@ -29,20 +31,41 @@
 
 using namespace android;
 
+static sp<IMediaCodecList> getCodecList(JNIEnv *env) {
+    sp<IMediaCodecList> mcl = MediaCodecList::getInstance();
+    if (mcl == NULL) {
+        // This should never happen unless something is really wrong
+        jniThrowException(
+                    env, "java/lang/RuntimeException", "cannot get MediaCodecList");
+    }
+    return mcl;
+}
+
 static jint android_media_MediaCodecList_getCodecCount(
         JNIEnv *env, jobject thiz) {
-    return MediaCodecList::getInstance()->countCodecs();
+    sp<IMediaCodecList> mcl = getCodecList(env);
+    if (mcl == NULL) {
+        // Runtime exception already pending.
+        return 0;
+    }
+    return mcl->countCodecs();
 }
 
 static jstring android_media_MediaCodecList_getCodecName(
         JNIEnv *env, jobject thiz, jint index) {
-    const char *name = MediaCodecList::getInstance()->getCodecName(index);
+    sp<IMediaCodecList> mcl = getCodecList(env);
+    if (mcl == NULL) {
+        // Runtime exception already pending.
+        return NULL;
+    }
 
-    if (name == NULL) {
+    const sp<MediaCodecInfo> &info = mcl->getCodecInfo(index);
+    if (info == NULL) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
         return NULL;
     }
 
+    const char *name = info->getCodecName();
     return env->NewStringUTF(name);
 }
 
@@ -54,33 +77,56 @@
     }
 
     const char *nameStr = env->GetStringUTFChars(name, NULL);
-
     if (nameStr == NULL) {
         // Out of memory exception already pending.
         return -ENOENT;
     }
 
-    jint ret = MediaCodecList::getInstance()->findCodecByName(nameStr);
+    sp<IMediaCodecList> mcl = getCodecList(env);
+    if (mcl == NULL) {
+        // Runtime exception already pending.
+        return -ENOENT;
+    }
+
+    jint ret = mcl->findCodecByName(nameStr);
     env->ReleaseStringUTFChars(name, nameStr);
     return ret;
 }
 
 static jboolean android_media_MediaCodecList_isEncoder(
         JNIEnv *env, jobject thiz, jint index) {
-    return MediaCodecList::getInstance()->isEncoder(index);
+    sp<IMediaCodecList> mcl = getCodecList(env);
+    if (mcl == NULL) {
+        // Runtime exception already pending.
+        return false;
+    }
+
+    const sp<MediaCodecInfo> &info = mcl->getCodecInfo(index);
+    if (info == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return info->isEncoder();
 }
 
 static jarray android_media_MediaCodecList_getSupportedTypes(
         JNIEnv *env, jobject thiz, jint index) {
-    Vector<AString> types;
-    status_t err =
-        MediaCodecList::getInstance()->getSupportedTypes(index, &types);
+    sp<IMediaCodecList> mcl = getCodecList(env);
+    if (mcl == NULL) {
+        // Runtime exception already pending.
+        return NULL;
+    }
 
-    if (err != OK) {
+    const sp<MediaCodecInfo> &info = mcl->getCodecInfo(index);
+    if (info == NULL) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
         return NULL;
     }
 
+    Vector<AString> types;
+    info->getSupportedMimes(&types);
+
     jclass clazz = env->FindClass("java/lang/String");
     CHECK(clazz != NULL);
 
@@ -103,6 +149,18 @@
         return NULL;
     }
 
+    sp<IMediaCodecList> mcl = getCodecList(env);
+    if (mcl == NULL) {
+        // Runtime exception already pending.
+        return NULL;
+    }
+
+    const sp<MediaCodecInfo> &info = mcl->getCodecInfo(index);
+    if (info == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
+    }
+
     const char *typeStr = env->GetStringUTFChars(type, NULL);
 
     if (typeStr == NULL) {
@@ -110,30 +168,27 @@
         return NULL;
     }
 
-    Vector<MediaCodecList::ProfileLevel> profileLevels;
+    Vector<MediaCodecInfo::ProfileLevel> profileLevels;
     Vector<uint32_t> colorFormats;
-    uint32_t flags;
-    sp<AMessage> capabilities;
 
     sp<AMessage> defaultFormat = new AMessage();
     defaultFormat->setString("mime", typeStr);
 
     // TODO query default-format also from codec/codec list
-
-    status_t err =
-        MediaCodecList::getInstance()->getCodecCapabilities(
-                index, typeStr, &profileLevels, &colorFormats, &flags,
-                &capabilities);
-
-    bool isEncoder = MediaCodecList::getInstance()->isEncoder(index);
-
-    env->ReleaseStringUTFChars(type, typeStr);
-    typeStr = NULL;
-
-    if (err != OK) {
+    const sp<MediaCodecInfo::Capabilities> &capabilities =
+        info->getCapabilitiesFor(typeStr);
+    if (capabilities == NULL) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
         return NULL;
     }
+    env->ReleaseStringUTFChars(type, typeStr);
+    typeStr = NULL;
+
+    capabilities->getSupportedColorFormats(&colorFormats);
+    capabilities->getSupportedProfileLevels(&profileLevels);
+    uint32_t flags = capabilities->getFlags();
+    sp<AMessage> details = capabilities->getDetails();
+    bool isEncoder = info->isEncoder();
 
     jobject defaultFormatObj = NULL;
     if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
@@ -141,7 +196,7 @@
     }
 
     jobject infoObj = NULL;
-    if (ConvertMessageToMap(env, capabilities, &infoObj)) {
+    if (ConvertMessageToMap(env, details, &infoObj)) {
         env->DeleteLocalRef(defaultFormatObj);
         return NULL;
     }
@@ -164,7 +219,7 @@
         env->GetFieldID(profileLevelClazz, "level", "I");
 
     for (size_t i = 0; i < profileLevels.size(); ++i) {
-        const MediaCodecList::ProfileLevel &src = profileLevels.itemAt(i);
+        const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
 
         jobject profileLevelObj = env->AllocObject(profileLevelClazz);
 
@@ -192,26 +247,6 @@
             profileLevelArray, colorFormatsArray, isEncoder, flags,
             defaultFormatObj, infoObj);
 
-#if 0
-    jfieldID profileLevelsField = env->GetFieldID(
-            capsClazz,
-            "profileLevels",
-            "[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
-
-    env->SetObjectField(caps, profileLevelsField, profileLevelArray);
-
-    jfieldID flagsField =
-        env->GetFieldID(capsClazz, "mFlagsVerified", "I");
-
-    env->SetIntField(caps, flagsField, flags);
-
-    jfieldID colorFormatsField = env->GetFieldID(
-            capsClazz, "colorFormats", "[I");
-
-    env->SetObjectField(caps, colorFormatsField, colorFormatsArray);
-
-#endif
-
     env->DeleteLocalRef(profileLevelArray);
     profileLevelArray = NULL;
 
diff --git a/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java
index 6bb1f2c..e664fb9 100644
--- a/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java
+++ b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java
@@ -26,38 +26,70 @@
  */
 public class AppearAnimationUtils implements AppearAnimationCreator<View> {
 
-    public static final long APPEAR_DURATION = 220;
+    public static final long DEFAULT_APPEAR_DURATION = 220;
 
-    private final Interpolator mLinearOutSlowIn;
+    private final Interpolator mInterpolator;
     private final float mStartTranslation;
     private final AppearAnimationProperties mProperties = new AppearAnimationProperties();
     private final float mDelayScale;
+    private final long mDuration;
 
     public AppearAnimationUtils(Context ctx) {
-        this(ctx, 1.0f, 1.0f);
+        this(ctx, DEFAULT_APPEAR_DURATION,
+                ctx.getResources().getDimensionPixelSize(R.dimen.appear_y_translation_start),
+                1.0f,
+                AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in));
     }
 
-    public AppearAnimationUtils(Context ctx, float delayScaleFactor,
-            float translationScaleFactor) {
-        mLinearOutSlowIn = AnimationUtils.loadInterpolator(
-                ctx, android.R.interpolator.linear_out_slow_in);
+    public AppearAnimationUtils(Context ctx, long duration, float translationScaleFactor,
+            float delayScaleFactor, Interpolator interpolator) {
+        mInterpolator = interpolator;
         mStartTranslation = ctx.getResources().getDimensionPixelOffset(
                 R.dimen.appear_y_translation_start) * translationScaleFactor;
         mDelayScale = delayScaleFactor;
+        mDuration = duration;
     }
 
     public void startAppearAnimation(View[][] objects, final Runnable finishListener) {
         startAppearAnimation(objects, finishListener, this);
     }
 
+    public void startAppearAnimation(View[] objects, final Runnable finishListener) {
+        startAppearAnimation(objects, finishListener, this);
+    }
+
     public <T> void startAppearAnimation(T[][] objects, final Runnable finishListener,
             AppearAnimationCreator<T> creator) {
         AppearAnimationProperties properties = getDelays(objects);
         startAnimations(properties, objects, finishListener, creator);
     }
 
+    public <T> void startAppearAnimation(T[] objects, final Runnable finishListener,
+            AppearAnimationCreator<T> creator) {
+        AppearAnimationProperties properties = getDelays(objects);
+        startAnimations(properties, objects, finishListener, creator);
+    }
+
+    private <T> void startAnimations(AppearAnimationProperties properties, T[] objects,
+            final Runnable finishListener, AppearAnimationCreator<T> creator) {
+        if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) {
+            finishListener.run();
+            return;
+        }
+        for (int row = 0; row < properties.delays.length; row++) {
+            long[] columns = properties.delays[row];
+            long delay = columns[0];
+            Runnable endRunnable = null;
+            if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == 0) {
+                endRunnable = finishListener;
+            }
+            creator.createAnimation(objects[row], delay, mDuration,
+                    mStartTranslation, mInterpolator, endRunnable);
+        }
+    }
+
     private <T> void startAnimations(AppearAnimationProperties properties, T[][] objects,
-            final Runnable finishListener, AppearAnimationCreator creator) {;
+            final Runnable finishListener, AppearAnimationCreator<T> creator) {
         if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) {
             finishListener.run();
             return;
@@ -70,15 +102,32 @@
                 if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == col) {
                     endRunnable = finishListener;
                 }
-                creator.createAnimation(objects[row][col], delay, APPEAR_DURATION,
-                        mStartTranslation, mLinearOutSlowIn, endRunnable);
+                creator.createAnimation(objects[row][col], delay, mDuration,
+                        mStartTranslation, mInterpolator, endRunnable);
             }
         }
+    }
 
+    private <T> AppearAnimationProperties getDelays(T[] items) {
+        long maxDelay = -1;
+        mProperties.maxDelayColIndex = -1;
+        mProperties.maxDelayRowIndex = -1;
+        mProperties.delays = new long[items.length][];
+        for (int row = 0; row < items.length; row++) {
+            mProperties.delays[row] = new long[1];
+            long delay = calculateDelay(row, 0);
+            mProperties.delays[row][0] = delay;
+            if (items[row] != null && delay > maxDelay) {
+                maxDelay = delay;
+                mProperties.maxDelayColIndex = 0;
+                mProperties.maxDelayRowIndex = row;
+            }
+        }
+        return mProperties;
     }
 
     private <T> AppearAnimationProperties getDelays(T[][] items) {
-        long maxDelay = 0;
+        long maxDelay = -1;
         mProperties.maxDelayColIndex = -1;
         mProperties.maxDelayRowIndex = -1;
         mProperties.delays = new long[items.length][];
@@ -103,7 +152,7 @@
     }
 
     public Interpolator getInterpolator() {
-        return mLinearOutSlowIn;
+        return mInterpolator;
     }
 
     public float getStartTranslation() {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index d5dcd71..12bbd35 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -37,6 +37,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.LinearLayout;
@@ -108,8 +109,10 @@
     public KeyguardPatternView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mAppearAnimationUtils = new AppearAnimationUtils(context, 1.5f /* delayScale */,
-                2.0f /* transitionScale */);
+        mAppearAnimationUtils = new AppearAnimationUtils(context,
+                AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* delayScale */,
+                2.0f /* transitionScale */, AnimationUtils.loadInterpolator(
+                        mContext, android.R.interpolator.linear_out_slow_in));
     }
 
     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
@@ -420,7 +423,7 @@
                 this);
         if (!TextUtils.isEmpty(mHelpMessage.getText())) {
             mAppearAnimationUtils.createAnimation(mHelpMessage, 0,
-                    AppearAnimationUtils.APPEAR_DURATION,
+                    AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
                     mAppearAnimationUtils.getStartTranslation(),
                     mAppearAnimationUtils.getInterpolator(),
                     null /* finishRunnable */);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 9dd6bc2..1835b8e 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -57,7 +57,7 @@
     }
 
     public KeyguardSecurityContainer(Context context) {
-        this(null, null, 0);
+        this(context, null, 0);
     }
 
     public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
@@ -240,10 +240,13 @@
 
         boolean showTimeout = false;
         if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
-            // If we reach this code, it means the user has installed a DevicePolicyManager
-            // that requests device wipe after N attempts.  Once we get below the grace
-            // period, we'll post this dialog every time as a clear warning until the
-            // bombshell hits and the device is wiped.
+            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+            // N attempts. Once we get below the grace period, we post this dialog every time as a
+            // clear warning until the deletion fires.
+            //
+            // TODO: Show a different dialog depending on whether the device will be completely
+            // wiped (i.e. policy is set for the primary profile of the USER_OWNER) or a single
+            // secondary user or managed profile will be removed.
             if (remainingBeforeWipe > 0) {
                 showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe);
             } else {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 58cbaba..0d79ee2 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -229,13 +229,16 @@
     private SparseBooleanArray mUserFingerprintRecognized = new SparseBooleanArray();
 
     @Override
-    public void onTrustChanged(boolean enabled, int userId) {
+    public void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser) {
         mUserHasTrust.put(userId, enabled);
 
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onTrustChanged(userId);
+                if (enabled && initiatedByUser) {
+                    cb.onTrustInitiatedByUser(userId);
+                }
             }
         }
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 4827e79..0acb9d0 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -179,6 +179,11 @@
     public void onTrustManagedChanged(int userId) { }
 
     /**
+     * Called when the user has proved to a trust agent that they want to use the device.
+     */
+    public void onTrustInitiatedByUser(int userId) { }
+
+    /**
      * Called when a fingerprint is recognized.
      * @param userId
      */
diff --git a/packages/SystemUI/res/color/qs_user_detail_name.xml b/packages/SystemUI/res/color/qs_user_detail_name.xml
index 8ddb9be..57f7e65 100644
--- a/packages/SystemUI/res/color/qs_user_detail_name.xml
+++ b/packages/SystemUI/res/color/qs_user_detail_name.xml
@@ -17,6 +17,6 @@
   -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_activated="true" android:color="#ffffffff" />
+    <item android:state_activated="true" android:color="@color/current_user_border_color" />
     <item android:color="#66ffffff" /> <!-- 40% white -->
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_account_circle_qs.xml b/packages/SystemUI/res/drawable/ic_account_circle_qs.xml
index 0d5cd2e..d10a96d 100644
--- a/packages/SystemUI/res/drawable/ic_account_circle_qs.xml
+++ b/packages/SystemUI/res/drawable/ic_account_circle_qs.xml
@@ -16,7 +16,19 @@
   ~ limitations under the License
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:state_activated="true" android:drawable="@drawable/ic_account_circle" />
-    <item android:drawable="@drawable/ic_account_circle_qs_muted" />
-</selector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        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:fillColor="@color/qs_user_detail_icon_muted"
+                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/ic_account_circle_qs_muted.xml b/packages/SystemUI/res/drawable/ic_account_circle_qs_muted.xml
deleted file mode 100644
index 7b8b89f..0000000
--- a/packages/SystemUI/res/drawable/ic_account_circle_qs_muted.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        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:fillColor="@color/qs_user_detail_icon_muted"
-        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/ic_add_circle_qs.xml b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml
new file mode 100644
index 0000000..f296076
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml
@@ -0,0 +1,30 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        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:fillColor="@color/qs_user_detail_icon_muted"
+            android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM17.000000,13.000000l-4.000000,0.000000l0.000000,4.000000l-2.000000,0.000000l0.000000,-4.000000L7.000000,13.000000l0.000000,-2.000000l4.000000,0.000000L11.000000,7.000000l2.000000,0.000000l0.000000,4.000000l4.000000,0.000000L17.000000,13.000000z"/>
+    </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_settings_24dp.xml
rename to packages/SystemUI/res/drawable/ic_settings.xml
diff --git a/packages/SystemUI/res/layout/data_usage.xml b/packages/SystemUI/res/layout/data_usage.xml
index 63d22b2..8831a05 100644
--- a/packages/SystemUI/res/layout/data_usage.xml
+++ b/packages/SystemUI/res/layout/data_usage.xml
@@ -17,6 +17,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:paddingTop="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
     android:orientation="vertical" >
 
     <TextView
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
index 5648065..7c918c2 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -14,7 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.statusbar.AlphaOptimizedLinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/keyguard_user_switcher"
         android:orientation="vertical"
         android:layout_height="wrap_content"
@@ -22,4 +23,4 @@
         android:gravity="end"
         android:visibility="gone"
         android:paddingTop="4dp">
-</LinearLayout>
+</com.android.systemui.statusbar.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index d17390e..2e67376 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -16,7 +16,9 @@
   ~ limitations under the License
   -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!-- LinearLayout -->
+<com.android.systemui.qs.tiles.UserDetailItemView
+        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"
@@ -24,17 +26,21 @@
         android:layout_marginEnd="8dp"
         android:gravity="center_vertical"
         android:clickable="true"
-        android:background="@drawable/ripple_drawable">
-    <TextView android:id="@+id/name"
+        android:background="@drawable/ripple_drawable"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        sysui:activatedFontFamily="sans-serif-medium">
+    <TextView android:id="@+id/user_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"
+    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
             android:layout_width="@dimen/max_avatar_size"
             android:layout_height="@dimen/max_avatar_size"
             android:contentDescription="@null"
             sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
+            sysui:framePadding="6dp"
             sysui:activeFrameColor="@color/current_user_border_color" />
-</LinearLayout>
\ No newline at end of file
+</com.android.systemui.qs.tiles.UserDetailItemView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 0e78d66..d65a23e 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -77,30 +77,14 @@
                     />
         </LinearLayout>
 
-        <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:padding="8dp"
+        <ImageButton style="@android:style/Widget.Material.Light.Button.Borderless.Small"
+                android:id="@+id/notification_inspect_item"
+                android:layout_width="52dp"
+                android:layout_height="match_parent"
                 android:layout_weight="0"
-                android:orientation="horizontal"
-                android:showDividers="beginning|middle"
-                android:divider="@*android:drawable/list_divider_holo_dark"
-                android:dividerPadding="8dp"
-                >
-            <Button style="@android:style/Widget.Material.Light.Button.Borderless.Small"
-                    android:id="@+id/notification_inspect_item"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:gravity="start|center_vertical"
-                    android:drawablePadding="8dp"
-                    android:paddingStart="8dp"
-                    android:textColor="@color/notification_guts_btn_color"
-                    android:textSize="14dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:text="@string/status_bar_notification_inspect_item_title"
-                    />
-        </LinearLayout>
+                android:gravity="center"
+                android:contentDescription="@string/status_bar_notification_inspect_item_title"
+                android:src="@drawable/ic_settings"
+                />
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index c6a7368..5869bf3 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -14,39 +14,43 @@
      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="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/qs_detail_background"
-    android:padding="16dp" >
-
-    <TextView
-        android:id="@android:id/button1"
-        style="@style/QSBorderlessButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="88dp"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:text="@string/quick_settings_done"
-        android:textAppearance="@style/TextAppearance.QS.DetailButton" />
-
-    <TextView
-        android:id="@android:id/button2"
-        style="@style/QSBorderlessButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_marginEnd="8dp"
-        android:minWidth="132dp"
-        android:layout_toStartOf="@android:id/button1"
-        android:text="@string/quick_settings_more_settings"
-        android:textAppearance="@style/TextAppearance.QS.DetailButton" />
-
-    <FrameLayout
-        android:id="@android:id/content"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_above="@android:id/button1" />
+        android:background="@drawable/qs_detail_background"
+        android:paddingBottom="16dp"
+        android:orientation="vertical">
 
-</RelativeLayout>
\ No newline at end of file
+    <FrameLayout
+            android:id="@android:id/content"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingEnd="16dp"
+            android:gravity="end">
+
+        <TextView
+                android:id="@android:id/button2"
+                style="@style/QSBorderlessButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:minWidth="132dp"
+                android:text="@string/quick_settings_more_settings"
+                android:textAppearance="@style/TextAppearance.QS.DetailButton" />
+
+        <TextView
+                android:id="@android:id/button1"
+                style="@style/QSBorderlessButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minWidth="88dp"
+                android:text="@string/quick_settings_done"
+                android:textAppearance="@style/TextAppearance.QS.DetailButton" />
+
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
index b64005f..f61a43c 100644
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -17,7 +17,10 @@
 <!-- extends FrameLayout -->
 <com.android.systemui.qs.QSDetailItems xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent"
+    android:paddingTop="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp">
 
     <LinearLayout
         android:id="@android:id/list"
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
index 1d6df61..91d3a53 100644
--- a/packages/SystemUI/res/layout/qs_user_detail.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -16,14 +16,12 @@
   ~ limitations under the License
   -->
 
-<!-- GridView -->
+<!-- PseudoGridView -->
 <com.android.systemui.qs.tiles.UserDetailView
         xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:verticalSpacing="4dp"
-        android:horizontalSpacing="4dp"
-        android:numColumns="3"
-        android:listSelector="@drawable/ripple_drawable">
-
-</com.android.systemui.qs.tiles.UserDetailView>
\ No newline at end of file
+        sysui:verticalSpacing="4dp"
+        sysui:horizontalSpacing="4dp"
+        style="@style/UserDetailView" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 403860c..2322f16 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -25,14 +25,19 @@
         android:orientation="vertical"
         android:gravity="top|center_horizontal"
         android:paddingTop="16dp"
-        android:paddingBottom="20dp">
+        android:minHeight="112dp"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:background="@drawable/ripple_drawable"
+        systemui:activatedFontFamily="sans-serif-medium">
 
     <com.android.systemui.statusbar.phone.UserAvatarView
             android:id="@+id/user_picture"
             android:layout_width="@dimen/max_avatar_size"
             android:layout_height="@dimen/max_avatar_size"
-            android:layout_marginBottom="12dp"
+            android:layout_marginBottom="10dp"
             systemui:frameWidth="2dp"
+            systemui:framePadding="6dp"
             systemui:activeFrameColor="@color/current_user_border_color"/>
 
     <TextView
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index f8dfd65..4a5fffe 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -18,11 +18,11 @@
     android:layout_width="match_parent" 
     android:layout_height="match_parent"
     android:focusable="true">
-    <com.android.systemui.recents.views.TaskThumbnailView
+    <com.android.systemui.recents.views.TaskViewThumbnail
         android:id="@+id/task_view_thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
-    <com.android.systemui.recents.views.TaskBarView
+    <com.android.systemui.recents.views.TaskViewHeader
         android:id="@+id/task_view_bar"
         android:layout_width="match_parent"
         android:layout_height="@dimen/recents_task_bar_height"
@@ -61,7 +61,7 @@
             android:background="@drawable/recents_button_bg"
             android:visibility="invisible"
             android:src="@drawable/recents_dismiss_light" />
-    </com.android.systemui.recents.views.TaskBarView>
+    </com.android.systemui.recents.views.TaskViewHeader>
     <FrameLayout
         android:id="@+id/lock_to_app_fab"
         android:layout_width="48dp"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index fc2dc02..4eab9c7 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -50,12 +50,8 @@
         android:visibility="gone"
         />
 
-    <include
-        layout="@layout/keyguard_status_bar"
-        android:visibility="invisible" />
-
     <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
-        android:layout_width="@dimen/notification_panel_width"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="@integer/notification_panel_layout_gravity"
         android:id="@+id/notification_container_parent"
@@ -64,8 +60,9 @@
 
         <com.android.systemui.statusbar.phone.ObservableScrollView
             android:id="@+id/scroll_view"
-            android:layout_width="match_parent"
+            android:layout_width="@dimen/notification_panel_width"
             android:layout_height="match_parent"
+            android:layout_gravity="@integer/notification_panel_layout_gravity"
             android:scrollbars="none"
             android:overScrollMode="never"
             android:fillViewport="true">
@@ -97,8 +94,9 @@
 
         <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
-            android:layout_width="match_parent"
+            android:layout_width="@dimen/notification_panel_width"
             android:layout_height="match_parent"
+            android:layout_gravity="@integer/notification_panel_layout_gravity"
             android:layout_marginBottom="@dimen/close_handle_underlap"/>
 
         <ViewStub
@@ -109,6 +107,10 @@
             android:layout_gravity="end"
             android:layout="@layout/keyguard_user_switcher" />
 
+        <include
+            layout="@layout/keyguard_status_bar"
+            android:visibility="invisible" />
+
     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
 
     <include layout="@layout/status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 688a88c..21d8457 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -50,7 +50,7 @@
         android:layout_width="48dp"
         android:layout_height="@dimen/status_bar_header_height"
         android:background="@drawable/ripple_drawable"
-        android:src="@drawable/ic_settings_24dp"
+        android:src="@drawable/ic_settings"
         android:contentDescription="@string/accessibility_desc_quick_settings"/>
 
     <LinearLayout android:id="@+id/system_icons_super_container"
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 2f73c63..9d5f7bd 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -78,7 +78,7 @@
             android:clickable="true"
             android:contentDescription="@null"
             android:scaleType="center"
-            android:src="@drawable/ic_settings_24dp" />
+            android:src="@drawable/ic_settings" />
     </FrameLayout>
 
     <LinearLayout
@@ -98,4 +98,4 @@
         android:paddingTop="4dp"
         android:paddingBottom="4dp"
         android:textAppearance="@style/TextAppearance.QS.Subhead" />
-</com.android.systemui.volume.ZenModePanel>
\ No newline at end of file
+</com.android.systemui.volume.ZenModePanel>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 2d82d6e..7cdc078 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -72,9 +72,6 @@
     <!-- Margin on the left side of the carrier text on Keyguard -->
     <dimen name="keyguard_carrier_text_margin">24dp</dimen>
 
-    <!-- end margin for system icons if multi user switch is hidden -->
-    <dimen name="system_icons_switcher_hidden_expanded_margin">20dp</dimen>
-
     <!-- The width/height of the phone/camera/unlock icon on keyguard. -->
     <dimen name="keyguard_affordance_height">80dp</dimen>
     <dimen name="keyguard_affordance_width">120dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 5daf08e..9e5b1d6 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -34,4 +34,8 @@
         <item name="android:layout_height">@dimen/search_panel_scrim_height</item>
         <item name="android:layout_gravity">bottom</item>
     </style>
+
+    <style name="UserDetailView">
+        <item name="numColumns">4</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index d8d170d..6ecdca3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -56,11 +56,21 @@
     </attr>
     <declare-styleable name="UserAvatarView">
         <attr name="frameWidth" format="dimension" />
+        <attr name="framePadding" format="dimension" />
         <attr name="activeFrameColor" format="color" />
         <attr name="frameColor" />
     </declare-styleable>
+    <declare-styleable name="UserDetailItemView">
+        <attr name="regularFontFamily" format="string" />
+        <attr name="activatedFontFamily" format="string" />
+    </declare-styleable>
     <declare-styleable name="DateView">
         <attr name="datePattern" format="string" />
     </declare-styleable>
+    <declare-styleable name="PseudoGridView">
+        <attr name="numColumns" format="integer" />
+        <attr name="verticalSpacing" format="dimension" />
+        <attr name="horizontalSpacing" format="dimension" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1cdcc2b..c3ea8f8 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -103,4 +103,6 @@
     <color name="notification_guts_btn_color">#FFFFFFFF</color>
 
     <color name="search_panel_card_color">#ffffff</color>
+
+    <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11a8063..4a4fa41 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -237,6 +237,9 @@
          in dps over one second of time. -->
     <dimen name="recents_animation_movement_in_dps_per_second">800dp</dimen>
 
+    <!-- The min alpha to apply to a task affiliation group color. -->
+    <item name="recents_task_affiliation_color_min_alpha_percentage" format="float" type="dimen">0.6</item>
+
     <!-- Space reserved for the cards behind the top card in the top stack -->
     <dimen name="top_stack_peek_amount">12dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 83a9a81..51633dc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -454,8 +454,8 @@
     <string name="accessibility_clear_all">Clear all notifications.</string>
 
     <!-- Title shown in notification popup for inspecting the responsible
-         application -->
-    <string name="status_bar_notification_inspect_item_title">App info</string>
+         application [CHAR LIMIT=30] -->
+    <string name="status_bar_notification_inspect_item_title">Settings</string>
 
     <!-- Description of the button in the phone-style notification panel that controls auto-rotation, when auto-rotation is on. [CHAR LIMIT=NONE] -->
     <string name="accessibility_rotation_lock_off">Screen will rotate automatically.</string>
@@ -672,6 +672,13 @@
     <string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
 
     <!-- Related to user switcher --><skip/>
+
+    <!-- Label for the adding a new user in the user switcher [CHAR LIMIT=35] -->
+    <string name="user_add_user">Add user</string>
+
+    <!-- Name for a freshly added user [CHAR LIMIT=30] -->
+    <string name="user_new_user_name">New user</string>
+
     <!-- Name for the guest user [CHAR LIMIT=35] -->
     <string name="guest_nickname">Guest</string>
 
@@ -761,13 +768,13 @@
     <string name="disconnect_vpn">Disconnect VPN</string>
 
     <!-- Monitoring dialog device owner body text [CHAR LIMIT=300] -->
-    <string name="monitoring_description_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
+    <string name="monitoring_description_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
 
     <!-- Monitoring dialog non-legacy VPN text [CHAR LIMIT=300] -->
-    <string name="monitoring_description_vpn">You gave \"<xliff:g id="application">%1$s</xliff:g>\" permission to set up a VPN connection.\n\nThis app can monitor your network activity, including emails, apps and secure websites.</string>
+    <string name="monitoring_description_vpn">You gave \"<xliff:g id="application">%1$s</xliff:g>\" permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and secure websites.</string>
 
     <!-- Monitoring dialog legacy VPN text [CHAR LIMIT=300] -->
-    <string name="monitoring_description_legacy_vpn">You\'re connected to a VPN (\"<xliff:g id="application">%1$s</xliff:g>\").\n\nYour VPN service provider can monitor your network activity including emails, apps, and secure websites.</string>
+    <string name="monitoring_description_legacy_vpn">You\'re connected to a VPN (\"<xliff:g id="application">%1$s</xliff:g>\").\n\nYour VPN service provider can monitor your device and network activity including emails, apps, and secure websites.</string>
 
     <!-- Monitoring dialog non-legacy VPN with device owner text [CHAR LIMIT=300] -->
     <string name="monitoring_description_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%2$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fc66730..baaa379 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -103,7 +103,7 @@
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
         <item name="android:textSize">16sp</item>
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">#ffffff</item>
+        <item name="android:textColor">@color/qs_user_detail_name</item>
     </style>
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.UserName" />
 
@@ -289,4 +289,8 @@
         <item name="android:layout_height">@dimen/search_panel_scrim_height</item>
         <item name="android:layout_gravity">bottom</item>
     </style>
+
+    <style name="UserDetailView">
+        <item name="numColumns">3</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
new file mode 100644
index 0000000..cb6708e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -0,0 +1,213 @@
+/*
+ * 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.qs;
+
+import com.android.systemui.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A view that arranges it's children in a grid with a fixed number of evenly spaced columns.
+ *
+ * {@see android.widget.GridView}
+ */
+public class PseudoGridView extends ViewGroup {
+
+    private int mNumColumns = 3;
+    private int mVerticalSpacing;
+    private int mHorizontalSpacing;
+
+    public PseudoGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PseudoGridView);
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.PseudoGridView_numColumns:
+                    mNumColumns = a.getInt(attr, 3);
+                    break;
+                case R.styleable.PseudoGridView_verticalSpacing:
+                    mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
+                    break;
+                case R.styleable.PseudoGridView_horizontalSpacing:
+                    mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
+                    break;
+            }
+        }
+
+        a.recycle();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
+            throw new UnsupportedOperationException("Needs a maximum width");
+        }
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+
+        int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+        int childHeightSpec = MeasureSpec.UNSPECIFIED;
+        int totalHeight = 0;
+        int children = getChildCount();
+        int rows = (children + mNumColumns - 1) / mNumColumns;
+        for (int row = 0; row < rows; row++) {
+            int startOfRow = row * mNumColumns;
+            int endOfRow = Math.min(startOfRow + mNumColumns, children);
+            int maxHeight = 0;
+            for (int i = startOfRow; i < endOfRow; i++) {
+                View child = getChildAt(i);
+                child.measure(childWidthSpec, childHeightSpec);
+                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+            }
+            int maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
+            for (int i = startOfRow; i < endOfRow; i++) {
+                View child = getChildAt(i);
+                if (child.getMeasuredHeight() != maxHeight) {
+                    child.measure(childWidthSpec, maxHeightSpec);
+                }
+            }
+            totalHeight += maxHeight;
+            if (row > 0) {
+                totalHeight += mVerticalSpacing;
+            }
+        }
+
+        setMeasuredDimension(width, getDefaultSize(totalHeight, heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        boolean isRtl = isLayoutRtl();
+        int children = getChildCount();
+        int rows = (children + mNumColumns - 1) / mNumColumns;
+        int y = 0;
+        for (int row = 0; row < rows; row++) {
+            int x = isRtl ? getWidth() : 0;
+            int maxHeight = 0;
+            int startOfRow = row * mNumColumns;
+            int endOfRow = Math.min(startOfRow + mNumColumns, children);
+            for (int i = startOfRow; i < endOfRow; i++) {
+                View child = getChildAt(i);
+                int width = child.getMeasuredWidth();
+                int height = child.getMeasuredHeight();
+                if (isRtl) {
+                    x -= width;
+                }
+                child.layout(x, y, x + width, y + height);
+                maxHeight = Math.max(maxHeight, height);
+                if (isRtl) {
+                    x -= mHorizontalSpacing;
+                } else {
+                    x += width + mHorizontalSpacing;
+                }
+            }
+            y += maxHeight;
+            if (row > 0) {
+                y += mVerticalSpacing;
+            }
+        }
+    }
+
+    /**
+     * Bridges between a ViewGroup and a BaseAdapter.
+     * <p>
+     * Usage: {@code ViewGroupAdapterBridge.link(viewGroup, adapter)}
+     * <br />
+     * After this call, the ViewGroup's children will be provided by the adapter.
+     */
+    public static class ViewGroupAdapterBridge extends DataSetObserver {
+
+        private final WeakReference<ViewGroup> mViewGroup;
+        private final BaseAdapter mAdapter;
+        private boolean mReleased;
+
+        public static void link(ViewGroup viewGroup, BaseAdapter adapter) {
+            new ViewGroupAdapterBridge(viewGroup, adapter);
+        }
+
+        private ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter) {
+            mViewGroup = new WeakReference<>(viewGroup);
+            mAdapter = adapter;
+            mReleased = false;
+            mAdapter.registerDataSetObserver(this);
+            refresh();
+        }
+
+        private void refresh() {
+            if (mReleased) {
+                return;
+            }
+            ViewGroup viewGroup = mViewGroup.get();
+            if (viewGroup == null) {
+                release();
+                return;
+            }
+            final int childCount = viewGroup.getChildCount();
+            final int adapterCount = mAdapter.getCount();
+            final int N = Math.max(childCount, adapterCount);
+            for (int i = 0; i < N; i++) {
+                if (i < adapterCount) {
+                    View oldView = null;
+                    if (i < childCount) {
+                        oldView = viewGroup.getChildAt(i);
+                    }
+                    View newView = mAdapter.getView(i, oldView, viewGroup);
+                    if (oldView == null) {
+                        // We ran out of existing views. Add it at the end.
+                        viewGroup.addView(newView);
+                    } else if (oldView != newView) {
+                        // We couldn't rebind the view. Replace it.
+                        viewGroup.removeViewAt(i);
+                        viewGroup.addView(newView, i);
+                    }
+                } else {
+                    int lastIndex = viewGroup.getChildCount() - 1;
+                    viewGroup.removeViewAt(lastIndex);
+                }
+            }
+        }
+
+        @Override
+        public void onChanged() {
+            refresh();
+        }
+
+        @Override
+        public void onInvalidated() {
+            release();
+        }
+
+        private void release() {
+            if (!mReleased) {
+                mReleased = true;
+                mAdapter.unregisterDataSetObserver(this);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 59f3b3d..3679b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -326,8 +326,11 @@
         if (mFooter.hasFooter()) {
             h += mFooter.getView().getHeight();
         }
-        mDetail.measure(exactly(width), exactly(h));
-        setMeasuredDimension(width, h);
+        mDetail.measure(exactly(width), MeasureSpec.UNSPECIFIED);
+        if (mDetail.getMeasuredHeight() < h) {
+            mDetail.measure(exactly(width), exactly(h));
+        }
+        setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight()));
     }
 
     private static int exactly(int size) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index d765aab..a56b7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -16,11 +16,14 @@
 
 package com.android.systemui.qs.tiles;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.UserAvatarView;
 
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -36,6 +39,8 @@
 
     private UserAvatarView mAvatar;
     private TextView mName;
+    private Typeface mRegularTypeface;
+    private Typeface mActivatedTypeface;
 
     public UserDetailItemView(Context context) {
         this(context, null);
@@ -52,6 +57,21 @@
     public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.UserDetailItemView, defStyleAttr, defStyleRes);
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.UserDetailItemView_regularFontFamily:
+                    mRegularTypeface = Typeface.create(a.getString(attr), 0 /* style */);
+                    break;
+                case R.styleable.UserDetailItemView_activatedFontFamily:
+                    mActivatedTypeface = Typeface.create(a.getString(attr), 0 /* style */);
+                    break;
+            }
+        }
+        a.recycle();
     }
 
     public static UserDetailItemView convertOrInflate(Context context, View convertView,
@@ -77,6 +97,28 @@
     protected void onFinishInflate() {
         mAvatar = (UserAvatarView) findViewById(R.id.user_picture);
         mName = (TextView) findViewById(R.id.user_name);
+        if (mRegularTypeface == null) {
+            mRegularTypeface = mName.getTypeface();
+        }
+        if (mActivatedTypeface == null) {
+            mActivatedTypeface = mName.getTypeface();
+        }
+        updateTypeface();
     }
 
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        updateTypeface();
+    }
+
+    private void updateTypeface() {
+        boolean activated = ArrayUtils.contains(getDrawableState(), android.R.attr.state_activated);
+        mName.setTypeface(activated ? mActivatedTypeface : mRegularTypeface);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 67eef56..c524edc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -17,45 +17,24 @@
 package com.android.systemui.qs.tiles;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.PseudoGridView;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 import android.content.Context;
-import android.content.Intent;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.GridView;
 
 /**
  * Quick settings detail view for user switching.
  */
-public class UserDetailView extends GridView {
+public class UserDetailView extends PseudoGridView {
 
-    public UserDetailView(Context context) {
-        this(context, null);
-    }
+    private Adapter mAdapter;
 
     public UserDetailView(Context context, AttributeSet attrs) {
-        this(context, attrs, android.R.attr.gridViewStyle);
-    }
-
-    public UserDetailView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public UserDetailView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-
-        setOnItemClickListener(new OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                UserSwitcherController.UserRecord tag =
-                        (UserSwitcherController.UserRecord) view.getTag();
-                ((Adapter)getAdapter()).switchTo(tag);
-            }
-        });
+        super(context, attrs);
     }
 
     public static UserDetailView inflate(Context context, ViewGroup parent, boolean attach) {
@@ -64,10 +43,12 @@
     }
 
     public void createAndSetAdapter(UserSwitcherController controller) {
-        setAdapter(new Adapter(mContext, controller));
+        mAdapter = new Adapter(mContext, controller);
+        ViewGroupAdapterBridge.link(this, mAdapter);
     }
 
-    public static class Adapter extends UserSwitcherController.BaseUserAdapter {
+    public static class Adapter extends UserSwitcherController.BaseUserAdapter
+            implements OnClickListener {
 
         private Context mContext;
 
@@ -81,9 +62,12 @@
             UserSwitcherController.UserRecord item = getItem(position);
             UserDetailItemView v = UserDetailItemView.convertOrInflate(
                     mContext, convertView, parent);
+            if (v != convertView) {
+                v.setOnClickListener(this);
+            }
             String name = getName(mContext, item);
             if (item.picture == null) {
-                v.bind(name, mContext.getDrawable(R.drawable.ic_account_circle_qs));
+                v.bind(name, getDrawable(mContext, item));
             } else {
                 v.bind(name, item.picture);
             }
@@ -91,5 +75,12 @@
             v.setTag(item);
             return v;
         }
+
+        @Override
+        public void onClick(View view) {
+            UserSwitcherController.UserRecord tag =
+                    (UserSwitcherController.UserRecord) view.getTag();
+            switchTo(tag);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 98bdee0..2a782cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -738,12 +738,12 @@
         }
     }
 
-    private void startApplicationDetailsActivity(String packageName) {
+    private void startApplicationDetailsActivity(String packageName, int userId) {
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                 Uri.fromParts("package", packageName, null));
         intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
         TaskStackBuilder.create(getContext())
-                .addNextIntentWithParentStack(intent).startActivities();
+                .addNextIntentWithParentStack(intent).startActivities(null, new UserHandle(userId));
     }
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -769,7 +769,7 @@
                     ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
                     if (viewHolder != null) {
                         final TaskDescription ad = viewHolder.taskDescription;
-                        startApplicationDetailsActivity(ad.packageName);
+                        startApplicationDetailsActivity(ad.packageName, ad.userId);
                         show(false);
                     } else {
                         throw new IllegalStateException("Oops, no tag on view " + selectedView);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index a55c0f2..bb5fe54 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -328,7 +328,7 @@
         TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
         Rect taskStackBounds = new Rect(mTaskStackBounds);
         taskStackBounds.bottom -= mSystemInsets.bottom;
-        tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
+        tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds, mTriggeredFromAltTab);
         tsv.getScroller().setStackScrollToInitialState();
 
         // Find the running task in the TaskStack
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index cf0a1dc..980a4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -86,6 +86,7 @@
     public int taskBarViewLightTextColor;
     public int taskBarViewDarkTextColor;
     public int taskBarViewHighlightColor;
+    public float taskBarViewAffiliationColorMinAlpha;
 
     /** Task bar size & animations */
     public int taskBarHeight;
@@ -221,6 +222,9 @@
                 res.getColor(R.color.recents_task_bar_dark_text_color);
         taskBarViewHighlightColor =
                 res.getColor(R.color.recents_task_bar_highlight_color);
+        TypedValue affMinAlphaPctValue = new TypedValue();
+        res.getValue(R.dimen.recents_task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);
+        taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();
 
         // Task bar size & animations
         taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index f30e22a..8716184 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -339,8 +339,9 @@
 
             // Create a new task
             Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, t.affiliatedTaskId,
-                    activityLabel, activityIcon, activityColor, t.userId, t.firstActiveTime,
-                    t.lastActiveTime, (i == (taskCount - 1)), config.lockToAppEnabled);
+                    t.affiliatedTaskColor, activityLabel, activityIcon, activityColor, t.userId,
+                    t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1)),
+                    config.lockToAppEnabled);
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
@@ -381,7 +382,7 @@
         }
 
         // Simulate the groupings that we describe
-        stack.createAffiliatedGroupings();
+        stack.createAffiliatedGroupings(config);
 
         // Start the task loader and add all the tasks we need to load
         mLoader.start(context);
@@ -405,11 +406,11 @@
             ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
             if (info == null) continue;
 
-            stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
-                    null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1)),
-                    config.lockToAppEnabled));
+            stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId,
+                    t.affiliatedTaskColor, null, null, 0, 0, t.firstActiveTime, t.lastActiveTime,
+                    (i == (taskCount - 1)), config.lockToAppEnabled));
         }
-        stack.createAffiliatedGroupings();
+        stack.createAffiliatedGroupings(config);
         return stack;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 41874fc..f6c3a7e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -77,6 +77,7 @@
     public TaskKey key;
     public TaskGrouping group;
     public int taskAffiliation;
+    public int taskAffiliationColor;
     public boolean isLaunchTarget;
     public Drawable applicationIcon;
     public Drawable activityIcon;
@@ -95,16 +96,19 @@
         // Only used by RecentsService for task rect calculations.
     }
 
-    public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
-                Drawable activityIcon, int colorPrimary, int userId,
+    public Task(int id, boolean isActive, Intent intent, int taskAffiliation, int taskAffiliationColor,
+                String activityTitle, Drawable activityIcon, int colorPrimary, int userId,
                 long firstActiveTime, long lastActiveTime, boolean lockToThisTask,
                 boolean lockToTaskEnabled) {
+        boolean isInAffiliationGroup = (taskAffiliation != id);
+        boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0);
         this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
         this.taskAffiliation = taskAffiliation;
+        this.taskAffiliationColor = taskAffiliationColor;
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
-        this.colorPrimary = colorPrimary;
-        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(colorPrimary,
+        this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary;
+        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
                 Color.WHITE) > 3f;
         this.isActive = isActive;
         this.lockToThisTask = lockToTaskEnabled && lockToThisTask;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index fd6303f..435eb42 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.recents.model;
 
+import android.graphics.Color;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.NamedCounter;
 
 import java.util.ArrayList;
@@ -320,7 +322,7 @@
     /**
      * Temporary: This method will simulate affiliation groups by
      */
-    public void createAffiliatedGroupings() {
+    public void createAffiliatedGroupings(RecentsConfiguration config) {
         if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
             HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
             // Sort all tasks by increasing firstActiveTime of the task
@@ -387,6 +389,7 @@
             mTaskList.set(tasks);
         } else {
             // Create the task groups
+            HashMap<Task.TaskKey, Task> tasksMap = new HashMap<Task.TaskKey, Task>();
             ArrayList<Task> tasks = mTaskList.getTasks();
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
@@ -401,6 +404,28 @@
                     addGroup(group);
                 }
                 group.addTask(t);
+                tasksMap.put(t.key, t);
+            }
+            // Update the task colors for each of the groups
+            float minAlpha = config.taskBarViewAffiliationColorMinAlpha;
+            int taskGroupCount = mGroups.size();
+            for (int i = 0; i < taskGroupCount; i++) {
+                TaskGrouping group = mGroups.get(i);
+                taskCount = group.getTaskCount();
+                // Ignore the groups that only have one task
+                if (taskCount <= 1) continue;
+                // Calculate the group color distribution
+                int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).taskAffiliationColor;
+                float alphaStep = (1f - minAlpha) / taskCount;
+                float alpha = 1f;
+                for (int j = 0; j < taskCount; j++) {
+                    Task t = tasksMap.get(group.mTaskKeys.get(j));
+                    t.colorPrimary = Color.rgb(
+                            (int) (alpha * Color.red(affiliationColor) + (1f - alpha) * 0xFF),
+                            (int) (alpha * Color.green(affiliationColor) + (1f - alpha) * 0xFF),
+                            (int) (alpha * Color.blue(affiliationColor) + (1f - alpha) * 0xFF));
+                    alpha -= alphaStep;
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 1a32b81..34e8860 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -26,6 +26,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.net.Uri;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -385,7 +386,7 @@
             sourceView = stackView;
             transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
             offsetX = transform.rect.left;
-            offsetY = Math.min(transform.rect.top, mConfig.displayRect.height());
+            offsetY = mConfig.displayRect.height();
         } else {
             transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
         }
@@ -474,7 +475,8 @@
                 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
         intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
         TaskStackBuilder.create(getContext())
-                .addNextIntentWithParentStack(intent).startActivities();
+                .addNextIntentWithParentStack(intent).startActivities(null,
+                new UserHandle(t.userId));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 57f1274..46996bb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -330,7 +330,7 @@
                         // We can reuse the current task transforms to find the task rects
                         TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.indexOfTask(tv.getTask()));
                         TaskViewTransform nextTransform = mCurrentTaskTransforms.get(mStack.indexOfTask(nextTv.getTask()));
-                        clipBottom = transform.rect.bottom - nextTransform.rect.top - 200;
+                        clipBottom = transform.rect.bottom - nextTransform.rect.top;
                     }
                 }
                 tv.getViewBounds().setClipBottom(clipBottom);
@@ -349,9 +349,9 @@
     }
 
     /** Updates the min and max virtual scroll bounds */
-    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
+    void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab) {
         // Compute the min and max scroll values
-        mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks());
+        mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab);
 
         // Debug logging
         if (boundScrollToNewMinMax) {
@@ -388,17 +388,17 @@
                 };
             }
 
+            // Scroll the view into position (just center it in the curve)
             if (scrollToNewPosition) {
-                // Scroll the view into position
-                // XXX: We probably want this to be centered in view instead of p = 0f
-                float newScroll = mStackScroller.getBoundedStackScroll(
-                        mLayoutAlgorithm.getStackScrollForTaskIndex(t));
+                float newScroll = mLayoutAlgorithm.getStackScrollForTaskIndex(t) - 0.5f;
+                newScroll = mStackScroller.getBoundedStackScroll(newScroll);
                 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
             } else {
                 if (postScrollRunnable != null) {
                     postScrollRunnable.run();
                 }
             }
+
         }
     }
 
@@ -430,18 +430,18 @@
     public void computeScroll() {
         mStackScroller.computeScroll();
         // Synchronize the views
-        if (synchronizeStackViewsWithModel()) {
-            clipTaskViews();
-        }
+        synchronizeStackViewsWithModel();
+        clipTaskViews();
     }
 
     /** Computes the stack and task rects */
-    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
+    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
+                             boolean launchedWithAltTab) {
         // Compute the rects in the stack algorithm
         mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
 
         // Update the scroll bounds
-        updateMinMaxScroll(false);
+        updateMinMaxScroll(false, launchedWithAltTab);
     }
 
     /**
@@ -456,7 +456,7 @@
         // Compute our stack/task rects
         Rect taskStackBounds = new Rect(mTaskStackBounds);
         taskStackBounds.bottom -= mConfig.systemInsets.bottom;
-        computeRects(width, height, taskStackBounds);
+        computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab);
 
         // If this is the first layout, then scroll to the front of the stack and synchronize the
         // stack views immediately to load all the views
@@ -544,9 +544,8 @@
             mStartEnterAnimationContext = null;
         }
 
-        // Update the focused task index to be the next item to the top task
+        // When Alt-Tabbing, we scroll to and focus the previous task
         if (mConfig.launchedWithAltTab) {
-            // When alt-tabbing, we focus the next previous task
             focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
         }
     }
@@ -662,7 +661,7 @@
         mCb.onTaskViewDismissed(removedTask);
 
         // Update the min/max scroll and animate other task views into their new positions
-        updateMinMaxScroll(true);
+        updateMinMaxScroll(true, mConfig.launchedWithAltTab);
         requestSynchronizeStackViewsWithModel(200);
 
         // Update the new front most task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index b1482bb..633e6fa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -18,7 +18,6 @@
 
 import android.graphics.Rect;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
@@ -87,8 +86,9 @@
                 left + size, mStackRect.top + size);
     }
 
-    /** Computes the minimum and maximum scroll progress values */
-    void computeMinMaxScroll(ArrayList<Task> tasks) {
+    /** Computes the minimum and maximum scroll progress values.  This method may be called before
+     * the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
+    void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab) {
         // Clear the progress map
         mTaskProgressMap.clear();
 
@@ -130,7 +130,12 @@
 
         mMinScrollP = 0f;
         mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
-        mInitialScrollP = pAtSecondFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
+        if (launchedWithAltTab) {
+            // Center the second most task, since that will be focused first
+            mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
+        } else {
+            mInitialScrollP = pAtSecondFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
+        }
     }
 
     /** Update/get the transform */
@@ -151,6 +156,7 @@
         // If the task top is outside of the bounds below the screen, then immediately reset it
         if (pTaskRelative > 1f) {
             transformOut.reset();
+            transformOut.rect.set(mTaskRect);
             return transformOut;
         }
         // The check for the top is trickier, since we want to show the next task if it is at all
@@ -158,6 +164,7 @@
         if (pTaskRelative < 0f) {
             if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
                 transformOut.reset();
+                transformOut.rect.set(mTaskRect);
                 return transformOut;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index abf3c50..3b9bcc4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -39,7 +39,7 @@
 
 /* A task view */
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
-        TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
+        TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
@@ -67,9 +67,9 @@
     AnimateableViewBounds mViewBounds;
     Paint mLayerPaint = new Paint();
 
-    TaskThumbnailView mThumbnailView;
-    TaskBarView mBarView;
-    TaskFooterView mFooterView;
+    TaskViewThumbnail mThumbnailView;
+    TaskViewHeader mBarView;
+    TaskViewFooter mFooterView;
     View mActionButtonView;
     TaskViewCallbacks mCb;
 
@@ -124,8 +124,8 @@
     @Override
     protected void onFinishInflate() {
         // Bind the views
-        mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
-        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+        mBarView = (TaskViewHeader) findViewById(R.id.task_view_bar);
+        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
         mActionButtonView = findViewById(R.id.lock_to_app_fab);
         if (mFooterView != null) {
             mFooterView.setCallbacks(this);
@@ -712,7 +712,7 @@
         setOnClickListener(enabled ? this : null);
     }
 
-    /**** TaskFooterView.TaskFooterViewCallbacks ****/
+    /**** TaskViewFooter.TaskFooterViewCallbacks ****/
 
     @Override
     public void onTaskFooterHeightChanged(int height, int maxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
index 881bbcf..324169e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
@@ -24,7 +24,7 @@
 
 
 /** The task footer view */
-public class TaskFooterView extends FrameLayout {
+public class TaskViewFooter extends FrameLayout {
 
     interface TaskFooterViewCallbacks {
         public void onTaskFooterHeightChanged(int height, int maxHeight);
@@ -37,19 +37,19 @@
     int mMaxFooterHeight;
     ObjectAnimator mFooterAnimator;
 
-    public TaskFooterView(Context context) {
+    public TaskViewFooter(Context context) {
         this(context, null);
     }
 
-    public TaskFooterView(Context context, AttributeSet attrs) {
+    public TaskViewFooter(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TaskViewFooter(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskViewFooter(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
         mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 90bf12f..03fc16e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -28,7 +28,6 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -39,7 +38,7 @@
 
 
 /* The task bar view */
-class TaskBarView extends FrameLayout {
+class TaskViewHeader extends FrameLayout {
 
     RecentsConfiguration mConfig;
 
@@ -54,19 +53,19 @@
 
     static Paint sHighlightPaint;
 
-    public TaskBarView(Context context) {
+    public TaskViewHeader(Context context) {
         this(context, null);
     }
 
-    public TaskBarView(Context context, AttributeSet attrs) {
+    public TaskViewHeader(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 1116d51..f836aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -25,24 +25,24 @@
 
 
 /** The task thumbnail view */
-public class TaskThumbnailView extends FixedSizeImageView {
+public class TaskViewThumbnail extends FixedSizeImageView {
 
     // Task bar clipping
     Rect mClipRect = new Rect();
 
-    public TaskThumbnailView(Context context) {
+    public TaskViewThumbnail(Context context) {
         this(context, null);
     }
 
-    public TaskThumbnailView(Context context, AttributeSet attrs) {
+    public TaskViewThumbnail(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setScaleType(ScaleType.FIT_XY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index c559253..d4c8ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -429,6 +429,9 @@
         try {
             panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width);
         } catch (Resources.NotFoundException e) {
+        }
+        if (panelWidth <= 0) {
+            // includes notification_panel_width==match_parent (-1)
             panelWidth = mDisplayMetrics.widthPixels;
         }
         mPreviewWidth = panelWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 16e51c9..8319f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -21,6 +21,7 @@
 import android.animation.TimeInterpolator;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.TaskStackBuilder;
@@ -30,6 +31,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
@@ -585,31 +588,12 @@
                entry.expandedBig.findViewById(com.android.internal.R.id.media_action_area) != null;
     }
 
-    private void startApplicationDetailsActivity(String packageName) {
-        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                Uri.fromParts("package", packageName, null));
-        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
-        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
-                null, UserHandle.CURRENT);
-    }
-
-    private static final int max(int...args) {
-        switch (args.length) {
-            case 0:
-                return 0;
-            case 1:
-                return args[0];
-            case 2:
-                return args[1] > args[0] ? args[1] : args[0];
-            default:
-                int m = args[0];
-                for (int i = 0; i < args.length; i++) {
-                    if (args[i] > m) {
-                        m = args[i];
-                    }
-                }
-                return m;
-        }
+    private void startAppNotificationSettingsActivity(String packageName, int appUid) {
+        Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent)
+                .startActivities(null, new UserHandle(UserHandle.getUserId(appUid)));
     }
 
     protected SwipeHelper.LongPressListener getNotificationLongClicker() {
@@ -618,8 +602,6 @@
             public boolean onLongPress(View v, int x, int y) {
                 dismissPopups();
 
-                final String packageNameF = (String) v.getTag();
-                if (packageNameF == null) return false;
                 if (v.getWindowToken() == null) return false;
 
                 // Assume we are a status_bar_notification_row
@@ -629,14 +611,6 @@
                 // Already showing?
                 if (guts.getVisibility() == View.VISIBLE) return false;
 
-                final View button = guts.findViewById(R.id.notification_inspect_item);
-                button.setOnClickListener(new View.OnClickListener() {
-                    public void onClick(View v) {
-                        startApplicationDetailsActivity(packageNameF);
-                        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-                    }
-                });
-
                 guts.setVisibility(View.VISIBLE);
                 final double horz = Math.max(v.getWidth() - x, x);
                 final double vert = Math.max(v.getHeight() - y, y);
@@ -959,28 +933,10 @@
             return inflateViews(entry, parent, true);
     }
 
-    private Drawable loadPackageIconDrawable(String pkg, int userId) {
-        Drawable icon = null;
-        try {
-            icon = mContext.getPackageManager().getApplicationIcon(pkg);
-        } catch (PackageManager.NameNotFoundException e) {
-        }
-
-        return icon;
-    }
-
-    private CharSequence loadPackageName(String pkg) {
-        final PackageManager pm = mContext.getPackageManager();
-        try {
-            ApplicationInfo info = pm.getApplicationInfo(pkg,
-                    PackageManager.GET_UNINSTALLED_PACKAGES);
-            if (info != null) return pm.getApplicationLabel(info);
-        } catch (PackageManager.NameNotFoundException e) {
-        }
-        return pkg;
-    }
-
     private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
+        PackageManager pmUser = getPackageManagerForUser(
+                entry.notification.getUser().getIdentifier());
+
         int maxHeight = mRowMaxHeight;
         StatusBarNotification sbn = entry.notification;
         RemoteViews contentView = sbn.getNotification().contentView;
@@ -1031,12 +987,43 @@
         // the notification inspector (see SwipeHelper.setLongPressListener)
         row.setTag(sbn.getPackageName());
         final View guts = row.findViewById(R.id.notification_guts);
-        final Drawable pkgicon = loadPackageIconDrawable(entry.notification.getPackageName(),
-                entry.notification.getUserId());
-        final String pkgname = loadPackageName(entry.notification.getPackageName()).toString();
+        final String pkg = entry.notification.getPackageName();
+        String appname = pkg;
+        Drawable pkgicon = null;
+        int appUid = -1;
+        try {
+            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+                PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
+            if (info != null) {
+                appname = String.valueOf(pmUser.getApplicationLabel(info));
+                pkgicon = pmUser.getApplicationIcon(info);
+                appUid = info.uid;
+            }
+        } catch (NameNotFoundException e) {
+            // app is gone, just show package name and generic icon
+            pkgicon = pmUser.getDefaultActivityIcon();
+        }
         ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon);
         ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(entry.notification.getPostTime());
-        ((TextView) row.findViewById(R.id.pkgname)).setText(pkgname);
+        ((TextView) row.findViewById(R.id.pkgname)).setText(appname);
+        final View settingsButton = guts.findViewById(R.id.notification_inspect_item);
+        if (appUid >= 0) {
+            final int appUidF = appUid;
+            settingsButton.setOnClickListener(new View.OnClickListener() {
+                public void onClick(View v) {
+                    dismissKeyguardThenExecute(new OnDismissAction() {
+                        public boolean onDismiss() {
+                            startAppNotificationSettingsActivity(pkg, appUidF);
+                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                            visibilityChanged(false);
+                            return DELAY_DISMISS_TO_ACTIVITY_LAUNCH;
+                        }
+                    });
+                }
+            });
+        } else {
+            settingsButton.setVisibility(View.GONE);
+        }
 
         workAroundBadLayerDrawableOpacity(row);
         View vetoButton = updateNotificationVetoButton(row, sbn);
@@ -1108,9 +1095,6 @@
         }
 
         if (publicViewLocal == null) {
-            PackageManager pm = getPackageManagerForUser(
-                    entry.notification.getUser().getIdentifier());
-
             // Add a basic notification template
             publicViewLocal = LayoutInflater.from(mContext).inflate(
                     com.android.internal.R.layout.notification_template_material_base,
@@ -1118,8 +1102,8 @@
 
             final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title);
             try {
-                title.setText(pm.getApplicationLabel(
-                        pm.getApplicationInfo(entry.notification.getPackageName(), 0)));
+                title.setText(pmUser.getApplicationLabel(
+                        pmUser.getApplicationInfo(entry.notification.getPackageName(), 0)));
             } catch (NameNotFoundException e) {
                 title.setText(entry.notification.getPackageName());
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 8996197..3b14082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -82,6 +82,7 @@
 
     public void hide(boolean destroyView) {
         if (mKeyguardView != null) {
+            mKeyguardView.setOnDismissAction(null);
             mKeyguardView.cleanUp();
         }
         if (destroyView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index bf66c41..b66c310 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
@@ -48,6 +52,7 @@
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
 
     private int mSystemIconsSwitcherHiddenExpandedMargin;
+    private Interpolator mFastOutSlowInInterpolator;
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -61,6 +66,8 @@
         mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
         mBatteryLevel = (TextView) findViewById(R.id.battery_level);
         loadDimens();
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+                android.R.interpolator.fast_out_slow_in);
         updateUserSwitcher();
     }
 
@@ -70,7 +77,14 @@
     }
 
     private void updateVisibilities() {
-        mMultiUserSwitch.setVisibility(!mKeyguardUserSwitcherShowing ? VISIBLE : GONE);
+        if (mMultiUserSwitch.getParent() != this && !mKeyguardUserSwitcherShowing) {
+            if (mMultiUserSwitch.getParent() != null) {
+                getOverlay().remove(mMultiUserSwitch);
+            }
+            addView(mMultiUserSwitch, 0);
+        } else if (mMultiUserSwitch.getParent() == this && mKeyguardUserSwitcherShowing) {
+            removeView(mMultiUserSwitch);
+        }
         mBatteryLevel.setVisibility(mBatteryCharging ? View.VISIBLE : View.GONE);
     }
 
@@ -137,12 +151,71 @@
         updateUserSwitcher();
     }
 
-    public void setKeyguardUserSwitcherShowing(boolean showing) {
+    public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
         mKeyguardUserSwitcherShowing = showing;
+        if (animate) {
+            animateNextLayoutChange();
+        }
         updateVisibilities();
         updateSystemIconsLayoutParams();
     }
 
+    private void animateNextLayoutChange() {
+        final int systemIconsCurrentX = mSystemIconsSuperContainer.getLeft();
+        final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == this;
+        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                getViewTreeObserver().removeOnPreDrawListener(this);
+                boolean userSwitcherHiding = userSwitcherVisible
+                        && mMultiUserSwitch.getParent() != KeyguardStatusBarView.this;
+                mSystemIconsSuperContainer.setX(systemIconsCurrentX);
+                mSystemIconsSuperContainer.animate()
+                        .translationX(0)
+                        .setDuration(400)
+                        .setStartDelay(userSwitcherHiding ? 300 : 0)
+                        .setInterpolator(mFastOutSlowInInterpolator)
+                        .start();
+                if (userSwitcherHiding) {
+                    getOverlay().add(mMultiUserSwitch);
+                    mMultiUserSwitch.animate()
+                            .alpha(0f)
+                            .setDuration(300)
+                            .setStartDelay(0)
+                            .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+                            .withEndAction(new Runnable() {
+                                @Override
+                                public void run() {
+                                    mMultiUserSwitch.setAlpha(1f);
+                                    getOverlay().remove(mMultiUserSwitch);
+                                }
+                            })
+                            .start();
+
+                } else {
+                    mMultiUserSwitch.setAlpha(0f);
+                    mMultiUserSwitch.animate()
+                            .alpha(1f)
+                            .setDuration(300)
+                            .setStartDelay(200)
+                            .setInterpolator(PhoneStatusBar.ALPHA_IN);
+                }
+                return true;
+            }
+        });
+
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        if (visibility != View.VISIBLE) {
+            mSystemIconsSuperContainer.animate().cancel();
+            mMultiUserSwitch.animate().cancel();
+            mMultiUserSwitch.setAlpha(1f);
+        }
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index af30266..47325c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -65,7 +65,7 @@
         if (um.isUserSwitcherEnabled()) {
             if (mKeyguardMode) {
                 if (mKeyguardUserSwitcher != null) {
-                    mKeyguardUserSwitcher.show();
+                    mKeyguardUserSwitcher.show(true /* animate */);
                 }
             } else {
                 mQsPanel.showDetailAdapter(true,
@@ -78,4 +78,9 @@
             getContext().startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
         }
     }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
 }
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 6af2cf8..74ae4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.MirrorView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -62,6 +63,7 @@
 
     private KeyguardAffordanceHelper mAfforanceHelper;
     private StatusBarHeaderView mHeader;
+    private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private View mQsContainer;
     private QSPanel mQsPanel;
@@ -225,11 +227,18 @@
             mHeader.post(mUpdateHeader);
         }
 
-        lp = (FrameLayout.LayoutParams) mNotificationContainerParent.getLayoutParams();
+        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
         if (lp.width != panelWidth) {
             lp.width = panelWidth;
             lp.gravity = panelGravity;
-            mNotificationContainerParent.setLayoutParams(lp);
+            mNotificationStackScroller.setLayoutParams(lp);
+        }
+
+        lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
+        if (lp.width != panelWidth) {
+            lp.width = panelWidth;
+            lp.gravity = panelGravity;
+            mScrollView.setLayoutParams(lp);
         }
     }
 
@@ -359,6 +368,7 @@
         mUnlockIconActive = false;
         mAfforanceHelper.reset(true);
         closeQs();
+        mStatusBar.dismissPopups();
         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
                 true /* cancelAnimators */);
     }
@@ -939,6 +949,9 @@
                 && !mStackScrollerOverscrolling && mQsScrimEnabled
                         ? View.VISIBLE
                         : View.INVISIBLE);
+        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
+            mKeyguardUserSwitcher.hide(true /* animate */);
+        }
     }
 
     private void setQsExpansion(float height) {
@@ -1704,6 +1717,10 @@
         }
     }
 
+    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+        mKeyguardUserSwitcher = keyguardUserSwitcher;
+    }
+
     private final Runnable mUpdateHeader = new Runnable() {
         @Override
         public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 7c6e47c..57b7401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -35,6 +35,7 @@
     private View mScrollView;
     private View mUserSwitcher;
     private View mStackScroller;
+    private View mKeyguardStatusBar;
     private boolean mInflated;
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
@@ -46,6 +47,7 @@
         super.onFinishInflate();
         mScrollView = findViewById(R.id.scroll_view);
         mStackScroller = findViewById(R.id.notification_stack_scroller);
+        mKeyguardStatusBar = findViewById(R.id.keyguard_header);
         ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher);
         userSwitcher.setOnInflateListener(this);
         mUserSwitcher = userSwitcher;
@@ -61,18 +63,30 @@
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
+        boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
 
         // Invert the order of the scroll view and user switcher such that the notifications receive
         // touches first but the panel gets drawn above.
         if (child == mScrollView) {
             return super.drawChild(canvas, mStackScroller, drawingTime);
         } else if (child == mStackScroller) {
-            return super.drawChild(canvas, userSwitcherVisible ? mUserSwitcher : mScrollView,
+            return super.drawChild(canvas,
+                    userSwitcherVisible && statusBarVisible ? mUserSwitcher
+                    : statusBarVisible ? mKeyguardStatusBar
+                    : userSwitcherVisible ? mUserSwitcher
+                    : mScrollView,
                     drawingTime);
         } else if (child == mUserSwitcher) {
-            return super.drawChild(canvas, userSwitcherVisible ? mScrollView : mUserSwitcher,
+            return super.drawChild(canvas,
+                    userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
+                    : mScrollView,
                     drawingTime);
-        } else {
+        } else if (child == mKeyguardStatusBar) {
+            return super.drawChild(canvas,
+                    userSwitcherVisible && statusBarVisible ? mScrollView
+                    : mScrollView,
+                    drawingTime);
+        }else {
             return super.drawChild(canvas, child, drawingTime);
         }
     }
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 015a21d..75e31e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -799,13 +799,13 @@
 
         mFlashlightController = new FlashlightController(mContext);
         mKeyguardBottomArea.setFlashlightController(mFlashlightController);
-        mUserSwitcherController = new UserSwitcherController(mContext);
         mNextAlarmController = new NextAlarmController(mContext);
         mKeyguardMonitor = new KeyguardMonitor();
+        mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor);
 
         mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
                 (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
-                mKeyguardStatusBar, mUserSwitcherController);
+                mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
 
 
         // Set up the quick settings tile panel
@@ -3341,7 +3341,7 @@
 
     public void showKeyguard() {
         setBarState(StatusBarState.KEYGUARD);
-        updateKeyguardState(false /* goingToFullShade */);
+        updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
         instantExpandNotificationsPanel();
         mLeaveOpenOnKeyguardHide = false;
         if (mDraggedDownRow != null) {
@@ -3413,7 +3413,7 @@
         } else {
             instantCollapseNotificationPanel();
         }
-        updateKeyguardState(staying);
+        updateKeyguardState(staying, false /* fromShadeLocked */);
         return staying;
     }
 
@@ -3449,14 +3449,15 @@
                 && mStatusBarKeyguardViewManager.isSecure());
     }
 
-    private void updateKeyguardState(boolean goingToFullShade) {
+    private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
         if (mState == StatusBarState.KEYGUARD) {
             mKeyguardIndicationController.setVisible(true);
             mNotificationPanel.resetViews();
-            mKeyguardUserSwitcher.setKeyguard(true);
+            mKeyguardUserSwitcher.setKeyguard(true, fromShadeLocked);
         } else {
             mKeyguardIndicationController.setVisible(false);
-            mKeyguardUserSwitcher.setKeyguard(false);
+            mKeyguardUserSwitcher.setKeyguard(false,
+                    goingToFullShade || mState == StatusBarState.SHADE_LOCKED || fromShadeLocked);
         }
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             mScrimController.setKeyguardShowing(true);
@@ -3686,7 +3687,7 @@
         } else {
             mNotificationPanel.animateToFullShade(0 /* delay */);
             setBarState(StatusBarState.SHADE_LOCKED);
-            updateKeyguardState(false /* goingToFullShade */);
+            updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
             if (row != null) {
                 row.setUserLocked(false);
             }
@@ -3699,7 +3700,7 @@
     public void goToKeyguard() {
         if (mState == StatusBarState.SHADE_LOCKED) {
             setBarState(StatusBarState.KEYGUARD);
-            updateKeyguardState(false /* goingToFullShade */);
+            updateKeyguardState(false /* goingToFullShade */, true /* fromShadeLocked*/);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
index 93561aa..101a5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -38,6 +38,7 @@
     private int mActiveFrameColor;
     private int mFrameColor;
     private float mFrameWidth;
+    private float mFramePadding;
     private Bitmap mBitmap;
     private Drawable mDrawable;
 
@@ -60,6 +61,9 @@
                 case R.styleable.UserAvatarView_frameWidth:
                     setFrameWidth(a.getDimension(attr, 0));
                     break;
+                case R.styleable.UserAvatarView_framePadding:
+                    setFramePadding(a.getDimension(attr, 0));
+                    break;
                 case R.styleable.UserAvatarView_activeFrameColor:
                     setActiveFrameColor(a.getColor(attr, 0));
                     break;
@@ -115,6 +119,11 @@
         invalidate();
     }
 
+    public void setFramePadding(float framePadding) {
+        mFramePadding = framePadding;
+        invalidate();
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -131,11 +140,11 @@
             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);
+            dwidth = vwidth;
+            dheight = vheight;
+            mDrawable.setBounds(0, 0, dwidth, dheight);
         } else {
             return;
         }
@@ -183,7 +192,8 @@
         if (frameColor != 0) {
             mFramePaint.setColor(frameColor);
             mFramePaint.setStrokeWidth(mFrameWidth);
-            canvas.drawCircle(halfW, halfH, halfSW - mFrameWidth / 2f, mFramePaint);
+            canvas.drawCircle(halfW, halfH, halfSW + (mFramePadding - mFrameWidth) / 2f,
+                    mFramePaint);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index a0312bc..18583ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -16,17 +16,26 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.database.DataSetObserver;
-import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.view.animation.AnimationUtils;
 import android.widget.TextView;
 
+import com.android.keyguard.AppearAnimationUtils;
 import com.android.systemui.R;
+import com.android.systemui.qs.tiles.UserDetailItemView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
 import com.android.systemui.statusbar.phone.UserAvatarView;
 
 /**
@@ -41,30 +50,42 @@
     private final KeyguardStatusBarView mStatusBarView;
     private final Adapter mAdapter;
     private final boolean mSimpleUserSwitcher;
+    private final AppearAnimationUtils mAppearAnimationUtils;
+    private final KeyguardUserSwitcherScrim mBackground;
+    private ObjectAnimator mBgAnimator;
 
     public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            KeyguardStatusBarView statusBarView, UserSwitcherController userSwitcherController) {
+            KeyguardStatusBarView statusBarView, NotificationPanelView panelView,
+            UserSwitcherController userSwitcherController) {
         if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON) {
             mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+            mBackground = new KeyguardUserSwitcherScrim(mUserSwitcher);
+            mUserSwitcher.setBackground(mBackground);
             mStatusBarView = statusBarView;
             mStatusBarView.setKeyguardUserSwitcher(this);
+            panelView.setKeyguardUserSwitcher(this);
             mAdapter = new Adapter(context, userSwitcherController);
             mAdapter.registerDataSetObserver(mDataSetObserver);
             mSimpleUserSwitcher = userSwitcherController.isSimpleUserSwitcher();
+            mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
+                    AnimationUtils.loadInterpolator(
+                            context, android.R.interpolator.fast_out_slow_in));
         } else {
             mUserSwitcher = null;
             mStatusBarView = null;
             mAdapter = null;
             mSimpleUserSwitcher = false;
+            mAppearAnimationUtils = null;
+            mBackground = null;
         }
     }
 
-    public void setKeyguard(boolean keyguard) {
+    public void setKeyguard(boolean keyguard, boolean animate) {
         if (mUserSwitcher != null) {
             if (keyguard && shouldExpandByDefault()) {
-                show();
+                show(animate);
             } else {
-                hide();
+                hide(animate);
             }
         }
     }
@@ -74,25 +95,84 @@
      * @see android.os.UserManager#isUserSwitcherEnabled()
      */
     private boolean shouldExpandByDefault() {
-        return mSimpleUserSwitcher || mAdapter.getSwitchableUsers() > 1;
+        return mSimpleUserSwitcher;
     }
 
-    public void show() {
-        if (mUserSwitcher != null) {
-            // TODO: animate
+    public void show(boolean animate) {
+        if (mUserSwitcher != null && mUserSwitcher.getVisibility() != View.VISIBLE) {
+            cancelAnimations();
             mUserSwitcher.setVisibility(View.VISIBLE);
-            mStatusBarView.setKeyguardUserSwitcherShowing(true);
+            mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
+            if (animate) {
+                startAppearAnimation();
+            }
         }
     }
 
-    public void hide() {
-        if (mUserSwitcher != null) {
-            // TODO: animate
-            mUserSwitcher.setVisibility(View.GONE);
-            mStatusBarView.setKeyguardUserSwitcherShowing(false);
+    public void hide(boolean animate) {
+        if (mUserSwitcher != null && mUserSwitcher.getVisibility() == View.VISIBLE) {
+            cancelAnimations();
+            if (animate) {
+                startDisappearAnimation();
+            } else {
+                mUserSwitcher.setVisibility(View.GONE);
+            }
+            mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
         }
     }
 
+    private void cancelAnimations() {
+        int count = mUserSwitcher.getChildCount();
+        for (int i = 0; i < count; i++) {
+            mUserSwitcher.getChildAt(i).animate().cancel();
+        }
+        if (mBgAnimator != null) {
+            mBgAnimator.cancel();
+        }
+        mUserSwitcher.animate().cancel();
+    }
+
+    private void startAppearAnimation() {
+        int count = mUserSwitcher.getChildCount();
+        View[] objects = new View[count];
+        for (int i = 0; i < count; i++) {
+            objects[i] = mUserSwitcher.getChildAt(i);
+        }
+        mUserSwitcher.setClipChildren(false);
+        mUserSwitcher.setClipToPadding(false);
+        mAppearAnimationUtils.startAppearAnimation(objects, new Runnable() {
+            @Override
+            public void run() {
+                mUserSwitcher.setClipChildren(true);
+                mUserSwitcher.setClipToPadding(true);
+            }
+        });
+        mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
+        mBgAnimator.setDuration(400);
+        mBgAnimator.setInterpolator(PhoneStatusBar.ALPHA_IN);
+        mBgAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mBgAnimator = null;
+            }
+        });
+        mBgAnimator.start();
+    }
+
+    private void startDisappearAnimation() {
+        mUserSwitcher.animate()
+                .alpha(0f)
+                .setDuration(300)
+                .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mUserSwitcher.setVisibility(View.GONE);
+                        mUserSwitcher.setAlpha(1f);
+                    }
+                });
+    }
+
     private void refresh() {
         final int childCount = mUserSwitcher.getChildCount();
         final int adapterCount = mAdapter.getCount();
@@ -140,21 +220,19 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             UserSwitcherController.UserRecord item = getItem(position);
 
-            if (convertView == null
+            if (!(convertView instanceof UserDetailItemView)
                     || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
                 convertView = LayoutInflater.from(mContext).inflate(
                         R.layout.keyguard_user_switcher_item, parent, false);
                 convertView.setOnClickListener(this);
             }
+            UserDetailItemView v = (UserDetailItemView) convertView;
 
-            TextView nameView = (TextView) convertView.findViewById(R.id.name);
-            UserAvatarView pictureView = (UserAvatarView) convertView.findViewById(R.id.picture);
-
-            nameView.setText(getName(mContext, item));
+            String name = getName(mContext, item);
             if (item.picture == null) {
-                pictureView.setDrawable(mContext.getDrawable(R.drawable.ic_account_circle_qs));
+                v.bind(name, getDrawable(mContext, item));
             } else {
-                pictureView.setBitmap(item.picture);
+                v.bind(name, item.picture);
             }
             convertView.setActivated(item.isCurrent);
             convertView.setTag(item);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
new file mode 100644
index 0000000..4363037
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.LightingColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.LayoutDirection;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Gradient background for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherScrim extends Drawable
+        implements View.OnLayoutChangeListener {
+
+    private static final float OUTER_EXTENT = 2.5f;
+    private static final float INNER_EXTENT = 0.75f;
+
+    private int mDarkColor;
+    private int mTop;
+    private int mAlpha;
+    private Paint mRadialGradientPaint = new Paint();
+    private int mLayoutWidth;
+
+    public KeyguardUserSwitcherScrim(View host) {
+        host.addOnLayoutChangeListener(this);
+        mDarkColor = host.getResources().getColor(
+                R.color.keyguard_user_switcher_background_gradient_color);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        boolean isLtr = getLayoutDirection() == LayoutDirection.LTR;
+        Rect bounds = getBounds();
+        float width = bounds.width() * OUTER_EXTENT;
+        float height = (mTop + bounds.height()) * OUTER_EXTENT;
+        canvas.translate(0, -mTop);
+        canvas.scale(1, height / width);
+        canvas.drawRect(isLtr ? bounds.right - width : 0, 0,
+                isLtr ? bounds.right : bounds.left + width, width, mRadialGradientPaint);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mAlpha = alpha;
+        updatePaint();
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
+            mLayoutWidth = right - left;
+            mTop = top;
+            updatePaint();
+        }
+    }
+
+    private void updatePaint() {
+        if (mLayoutWidth == 0) {
+            return;
+        }
+        float radius = mLayoutWidth * OUTER_EXTENT;
+        boolean isLtr = getLayoutDirection() == LayoutDirection.LTR;
+        mRadialGradientPaint.setShader(
+                new RadialGradient(isLtr ? mLayoutWidth : 0, 0, radius,
+                        new int[] { Color.argb(
+                                        (int) (Color.alpha(mDarkColor) * mAlpha / 255f), 0, 0, 0),
+                                Color.TRANSPARENT },
+                        new float[] { Math.max(0f, mLayoutWidth * INNER_EXTENT / radius), 1f },
+                        Shader.TileMode.CLAMP));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 6e3656d..a1993f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -34,6 +34,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -68,15 +69,18 @@
     private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
     private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
             = new GuestResumeSessionReceiver();
-    private boolean mSimpleUserSwitcher;
+    private final KeyguardMonitor mKeyguardMonitor;
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
     private Dialog mExitGuestDialog;
     private int mLastNonGuestUser = UserHandle.USER_OWNER;
+    private boolean mSimpleUserSwitcher;
+    private boolean mAddUsersWhenLocked;
 
-    public UserSwitcherController(Context context) {
+    public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor) {
         mContext = context;
         mGuestResumeSessionReceiver.register(context);
+        mKeyguardMonitor = keyguardMonitor;
         mUserManager = UserManager.get(context);
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
@@ -87,11 +91,17 @@
         mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter,
                 null /* permission */, null /* scheduler */);
 
-        mSimpleUserSwitcher = Settings.Global.getInt(context.getContentResolver(),
-                SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
+
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
-                mSimpleUserSwitcherObserver);
+                mSettingsObserver);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
+                mSettingsObserver);
+        // Fetch initial values.
+        mSettingsObserver.onChange(false);
+
+        keyguardMonitor.addCallback(mCallback);
 
         refreshUsers(UserHandle.USER_NULL);
     }
@@ -116,6 +126,7 @@
             bitmaps.put(r.info.id, r.picture);
         }
 
+        final boolean addUsersWhenLocked = mAddUsersWhenLocked;
         new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
             @SuppressWarnings("unchecked")
             @Override
@@ -135,7 +146,8 @@
                     boolean isCurrent = currentId == info.id;
                     if (info.isGuest()) {
                         guestRecord = new UserRecord(info, null /* picture */,
-                                true /* isGuest */, isCurrent);
+                                true /* isGuest */, isCurrent, false /* isAddUser */,
+                                false /* isRestricted */);
                     } else if (info.supportsSwitchTo()) {
                         Bitmap picture = bitmaps.get(info.id);
                         if (picture == null) {
@@ -145,19 +157,40 @@
                             picture = BitmapHelper.createCircularClip(
                                     picture, avatarSize, avatarSize);
                         }
-                        records.add(new UserRecord(info, picture, false /* isGuest */, isCurrent));
+                        records.add(new UserRecord(info, picture, false /* isGuest */, isCurrent,
+                                false /* isAddUser */, false /* isRestricted */));
                     }
                 }
 
+                boolean ownerCanCreateUsers = !mUserManager.hasUserRestriction(
+                        UserManager.DISALLOW_ADD_USER, UserHandle.OWNER);
+                boolean currentUserCanCreateUsers =
+                        (currentId == UserHandle.USER_OWNER) && ownerCanCreateUsers;
+                boolean anyoneCanCreateUsers = ownerCanCreateUsers && addUsersWhenLocked;
+                boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
+                        && guestRecord == null;
+                boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
+                        && records.size() < UserManager.getMaxSupportedUsers();
+                boolean createIsRestricted = !addUsersWhenLocked;
+
                 if (!mSimpleUserSwitcher) {
                     if (guestRecord == null) {
-                        records.add(new UserRecord(null /* info */, null /* picture */,
-                                true /* isGuest */, false /* isCurrent */));
+                        if (canCreateGuest) {
+                            records.add(new UserRecord(null /* info */, null /* picture */,
+                                    true /* isGuest */, false /* isCurrent */,
+                                    false /* isAddUser */, createIsRestricted));
+                        }
                     } else {
                         records.add(guestRecord);
                     }
                 }
 
+                if (canCreateUser) {
+                    records.add(new UserRecord(null /* info */, null /* picture */,
+                            false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
+                            createIsRestricted));
+                }
+
                 return records;
             }
 
@@ -168,7 +201,7 @@
                     notifyAdapters();
                 }
             }
-        }.execute((SparseArray)bitmaps);
+        }.execute((SparseArray) bitmaps);
     }
 
     private void notifyAdapters() {
@@ -190,8 +223,10 @@
         int id;
         if (record.isGuest && record.info == null) {
             // No guest user. Create one.
-            id = mUserManager.createGuest(mContext,
-                    mContext.getResources().getString(R.string.guest_nickname)).id;
+            id = mUserManager.createGuest(mContext, mContext.getString(R.string.guest_nickname)).id;
+        } else if (record.isAddUser) {
+            id = mUserManager.createUser(
+                    mContext.getString(R.string.user_new_user_name), 0 /* flags */).id;
         } else {
             id = record.info.id;
         }
@@ -260,6 +295,11 @@
                     if (shouldBeCurrent && !record.isGuest) {
                         mLastNonGuestUser = record.info.id;
                     }
+                    if (currentId != UserHandle.USER_OWNER && record.isRestricted) {
+                        // Immediately remove restricted records in case the AsyncTask is too slow.
+                        mUsers.remove(i);
+                        i--;
+                    }
                 }
                 notifyAdapters();
             }
@@ -272,10 +312,12 @@
         }
     };
 
-    private final ContentObserver mSimpleUserSwitcherObserver = new ContentObserver(new Handler()) {
+    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
         public void onChange(boolean selfChange) {
             mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
                     SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
+            mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
             refreshUsers(UserHandle.USER_NULL);
         };
     };
@@ -301,7 +343,22 @@
 
         @Override
         public int getCount() {
-            return mController.mUsers.size();
+            boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
+                    && mController.mKeyguardMonitor.isSecure();
+            if (!secureKeyguardShowing) {
+                return mController.mUsers.size();
+            }
+            // The lock screen is secure and showing. Filter out restricted records.
+            final int N = mController.mUsers.size();
+            int count = 0;
+            for (int i = 0; i < N; i++) {
+                if (mController.mUsers.get(i).isRestricted) {
+                    break;
+                } else {
+                    count++;
+                }
+            }
+            return count;
         }
 
         @Override
@@ -326,6 +383,8 @@
                     return context.getString(
                             item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
                 }
+            } else if (item.isAddUser) {
+                return context.getString(R.string.user_add_user);
             } else {
                 return item.info.name;
             }
@@ -342,6 +401,13 @@
             }
             return result;
         }
+
+        public Drawable getDrawable(Context context, UserRecord item) {
+            if (item.isAddUser) {
+                return context.getDrawable(R.drawable.ic_add_circle_qs);
+            }
+            return context.getDrawable(R.drawable.ic_account_circle_qs);
+        }
     }
 
     public static final class UserRecord {
@@ -349,16 +415,22 @@
         public final Bitmap picture;
         public final boolean isGuest;
         public final boolean isCurrent;
+        public final boolean isAddUser;
+        /** If true, the record is only visible to the owner and only when unlocked. */
+        public final boolean isRestricted;
 
-        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent) {
+        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
+                boolean isAddUser, boolean isRestricted) {
             this.info = info;
             this.picture = picture;
             this.isGuest = isGuest;
             this.isCurrent = isCurrent;
+            this.isAddUser = isAddUser;
+            this.isRestricted = isRestricted;
         }
 
         public UserRecord copyWithIsCurrent(boolean _isCurrent) {
-            return new UserRecord(info, picture, isGuest, _isCurrent);
+            return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted);
         }
 
         public String toString() {
@@ -367,17 +439,17 @@
             if (info != null) {
                 sb.append("name=\"" + info.name + "\" id=" + info.id);
             } else {
-                sb.append("<add guest placeholder>");
+                if (isGuest) {
+                    sb.append("<add guest placeholder>");
+                } else if (isAddUser) {
+                    sb.append("<add user placeholder>");
+                }
             }
-            if (isGuest) {
-                sb.append(" <isGuest>");
-            }
-            if (isCurrent) {
-                sb.append(" <isCurrent>");
-            }
-            if (picture != null) {
-                sb.append(" <hasPicture>");
-            }
+            if (isGuest) sb.append(" <isGuest>");
+            if (isAddUser) sb.append(" <isAddUser>");
+            if (isCurrent) sb.append(" <isCurrent>");
+            if (picture != null) sb.append(" <hasPicture>");
+            if (isRestricted) sb.append(" <isRestricted>");
             sb.append(')');
             return sb.toString();
         }
@@ -393,12 +465,12 @@
 
         @Override
         public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            UserDetailView v;
             if (!(convertView instanceof UserDetailView)) {
-                convertView = UserDetailView.inflate(context, parent, false);
-            }
-            UserDetailView v = (UserDetailView) convertView;
-            if (v.getAdapter() == null) {
+                v = UserDetailView.inflate(context, parent, false);
                 v.createAndSetAdapter(UserSwitcherController.this);
+            } else {
+                v = (UserDetailView) convertView;
             }
             return v;
         }
@@ -418,6 +490,13 @@
         }
     };
 
+    private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
+        @Override
+        public void onKeyguardChanged() {
+            notifyAdapters();
+        }
+    };
+
     private final class ExitGuestDialog extends SystemUIDialog implements
             DialogInterface.OnClickListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 2e97d18..6c4fb7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -29,11 +29,13 @@
     Uri getExitConditionId();
     long getNextAlarm();
     void setUserId(int userId);
+    boolean isZenAvailable();
 
     public static class Callback {
         public void onZenChanged(int zen) {}
         public void onExitConditionChanged(Uri exitConditionId) {}
         public void onConditionsChanged(Condition[] conditions) {}
         public void onNextAlarmChanged() {}
+        public void onZenAvailableChanged(boolean available) {}
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index a3cdd41..9d3dec8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -20,15 +20,18 @@
 import android.app.AlarmManager;
 import android.app.INotificationManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
 import android.service.notification.Condition;
 import android.service.notification.IConditionListener;
 import android.service.notification.ZenModeConfig;
@@ -52,6 +55,7 @@
     private final INotificationManager mNoMan;
     private final LinkedHashMap<Uri, Condition> mConditions = new LinkedHashMap<Uri, Condition>();
     private final AlarmManager mAlarmManager;
+    private final SetupObserver mSetupObserver;
 
     private int mUserId;
     private boolean mRequesting;
@@ -76,6 +80,8 @@
         mNoMan = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mSetupObserver = new SetupObserver(handler);
+        mSetupObserver.register();
     }
 
     @Override
@@ -99,6 +105,11 @@
     }
 
     @Override
+    public boolean isZenAvailable() {
+        return mSetupObserver.isDeviceProvisioned() && mSetupObserver.isUserSetup();
+    }
+
+    @Override
     public void requestConditions(boolean request) {
         mRequesting = request;
         try {
@@ -148,6 +159,7 @@
         mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId),
                 new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED), null, null);
         mRegistered = true;
+        mSetupObserver.register();
     }
 
     private void fireNextAlarmChanged() {
@@ -162,6 +174,12 @@
         }
     }
 
+    private void fireZenAvailableChanged(boolean available) {
+        for (Callback cb : mCallbacks) {
+            cb.onZenAvailableChanged(available);
+        }
+    }
+
     private void fireConditionsChanged(Condition[] conditions) {
         for (Callback cb : mCallbacks) {
             cb.onConditionsChanged(conditions);
@@ -204,4 +222,42 @@
             }
         }
     };
+
+    private final class SetupObserver extends ContentObserver {
+        private final ContentResolver mResolver;
+
+        private boolean mRegistered;
+
+        public SetupObserver(Handler handler) {
+            super(handler);
+            mResolver = mContext.getContentResolver();
+        }
+
+        public boolean isUserSetup() {
+            return Secure.getIntForUser(mResolver, Secure.USER_SETUP_COMPLETE, 0, mUserId) != 0;
+        }
+
+        public boolean isDeviceProvisioned() {
+            return Global.getInt(mResolver, Global.DEVICE_PROVISIONED, 0) != 0;
+        }
+
+        public void register() {
+            if (mRegistered) {
+                mResolver.unregisterContentObserver(this);
+            }
+            mResolver.registerContentObserver(
+                    Global.getUriFor(Global.DEVICE_PROVISIONED), false, this);
+            mResolver.registerContentObserver(
+                    Secure.getUriFor(Secure.USER_SETUP_COMPLETE), false, this, mUserId);
+            fireZenAvailableChanged(isZenAvailable());
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (Global.getUriFor(Global.DEVICE_PROVISIONED).equals(uri)
+                    || Secure.getUriFor(Secure.USER_SETUP_COMPLETE).equals(uri)) {
+                fireZenAvailableChanged(isZenAvailable());
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 3a63a79..5233da2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -107,7 +107,7 @@
     private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
     private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
     private static final int MSG_LAYOUT_DIRECTION = 12;
-    private static final int MSG_ZEN_MODE_CHANGED = 13;
+    private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13;
     private static final int MSG_USER_ACTIVITY = 14;
 
     // Pseudo stream type for master volume
@@ -125,7 +125,7 @@
     private final ZenModeController mZenController;
     private boolean mRingIsSilent;
     private boolean mVoiceCapable;
-    private boolean mZenModeCapable;
+    private boolean mZenModeAvailable;
     private boolean mZenPanelExpanded;
     private int mTimeoutDelay = TIMEOUT_DELAY;
     private float mDisabledAlpha;
@@ -405,9 +405,10 @@
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
 
-        mZenModeCapable = !useMasterVolume && mZenController != null;
-        updateZenMode(mZenController != null ? mZenController.getZen() : Global.ZEN_MODE_OFF);
-        mZenController.addCallback(mZenCallback);
+        if (mZenController != null && !useMasterVolume) {
+            mZenModeAvailable = mZenController.isZenAvailable();
+            mZenController.addCallback(mZenCallback);
+        }
 
         final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
         final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
@@ -434,7 +435,7 @@
         pw.print("  mTag="); pw.println(mTag);
         pw.print("  mRingIsSilent="); pw.println(mRingIsSilent);
         pw.print("  mVoiceCapable="); pw.println(mVoiceCapable);
-        pw.print("  mZenModeCapable="); pw.println(mZenModeCapable);
+        pw.print("  mZenModeAvailable="); pw.println(mZenModeAvailable);
         pw.print("  mZenPanelExpanded="); pw.println(mZenPanelExpanded);
         pw.print("  mTimeoutDelay="); pw.println(mTimeoutDelay);
         pw.print("  mDisabledAlpha="); pw.println(mDisabledAlpha);
@@ -645,7 +646,7 @@
             active.group.setVisibility(View.VISIBLE);
             updateSlider(active);
             updateTimeoutDelay();
-            setZenPanelVisible(isNotificationOrRing(mActiveStreamType));
+            updateZenPanelVisible();
         }
     }
 
@@ -776,14 +777,8 @@
         }
     }
 
-    private void updateZenMode(int zen) {
-        final boolean show = mZenModeCapable && isNotificationOrRing(mActiveStreamType);
-        setZenPanelVisible(show);
-    }
-
-    public void postZenModeChanged(int zen) {
-        removeMessages(MSG_ZEN_MODE_CHANGED);
-        obtainMessage(MSG_ZEN_MODE_CHANGED, zen).sendToTarget();
+    private void updateZenPanelVisible() {
+        setZenPanelVisible(mZenModeAvailable && isNotificationOrRing(mActiveStreamType));
     }
 
     public void postVolumeChanged(int streamType, int flags) {
@@ -1307,8 +1302,9 @@
                 setLayoutDirection(msg.arg1);
                 break;
 
-            case MSG_ZEN_MODE_CHANGED:
-                updateZenMode(msg.arg1);
+            case MSG_ZEN_MODE_AVAILABLE_CHANGED:
+                mZenModeAvailable = msg.arg1 != 0;
+                updateZenPanelVisible();
                 break;
 
             case MSG_USER_ACTIVITY:
@@ -1359,8 +1355,8 @@
     };
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
-        public void onZenChanged(int zen) {
-            postZenModeChanged(zen);
+        public void onZenAvailableChanged(boolean available) {
+            obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget();
         }
     };
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index ff3cd9d..964acbd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1314,7 +1314,6 @@
             mBackgroundDrawable = drawable;
             if (mDecor != null) {
                 mDecor.setWindowBackground(drawable);
-                mDecor.setClipToOutline(drawable != null && mClipToOutline);
             }
         }
     }
@@ -3389,10 +3388,6 @@
             }
             mDecor.setWindowBackground(background);
 
-            if (background != null) {
-                mDecor.setClipToOutline(mClipToOutline);
-            }
-
             final Drawable frame;
             if (mFrameResource != 0) {
                 frame = getContext().getDrawable(mFrameResource);
@@ -3402,6 +3397,7 @@
             mDecor.setWindowFrame(frame);
 
             mDecor.setElevation(mElevation);
+            mDecor.setClipToOutline(mClipToOutline);
 
             if (mTitle != null) {
                 setTitle(mTitle);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 59aef32..77b14ac 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -5129,6 +5129,14 @@
                 }
             }
 
+            // The path needs to be canonical
+            if (info.path.contains("..") || info.path.contains("//")) {
+                if (MORE_DEBUG) {
+                    Slog.w(TAG, "Dropping invalid path " + info.path);
+                }
+                return false;
+            }
+
             // Otherwise we think this file is good to go
             return true;
         }
@@ -5680,6 +5688,14 @@
                                 break;
                         }
 
+                        // The path needs to be canonical
+                        if (info.path.contains("..") || info.path.contains("//")) {
+                            if (MORE_DEBUG) {
+                                Slog.w(TAG, "Dropping invalid path " + info.path);
+                            }
+                            okay = false;
+                        }
+
                         // If the policy is satisfied, go ahead and set up to pipe the
                         // data to the agent.
                         if (DEBUG && okay && mAgent != null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6761f24..f9baccd 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -251,10 +251,7 @@
     private int mNetworkPreference;
     private int mActiveDefaultNetwork = -1;
     // 0 is full bad, 100 is full good
-    private int mDefaultInetCondition = 0;
     private int mDefaultInetConditionPublished = 0;
-    private boolean mInetConditionChangeInFlight = false;
-    private int mDefaultConnectionSequence = 0;
 
     private Object mDnsLock = new Object();
     private int mNumDnsEntries;
@@ -274,19 +271,6 @@
     private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
 
     /**
-     * used internally to synchronize inet condition reports
-     * arg1 = networkType
-     * arg2 = condition (0 bad, 100 good)
-     */
-    private static final int EVENT_INET_CONDITION_CHANGE = 4;
-
-    /**
-     * used internally to mark the end of inet condition hold periods
-     * arg1 = networkType
-     */
-    private static final int EVENT_INET_CONDITION_HOLD_END = 5;
-
-    /**
      * used internally to clear a wakelock when transitioning
      * from one net to another.  Clear happens when we get a new
      * network - EVENT_EXPIRE_NET_TRANSITION_WAKELOCK happens
@@ -490,10 +474,6 @@
             mTypeLists[type] = new ArrayList<NetworkAgentInfo>();
         }
 
-        private boolean isDefaultNetwork(NetworkAgentInfo nai) {
-            return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai;
-        }
-
         public boolean isTypeSupported(int type) {
             return isNetworkTypeValid(type) && mTypeLists[type] != null;
         }
@@ -2052,6 +2032,9 @@
                 nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                         null, null);
             }
+            if (isDefaultNetwork(nai)) {
+                mDefaultInetConditionPublished = 0;
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
@@ -2222,18 +2205,6 @@
                     }
                     break;
                 }
-                case EVENT_INET_CONDITION_CHANGE: {
-                    int netType = msg.arg1;
-                    int condition = msg.arg2;
-                    handleInetConditionChange(netType, condition);
-                    break;
-                }
-                case EVENT_INET_CONDITION_HOLD_END: {
-                    int netType = msg.arg1;
-                    int sequence = msg.arg2;
-                    handleInetConditionHoldEnd(netType, sequence);
-                    break;
-                }
                 case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
                     handleDeprecatedGlobalHttpProxy();
                     break;
@@ -2428,99 +2399,15 @@
 
     // 100 percent is full good, 0 is full bad.
     public void reportInetCondition(int networkType, int percentage) {
-        if (VDBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.STATUS_BAR,
-                "ConnectivityService");
-
-        if (DBG) {
-            int pid = getCallingPid();
-            int uid = getCallingUid();
-            String s = pid + "(" + uid + ") reports inet is " +
-                (percentage > 50 ? "connected" : "disconnected") + " (" + percentage + ") on " +
-                "network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime();
-            mInetLog.add(s);
-            while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) {
-                mInetLog.remove(0);
-            }
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(
-            EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+        if (percentage > 50) return;  // don't handle good network reports
+        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+        if (nai != null) reportBadNetwork(nai.network);
     }
 
     public void reportBadNetwork(Network network) {
         //TODO
     }
 
-    private void handleInetConditionChange(int netType, int condition) {
-        if (mActiveDefaultNetwork == -1) {
-            if (DBG) log("handleInetConditionChange: no active default network - ignore");
-            return;
-        }
-        if (mActiveDefaultNetwork != netType) {
-            if (DBG) log("handleInetConditionChange: net=" + netType +
-                            " != default=" + mActiveDefaultNetwork + " - ignore");
-            return;
-        }
-        if (VDBG) {
-            log("handleInetConditionChange: net=" +
-                    netType + ", condition=" + condition +
-                    ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
-        }
-        mDefaultInetCondition = condition;
-        int delay;
-        if (mInetConditionChangeInFlight == false) {
-            if (VDBG) log("handleInetConditionChange: starting a change hold");
-            // setup a new hold to debounce this
-            if (mDefaultInetCondition > 50) {
-                delay = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
-            } else {
-                delay = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
-            }
-            mInetConditionChangeInFlight = true;
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END,
-                    mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
-        } else {
-            // we've set the new condition, when this hold ends that will get picked up
-            if (VDBG) log("handleInetConditionChange: currently in hold - not setting new end evt");
-        }
-    }
-
-    private void handleInetConditionHoldEnd(int netType, int sequence) {
-        if (DBG) {
-            log("handleInetConditionHoldEnd: net=" + netType +
-                    ", condition=" + mDefaultInetCondition +
-                    ", published condition=" + mDefaultInetConditionPublished);
-        }
-        mInetConditionChangeInFlight = false;
-
-        if (mActiveDefaultNetwork == -1) {
-            if (DBG) log("handleInetConditionHoldEnd: no active default network - ignoring");
-            return;
-        }
-        if (mDefaultConnectionSequence != sequence) {
-            if (DBG) log("handleInetConditionHoldEnd: event hold for obsolete network - ignoring");
-            return;
-        }
-        // TODO: Figure out why this optimization sometimes causes a
-        //       change in mDefaultInetCondition to be missed and the
-        //       UI to not be updated.
-        //if (mDefaultInetConditionPublished == mDefaultInetCondition) {
-        //    if (DBG) log("no change in condition - aborting");
-        //    return;
-        //}
-        NetworkInfo networkInfo = getNetworkInfoForType(mActiveDefaultNetwork);
-        if (networkInfo.isConnected() == false) {
-            if (DBG) log("handleInetConditionHoldEnd: default network not connected - ignoring");
-            return;
-        }
-        mDefaultInetConditionPublished = mDefaultInetCondition;
-        sendInetConditionBroadcast(networkInfo);
-        return;
-    }
-
     public ProxyInfo getProxy() {
         // this information is already available as a world read/writable jvm property
         // so this API change wouldn't have a benifit.  It also breaks the passing
@@ -4206,6 +4093,10 @@
 
     private final NetworkRequest mDefaultRequest;
 
+    private boolean isDefaultNetwork(NetworkAgentInfo nai) {
+        return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai;
+    }
+
     public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             int currentScore, NetworkMisc networkMisc) {
@@ -4532,6 +4423,7 @@
                             mLegacyTypeTracker.remove(currentNetwork.networkInfo.getType(),
                                                       currentNetwork);
                         }
+                        mDefaultInetConditionPublished = 100;
                         mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
                     }
                 }
@@ -4581,8 +4473,6 @@
                 // to connected after our normal pause unless somebody reports us as
                 // really disconnected
                 mDefaultInetConditionPublished = 0;
-                mDefaultConnectionSequence++;
-                mInetConditionChangeInFlight = false;
                 // TODO - read the tcp buffer size config string from somewhere
                 // updateNetworkSettings();
             }
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index af38664..d05c280 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -201,9 +201,6 @@
             // There are many components in the system watching for this so as to
             // adjust audio routing, screen orientation, etc.
             getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-
-            // Release the wake lock that was acquired when the message was posted.
-            mWakeLock.release();
         }
     }
 
@@ -213,6 +210,7 @@
             switch (msg.what) {
                 case MSG_DOCK_STATE_CHANGED:
                     handleDockStateChange();
+                    mWakeLock.release();
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 07ad9e4..4455901 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2200,8 +2200,7 @@
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
         systemDir.mkdirs();
-        mBatteryStatsService = new BatteryStatsService(new File(
-                systemDir, "batterystats.bin").toString(), mHandler);
+        mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
         mOnBattery = DEBUG_POWER ? true
@@ -7471,6 +7470,7 @@
         rti.firstActiveTime = tr.firstActiveTime;
         rti.lastActiveTime = tr.lastActiveTime;
         rti.affiliatedTaskId = tr.mAffiliatedTaskId;
+        rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
         return rti;
     }
 
@@ -8971,8 +8971,10 @@
     }
 
     /**
-     * Allows app to retrieve the MIME type of a URI without having permission
-     * to access its content provider.
+     * Allows apps to retrieve the MIME type of a URI.
+     * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
+     * users, then it does not need permission to access the ContentProvider.
+     * Either, it needs cross-user uri grants.
      *
      * CTS tests for this functionality can be run with "runtest cts-appsecurity".
      *
@@ -8981,12 +8983,22 @@
      */
     public String getProviderMimeType(Uri uri, int userId) {
         enforceNotIsolatedCaller("getProviderMimeType");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, false, ALLOW_NON_FULL_IN_PROFILE, "getProviderMimeType", null);
         final String name = uri.getAuthority();
-        final long ident = Binder.clearCallingIdentity();
+        int callingUid = Binder.getCallingUid();
+        int callingPid = Binder.getCallingPid();
+        long ident = 0;
+        boolean clearedIdentity = false;
+        userId = unsafeConvertIncomingUser(userId);
+        if (UserHandle.getUserId(callingUid) != userId) {
+            if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED
+                    || checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+                clearedIdentity = true;
+                ident = Binder.clearCallingIdentity();
+            }
+        }
         ContentProviderHolder holder = null;
-
         try {
             holder = getContentProviderExternalUnchecked(name, null, userId);
             if (holder != null) {
@@ -8996,10 +9008,17 @@
             Log.w(TAG, "Content provider dead retrieving " + uri, e);
             return null;
         } finally {
-            if (holder != null) {
-                removeContentProviderExternalUnchecked(name, null, userId);
+            // We need to clear the identity to call removeContentProviderExternalUnchecked
+            if (!clearedIdentity) {
+                ident = Binder.clearCallingIdentity();
             }
-            Binder.restoreCallingIdentity(ident);
+            try {
+                if (holder != null) {
+                    removeContentProviderExternalUnchecked(name, null, userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         return null;
@@ -10585,14 +10604,16 @@
     }
 
     void startAppProblemLocked(ProcessRecord app) {
-        if (app.userId == mCurrentUserId) {
-            app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
-                    mContext, app.info.packageName, app.info.flags);
-        } else {
-            // If this app is not running under the current user, then we
-            // can't give it a report button because that would require
-            // launching the report UI under a different user.
-            app.errorReportReceiver = null;
+        // If this app is not running under the current user, then we
+        // can't give it a report button because that would require
+        // launching the report UI under a different user.
+        app.errorReportReceiver = null;
+
+        for (int userId : mCurrentProfileIds) {
+            if (app.userId == userId) {
+                app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
+                        mContext, app.info.packageName, app.info.flags);
+            }
         }
         skipCurrentReceiverLocked(app);
     }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c5bc7d3..4653742 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1099,6 +1099,51 @@
         }
     }
 
+    // Find the first visible activity above the passed activity and if it is translucent return it
+    // otherwise return null;
+    ActivityRecord findNextTranslucentActivity(ActivityRecord r) {
+        TaskRecord task = r.task;
+        if (task == null) {
+            return null;
+        }
+
+        ActivityStack stack = task.stack;
+        if (stack == null) {
+            return null;
+        }
+
+        int stackNdx = mStacks.indexOf(stack);
+
+        ArrayList<TaskRecord> tasks = stack.mTaskHistory;
+        int taskNdx = tasks.indexOf(task);
+
+        ArrayList<ActivityRecord> activities = task.mActivities;
+        int activityNdx = activities.indexOf(r) + 1;
+
+        final int numStacks = mStacks.size();
+        while (stackNdx < numStacks) {
+            tasks = mStacks.get(stackNdx).mTaskHistory;
+            final int numTasks = tasks.size();
+            while (taskNdx < numTasks) {
+                activities = tasks.get(taskNdx).mActivities;
+                final int numActivities = activities.size();
+                while (activityNdx < numActivities) {
+                    final ActivityRecord activity = activities.get(activityNdx);
+                    if (!activity.finishing) {
+                        return activity.fullscreen ? null : activity;
+                    }
+                    ++activityNdx;
+                }
+                activityNdx = 0;
+                ++taskNdx;
+            }
+            taskNdx = 0;
+            ++stackNdx;
+        }
+
+        return null;
+    }
+
     // Checks if any of the stacks above this one has a fullscreen activity behind it.
     // If so, this stack is hidden, otherwise it is visible.
     private boolean isStackVisible() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b4e66c1..4a99ef3 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2680,6 +2680,13 @@
         }
 
         stack.setMediaPlayer(playing ? r : null);
+        if (!playing) {
+            // Make the activity immediately above r opaque.
+            final ActivityRecord next = stack.findNextTranslucentActivity(r);
+            if (next != null) {
+                mService.convertFromTranslucent(next.appToken);
+            }
+        }
         try {
             top.app.thread.scheduleBackgroundMediaPlayingChanged(top.appToken, playing);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 3fdeb54..d8da700 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -27,6 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -42,7 +43,10 @@
 import com.android.internal.os.PowerProfile;
 import com.android.server.LocalServices;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -62,8 +66,8 @@
     private BluetoothHeadset mBluetoothHeadset;
     PowerManagerInternal mPowerManagerInternal;
 
-    BatteryStatsService(String filename, Handler handler) {
-        mStats = new BatteryStatsImpl(filename, handler);
+    BatteryStatsService(File systemDir, Handler handler) {
+        mStats = new BatteryStatsImpl(systemDir, handler);
     }
     
     public void publish(Context context) {
@@ -117,7 +121,7 @@
     public BatteryStatsImpl getActiveStatistics() {
         return mStats;
     }
-    
+
     public byte[] getStatistics() {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
@@ -129,7 +133,24 @@
         out.recycle();
         return data;
     }
-    
+
+    public ParcelFileDescriptor getStatisticsStream() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.BATTERY_STATS, null);
+        //Slog.i("foo", "SENDING BATTERY INFO:");
+        //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
+        Parcel out = Parcel.obtain();
+        mStats.writeToParcel(out, 0);
+        byte[] data = out.marshall();
+        out.recycle();
+        try {
+            return ParcelFileDescriptor.fromData(data, "battery-stats");
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to create shared memory", e);
+            return null;
+        }
+    }
+
     public long computeBatteryTimeRemaining() {
         synchronized (mStats) {
             long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
@@ -726,7 +747,8 @@
         pw.println("  enable|disable <option>");
         pw.println("    Enable or disable a running option.  Option state is not saved across boots.");
         pw.println("    Options are:");
-        pw.println("      full-wake-history: include wake_lock_in battery history, full wake details.");
+        pw.println("      full-history: include additional detailed events in battery history:");
+        pw.println("          wake_lock_in and proc events");
         pw.println("      no-auto-reset: don't automatically reset stats when unplugged");
     }
 
@@ -737,9 +759,9 @@
             dumpHelp(pw);
             return -1;
         }
-        if ("full-wake-history".equals(args[i])) {
+        if ("full-wake-history".equals(args[i]) || "full-history".equals(args[i])) {
             synchronized (mStats) {
-                mStats.setRecordAllWakeLocksLocked(enable);
+                mStats.setRecordAllHistoryLocked(enable);
             }
         } else if ("no-auto-reset".equals(args[i])) {
             synchronized (mStats) {
@@ -764,7 +786,8 @@
         }
 
         int flags = 0;
-        boolean isCheckin = false;
+        boolean useCheckinFormat = false;
+        boolean isRealCheckin = false;
         boolean noOutput = false;
         boolean writeData = false;
         long historyStart = -1;
@@ -773,7 +796,8 @@
             for (int i=0; i<args.length; i++) {
                 String arg = args[i];
                 if ("--checkin".equals(arg)) {
-                    isCheckin = true;
+                    useCheckinFormat = true;
+                    isRealCheckin = true;
                 } else if ("--history".equals(arg)) {
                     flags |= BatteryStats.DUMP_HISTORY_ONLY;
                 } else if ("--history-start".equals(arg)) {
@@ -787,7 +811,7 @@
                     historyStart = Long.parseLong(args[i]);
                     writeData = true;
                 } else if ("-c".equals(arg)) {
-                    isCheckin = true;
+                    useCheckinFormat = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
                 } else if ("--unplugged".equals(arg)) {
                     flags |= BatteryStats.DUMP_UNPLUGGED_ONLY;
@@ -844,8 +868,35 @@
         if (noOutput) {
             return;
         }
-        if (isCheckin) {
+        if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
+            if (isRealCheckin) {
+                // For a real checkin, first we want to prefer to use the last complete checkin
+                // file if there is one.
+                synchronized (mStats.mCheckinFile) {
+                    if (mStats.mCheckinFile.exists()) {
+                        try {
+                            byte[] raw = mStats.mCheckinFile.readFully();
+                            if (raw != null) {
+                                Parcel in = Parcel.obtain();
+                                in.unmarshall(raw, 0, raw.length);
+                                in.setDataPosition(0);
+                                BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+                                        null, mStats.mHandler);
+                                checkinStats.readSummaryFromParcel(in);
+                                in.recycle();
+                                checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
+                                        historyStart);
+                                mStats.mCheckinFile.delete();
+                                return;
+                            }
+                        } catch (IOException e) {
+                            Slog.w(TAG, "Failure reading checkin file "
+                                    + mStats.mCheckinFile.getBaseFile(), e);
+                        }
+                    }
+                }
+            }
             synchronized (mStats) {
                 mStats.dumpCheckinLocked(mContext, pw, apps, flags, historyStart);
                 if (writeData) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4a84941..d0ec106 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -68,6 +68,7 @@
     private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
     private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
     private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
+    private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
     private static final String ATTR_CALLING_UID = "calling_uid";
     private static final String ATTR_CALLING_PACKAGE = "calling_package";
     private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
@@ -140,6 +141,7 @@
     CharSequence lastDescription; // Last description captured for this item.
 
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
+    int mAffiliatedTaskColor; // color of the parent task affiliation.
     TaskRecord mPrevAffiliate; // previous task in affiliated chain.
     int mPrevAffiliateTaskId = -1; // previous id for persistence.
     TaskRecord mNextAffiliate; // next task in affiliated chain.
@@ -172,7 +174,8 @@
             String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
             long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
             ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
-            int prevTaskId, int nextTaskId, int callingUid, String callingPackage) {
+            int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid,
+            String callingPackage) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
@@ -199,6 +202,7 @@
         mNeverRelinquishIdentity = neverRelinquishIdentity;
         lastTaskDescription = _lastTaskDescription;
         mAffiliatedTaskId = taskAffiliation;
+        mAffiliatedTaskColor = taskAffiliationColor;
         mPrevAffiliateTaskId = prevTaskId;
         mNextAffiliateTaskId = nextTaskId;
         mCallingUid = callingUid;
@@ -326,6 +330,7 @@
     void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
         closeRecentsChain();
         mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
+        mAffiliatedTaskColor = taskToAffiliateWith.mAffiliatedTaskColor;
         // Find the end
         while (taskToAffiliateWith.mNextAffiliate != null) {
             final TaskRecord nextRecents = taskToAffiliateWith.mNextAffiliate;
@@ -689,11 +694,14 @@
                     }
                     if (colorPrimary == 0) {
                         colorPrimary = r.taskDescription.getPrimaryColor();
-
                     }
                 }
             }
             lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
+            // Update the task affiliation color if we are the parent of the group
+            if (taskId == mAffiliatedTaskId) {
+                mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
+            }
         }
     }
 
@@ -777,6 +785,7 @@
             saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
                     LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
         }
+        out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
         out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
         out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
         out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
@@ -831,6 +840,7 @@
         final int outerDepth = in.getDepth();
         ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
         int taskAffiliation = -1;
+        int taskAffiliationColor = 0;
         int prevTaskId = -1;
         int nextTaskId = -1;
         int callingUid = -1;
@@ -877,6 +887,8 @@
                 prevTaskId = Integer.valueOf(attrValue);
             } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) {
                 nextTaskId = Integer.valueOf(attrValue);
+            } else if (ATTR_TASK_AFFILIATION_COLOR.equals(attrName)) {
+                taskAffiliationColor = Integer.valueOf(attrValue);
             } else if (ATTR_CALLING_UID.equals(attrName)) {
                 callingUid = Integer.valueOf(attrValue);
             } else if (ATTR_CALLING_PACKAGE.equals(attrName)) {
@@ -921,8 +933,8 @@
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 autoRemoveRecents, askedCompatMode, taskType, userId, lastDescription, activities,
                 firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
-                taskDescription, taskAffiliation, prevTaskId, nextTaskId, callingUid,
-                callingPackage);
+                taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
+                callingUid, callingPackage);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             activities.get(activityNdx).task = task;
diff --git a/services/core/java/com/android/server/display/ElectronBeam.java b/services/core/java/com/android/server/display/ColorFade.java
similarity index 65%
rename from services/core/java/com/android/server/display/ElectronBeam.java
rename to services/core/java/com/android/server/display/ColorFade.java
index 18e4049..920fdfb 100644
--- a/services/core/java/com/android/server/display/ElectronBeam.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,11 +16,16 @@
 
 package com.android.server.display;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.display.DisplayManagerInternal;
@@ -30,9 +35,8 @@
 import android.opengl.EGLContext;
 import android.opengl.EGLDisplay;
 import android.opengl.EGLSurface;
-import android.opengl.GLES10;
+import android.opengl.GLES20;
 import android.opengl.GLES11Ext;
-import android.os.Looper;
 import android.util.FloatMath;
 import android.util.Slog;
 import android.view.DisplayInfo;
@@ -41,10 +45,12 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
+import libcore.io.Streams;
+
 import com.android.server.LocalServices;
+import com.android.internal.R;
 
 /**
- * Bzzzoooop!  *crackle*
  * <p>
  * Animates a screen transition from on to off or off to on by applying
  * some GL transformations to a screenshot.
@@ -53,20 +59,14 @@
  * that belongs to the {@link DisplayPowerController}.
  * </p>
  */
-final class ElectronBeam {
-    private static final String TAG = "ElectronBeam";
+final class ColorFade {
+    private static final String TAG = "ColorFade";
 
     private static final boolean DEBUG = false;
 
     // The layer for the electron beam surface.
     // This is currently hardcoded to be one layer above the boot animation.
-    private static final int ELECTRON_BEAM_LAYER = 0x40000001;
-
-    // The relative proportion of the animation to spend performing
-    // the horizontal stretch effect.  The remainder is spent performing
-    // the vertical stretch effect.
-    private static final float HSTRETCH_DURATION = 0.5f;
-    private static final float VSTRETCH_DURATION = 1.0f - HSTRETCH_DURATION;
+    private static final int COLOR_FADE_LAYER = 0x40000001;
 
     // The number of frames to draw when preparing the animation so that it will
     // be ready to run smoothly.  We use 3 frames because we are triple-buffered.
@@ -98,6 +98,11 @@
     private final int[] mTexNames = new int[1];
     private boolean mTexNamesGenerated;
     private final float mTexMatrix[] = new float[16];
+    private final float mProjMatrix[] = new float[16];
+    private final int[] mGLBuffers = new int[2];
+    private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
+    private int mOpacityLoc, mScaleLoc, mGammaLoc, mSaturationLoc;
+    private int mProgram;
 
     // Vertex and corresponding texture coordinates.
     // We have 4 2D vertices, so 8 elements.  The vertices form a quad.
@@ -105,12 +110,12 @@
     private final FloatBuffer mTexCoordBuffer = createNativeFloatBuffer(8);
 
     /**
-     * Animates an electron beam warming up.
+     * Animates an color fade warming up.
      */
     public static final int MODE_WARM_UP = 0;
 
     /**
-     * Animates an electron beam shutting off.
+     * Animates an color fade shutting off.
      */
     public static final int MODE_COOL_DOWN = 1;
 
@@ -119,19 +124,19 @@
      */
     public static final int MODE_FADE = 2;
 
-    public ElectronBeam(int displayId) {
+    public ColorFade(int displayId) {
         mDisplayId = displayId;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
     }
 
     /**
-     * Warms up the electron beam in preparation for turning on or off.
+     * Warms up the color fade in preparation for turning on or off.
      * This method prepares a GL context, and captures a screen shot.
      *
      * @param mode The desired mode for the upcoming animation.
-     * @return True if the electron beam is ready, false if it is uncontrollable.
+     * @return True if the color fade is ready, false if it is uncontrollable.
      */
-    public boolean prepare(int mode) {
+    public boolean prepare(Context context, int mode) {
         if (DEBUG) {
             Slog.d(TAG, "prepare: mode=" + mode);
         }
@@ -139,18 +144,33 @@
         mMode = mode;
 
         // Get the display size and layer stack.
-        // This is not expected to change while the electron beam surface is showing.
+        // This is not expected to change while the color fade surface is showing.
         DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(mDisplayId);
         mDisplayLayerStack = displayInfo.layerStack;
         mDisplayWidth = displayInfo.getNaturalWidth();
         mDisplayHeight = displayInfo.getNaturalHeight();
 
         // Prepare the surface for drawing.
-        if (!tryPrepare()) {
+        if (!(createSurface() && createEglContext() && createEglSurface() &&
+              captureScreenshotTextureAndSetViewport())) {
             dismiss();
             return false;
         }
 
+        // Init GL
+        if (!attachEglContext()) {
+            return false;
+        }
+        try {
+            if(!initGLShaders(context) || !initGLBuffers() || checkGlErrors("prepare")) {
+                detachEglContext();
+                dismiss();
+                return false;
+            }
+        } finally {
+            detachEglContext();
+        }
+
         // Done.
         mPrepared = true;
 
@@ -169,24 +189,125 @@
         return true;
     }
 
-    private boolean tryPrepare() {
-        if (createSurface()) {
-            if (mMode == MODE_FADE) {
-                return true;
-            }
-            return createEglContext()
-                    && createEglSurface()
-                    && captureScreenshotTextureAndSetViewport();
+    private String readFile(Context context, int resourceId) {
+        try{
+            InputStream stream = context.getResources().openRawResource(resourceId);
+            return new String(Streams.readFully(new InputStreamReader(stream)));
         }
-        return false;
+        catch (IOException e) {
+            Slog.e(TAG, "Unrecognized shader " + Integer.toString(resourceId));
+            throw new RuntimeException(e);
+        }
+    }
+
+    private int loadShader(Context context, int resourceId, int type) {
+        String source = readFile(context, resourceId);
+
+        int shader = GLES20.glCreateShader(type);
+
+        GLES20.glShaderSource(shader, source);
+        GLES20.glCompileShader(shader);
+
+        int[] compiled = new int[1];
+        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+        if (compiled[0] == 0) {
+            Slog.e(TAG, "Could not compile shader " + shader + ", " + type + ":");
+            Slog.e(TAG, GLES20.glGetShaderSource(shader));
+            Slog.e(TAG, GLES20.glGetShaderInfoLog(shader));
+            GLES20.glDeleteShader(shader);
+            shader = 0;
+        }
+
+        return shader;
+    }
+
+    private boolean initGLShaders(Context context) {
+        int vshader = loadShader(context, com.android.internal.R.raw.color_fade_vert,
+                GLES20.GL_VERTEX_SHADER);
+        int fshader = loadShader(context, com.android.internal.R.raw.color_fade_frag,
+                GLES20.GL_FRAGMENT_SHADER);
+        if (vshader == 0 || fshader == 0) return false;
+
+        mProgram = GLES20.glCreateProgram();
+
+        GLES20.glAttachShader(mProgram, vshader);
+        GLES20.glAttachShader(mProgram, fshader);
+
+        GLES20.glLinkProgram(mProgram);
+
+        mVertexLoc = GLES20.glGetAttribLocation(mProgram, "position");
+        mTexCoordLoc = GLES20.glGetAttribLocation(mProgram, "uv");
+
+        mProjMatrixLoc = GLES20.glGetUniformLocation(mProgram, "proj_matrix");
+        mTexMatrixLoc = GLES20.glGetUniformLocation(mProgram, "tex_matrix");
+
+        mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
+        mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
+        mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
+        mScaleLoc = GLES20.glGetUniformLocation(mProgram, "scale");
+        mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
+
+        GLES20.glUseProgram(mProgram);
+        GLES20.glUniform1i(mTexUnitLoc, 0);
+        GLES20.glUseProgram(0);
+
+        return true;
+    }
+
+    private boolean initGLBuffers() {
+        //Fill vertices
+        setQuad(mVertexBuffer, 0, 0, mDisplayWidth, mDisplayHeight);
+
+        // Setup GL Textures
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
+
+        // Setup GL Buffers
+        GLES20.glGenBuffers(2, mGLBuffers, 0);
+
+        // fill vertex buffer
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLBuffers[0]);
+        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mVertexBuffer.capacity() * 4,
+                            mVertexBuffer, GLES20.GL_STATIC_DRAW);
+
+        // fill tex buffer
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLBuffers[1]);
+        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mTexCoordBuffer.capacity() * 4,
+                            mTexCoordBuffer, GLES20.GL_STATIC_DRAW);
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+        return true;
+    }
+
+    private static void setQuad(FloatBuffer vtx, float x, float y, float w, float h) {
+        if (DEBUG) {
+            Slog.d(TAG, "setQuad: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h);
+        }
+        vtx.put(0, x);
+        vtx.put(1, y);
+        vtx.put(2, x);
+        vtx.put(3, y + h);
+        vtx.put(4, x + w);
+        vtx.put(5, y + h);
+        vtx.put(6, x + w);
+        vtx.put(7, y);
     }
 
     /**
-     * Dismisses the electron beam animation surface and cleans up.
+     * Dismisses the color fade animation surface and cleans up.
      *
-     * To prevent stray photons from leaking out after the electron beam has been
+     * To prevent stray photons from leaking out after the color fade has been
      * turned off, it is a good idea to defer dismissing the animation until the
-     * electron beam has been turned back on fully.
+     * color fade has been turned back on fully.
      */
     public void dismiss() {
         if (DEBUG) {
@@ -200,10 +321,10 @@
     }
 
     /**
-     * Draws an animation frame showing the electron beam activated at the
+     * Draws an animation frame showing the color fade activated at the
      * specified level.
      *
-     * @param level The electron beam level.
+     * @param level The color fade level.
      * @return True if successful.
      */
     public boolean draw(float level) {
@@ -224,15 +345,18 @@
         }
         try {
             // Clear frame to solid black.
-            GLES10.glClearColor(0f, 0f, 0f, 1f);
-            GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT);
+            GLES20.glClearColor(0f, 0f, 0f, 1f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
 
             // Draw the frame.
-            if (level < HSTRETCH_DURATION) {
-                drawHStretch(1.0f - (level / HSTRETCH_DURATION));
-            } else {
-                drawVStretch(1.0f - ((level - HSTRETCH_DURATION) / VSTRETCH_DURATION));
-            }
+            float one_minus_level = 1 - level;
+            float cos = FloatMath.cos((float)Math.PI * one_minus_level);
+            float sign = cos < 0 ? -1 : 1;
+            float opacity = -FloatMath.pow(one_minus_level, 2) + 1;
+            float saturation = FloatMath.pow(level, 4);
+            float scale = (-FloatMath.pow(one_minus_level, 2) + 1) * 0.1f + 0.9f;
+            float gamma = (0.5f * sign * FloatMath.pow(cos, 2) + 0.5f) * 0.9f + 0.1f;
+            drawFaded(opacity, 1.f / gamma, saturation, scale);
             if (checkGlErrors("drawFrame")) {
                 return false;
             }
@@ -244,139 +368,59 @@
         return showSurface(1.0f);
     }
 
-    /**
-     * Draws a frame where the content of the electron beam is collapsing inwards upon
-     * itself vertically with red / green / blue channels dispersing and eventually
-     * merging down to a single horizontal line.
-     *
-     * @param stretch The stretch factor.  0.0 is no collapse, 1.0 is full collapse.
-     */
-    private void drawVStretch(float stretch) {
-        // compute interpolation scale factors for each color channel
-        final float ar = scurve(stretch, 7.5f);
-        final float ag = scurve(stretch, 8.0f);
-        final float ab = scurve(stretch, 8.5f);
+    private void drawFaded(float opacity, float gamma, float saturation, float scale) {
         if (DEBUG) {
-            Slog.d(TAG, "drawVStretch: stretch=" + stretch
-                    + ", ar=" + ar + ", ag=" + ag + ", ab=" + ab);
+            Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
+                        ", saturation=" + saturation + ", scale=" + scale);
         }
+        // Use shaders
+        GLES20.glUseProgram(mProgram);
 
-        // set blending
-        GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE);
-        GLES10.glEnable(GLES10.GL_BLEND);
+        // Set Uniforms
+        GLES20.glUniformMatrix4fv(mProjMatrixLoc, 1, false, mProjMatrix, 0);
+        GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, mTexMatrix, 0);
+        GLES20.glUniform1f(mOpacityLoc, opacity);
+        GLES20.glUniform1f(mGammaLoc, gamma);
+        GLES20.glUniform1f(mSaturationLoc, saturation);
+        GLES20.glUniform1f(mScaleLoc, scale);
 
-        // bind vertex buffer
-        GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
-        GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
+        // Use textures
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]);
 
-        // set-up texturing
-        GLES10.glDisable(GLES10.GL_TEXTURE_2D);
-        GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+        // draw the plane
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLBuffers[0]);
+        GLES20.glEnableVertexAttribArray(mVertexLoc);
+        GLES20.glVertexAttribPointer(mVertexLoc, 2, GLES20.GL_FLOAT, false, 0, 0);
 
-        // bind texture and set blending for drawing planes
-        GLES10.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]);
-        GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE,
-                mMode == MODE_WARM_UP ? GLES10.GL_MODULATE : GLES10.GL_REPLACE);
-        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
-                GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR);
-        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
-                GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR);
-        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
-                GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE);
-        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
-                GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE);
-        GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
-        GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer);
-        GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mGLBuffers[1]);
+        GLES20.glEnableVertexAttribArray(mTexCoordLoc);
+        GLES20.glVertexAttribPointer(mTexCoordLoc, 2, GLES20.GL_FLOAT, false, 0, 0);
 
-        // draw the red plane
-        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ar);
-        GLES10.glColorMask(true, false, false, true);
-        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
-
-        // draw the green plane
-        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag);
-        GLES10.glColorMask(false, true, false, true);
-        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
-
-        // draw the blue plane
-        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ab);
-        GLES10.glColorMask(false, false, true, true);
-        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
-
-        // clean up after drawing planes
-        GLES10.glDisable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
-        GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
-        GLES10.glColorMask(true, true, true, true);
-
-        // draw the white highlight (we use the last vertices)
-        if (mMode == MODE_COOL_DOWN) {
-            GLES10.glColor4f(ag, ag, ag, 1.0f);
-            GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
-        }
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);
 
         // clean up
-        GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY);
-        GLES10.glDisable(GLES10.GL_BLEND);
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
     }
 
-    /**
-     * Draws a frame where the electron beam has been stretched out into
-     * a thin white horizontal line that fades as it collapses inwards.
-     *
-     * @param stretch The stretch factor.  0.0 is maximum stretch / no fade,
-     * 1.0 is collapsed / maximum fade.
-     */
-    private void drawHStretch(float stretch) {
-        // compute interpolation scale factor
-        final float ag = scurve(stretch, 8.0f);
-        if (DEBUG) {
-            Slog.d(TAG, "drawHStretch: stretch=" + stretch + ", ag=" + ag);
-        }
-
-        if (stretch < 1.0f) {
-            // bind vertex buffer
-            GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
-            GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
-
-            // draw narrow fading white line
-            setHStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag);
-            GLES10.glColor4f(1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f);
-            GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
-
-            // clean up
-            GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY);
-        }
-    }
-
-    private static void setVStretchQuad(FloatBuffer vtx, float dw, float dh, float a) {
-        final float w = dw + (dw * a);
-        final float h = dh - (dh * a);
-        final float x = (dw - w) * 0.5f;
-        final float y = (dh - h) * 0.5f;
-        setQuad(vtx, x, y, w, h);
-    }
-
-    private static void setHStretchQuad(FloatBuffer vtx, float dw, float dh, float a) {
-        final float w = 2 * dw * (1.0f - a);
-        final float h = 1.0f;
-        final float x = (dw - w) * 0.5f;
-        final float y = (dh - h) * 0.5f;
-        setQuad(vtx, x, y, w, h);
-    }
-
-    private static void setQuad(FloatBuffer vtx, float x, float y, float w, float h) {
-        if (DEBUG) {
-            Slog.d(TAG, "setQuad: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h);
-        }
-        vtx.put(0, x);
-        vtx.put(1, y);
-        vtx.put(2, x);
-        vtx.put(3, y + h);
-        vtx.put(4, x + w);
-        vtx.put(5, y + h);
-        vtx.put(6, x + w);
-        vtx.put(7, y);
+    private void ortho(float left, float right, float bottom, float top, float znear, float zfar) {
+        mProjMatrix[0] = 2f / (right - left);
+        mProjMatrix[1] = 0;
+        mProjMatrix[2] = 0;
+        mProjMatrix[3] = 0;
+        mProjMatrix[4] = 0;
+        mProjMatrix[5] = 2f / (top - bottom);
+        mProjMatrix[6] = 0;
+        mProjMatrix[7] = 0;
+        mProjMatrix[8] = 0;
+        mProjMatrix[9] = 0;
+        mProjMatrix[10] = -2f / (zfar - znear);
+        mProjMatrix[11] = 0;
+        mProjMatrix[12] = -(right + left) / (right - left);
+        mProjMatrix[13] = -(top + bottom) / (top - bottom);
+        mProjMatrix[14] = -(zfar + znear) / (zfar - znear);
+        mProjMatrix[15] = 1f;
     }
 
     private boolean captureScreenshotTextureAndSetViewport() {
@@ -385,7 +429,7 @@
         }
         try {
             if (!mTexNamesGenerated) {
-                GLES10.glGenTextures(1, mTexNames, 0);
+                GLES20.glGenTextures(1, mTexNames, 0);
                 if (checkGlErrors("glGenTextures")) {
                     return false;
                 }
@@ -413,15 +457,8 @@
             mTexCoordBuffer.put(6, 1f); mTexCoordBuffer.put(7, 0f);
 
             // Set up our viewport.
-            GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
-            GLES10.glMatrixMode(GLES10.GL_PROJECTION);
-            GLES10.glLoadIdentity();
-            GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1);
-            GLES10.glMatrixMode(GLES10.GL_MODELVIEW);
-            GLES10.glLoadIdentity();
-            GLES10.glMatrixMode(GLES10.GL_TEXTURE);
-            GLES10.glLoadIdentity();
-            GLES10.glLoadMatrixf(mTexMatrix, 0);
+            GLES20.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
+            ortho(0, mDisplayWidth, 0, mDisplayHeight, -1, 1);
         } finally {
             detachEglContext();
         }
@@ -433,7 +470,7 @@
             mTexNamesGenerated = false;
             if (attachEglContext()) {
                 try {
-                    GLES10.glDeleteTextures(1, mTexNames, 0);
+                    GLES20.glDeleteTextures(1, mTexNames, 0);
                     checkGlErrors("glDeleteTextures");
                 } finally {
                     detachEglContext();
@@ -460,6 +497,8 @@
 
         if (mEglConfig == null) {
             int[] eglConfigAttribList = new int[] {
+                    EGL14.EGL_RENDERABLE_TYPE,
+                    EGL14.EGL_OPENGL_ES2_BIT,
                     EGL14.EGL_RED_SIZE, 8,
                     EGL14.EGL_GREEN_SIZE, 8,
                     EGL14.EGL_BLUE_SIZE, 8,
@@ -478,6 +517,7 @@
 
         if (mEglContext == null) {
             int[] eglContextAttribList = new int[] {
+                    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                     EGL14.EGL_NONE
             };
             mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig,
@@ -490,16 +530,6 @@
         return true;
     }
 
-    /* not used because it is too expensive to create / destroy contexts all of the time
-    private void destroyEglContext() {
-        if (mEglContext != null) {
-            if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) {
-                logEglError("eglDestroyContext");
-            }
-            mEglContext = null;
-        }
-    }*/
-
     private boolean createSurface() {
         if (mSurfaceSession == null) {
             mSurfaceSession = new SurfaceSession();
@@ -516,7 +546,7 @@
                         flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN;
                     }
                     mSurfaceControl = new SurfaceControl(mSurfaceSession,
-                            "ElectronBeam", mDisplayWidth, mDisplayHeight,
+                            "ColorFade", mDisplayWidth, mDisplayHeight,
                             PixelFormat.OPAQUE, flags);
                 } catch (OutOfResourcesException ex) {
                     Slog.e(TAG, "Unable to create surface.", ex);
@@ -584,7 +614,7 @@
         if (!mSurfaceVisible || mSurfaceAlpha != alpha) {
             SurfaceControl.openTransaction();
             try {
-                mSurfaceControl.setLayer(ELECTRON_BEAM_LAYER);
+                mSurfaceControl.setLayer(COLOR_FADE_LAYER);
                 mSurfaceControl.setAlpha(alpha);
                 mSurfaceControl.show();
             } finally {
@@ -614,34 +644,6 @@
         }
     }
 
-    /**
-     * Interpolates a value in the range 0 .. 1 along a sigmoid curve
-     * yielding a result in the range 0 .. 1 scaled such that:
-     * scurve(0) == 0, scurve(0.5) == 0.5, scurve(1) == 1.
-     */
-    private static float scurve(float value, float s) {
-        // A basic sigmoid has the form y = 1.0f / FloatMap.exp(-x * s).
-        // Here we take the input datum and shift it by 0.5 so that the
-        // domain spans the range -0.5 .. 0.5 instead of 0 .. 1.
-        final float x = value - 0.5f;
-
-        // Next apply the sigmoid function to the scaled value
-        // which produces a value in the range 0 .. 1 so we subtract
-        // 0.5 to get a value in the range -0.5 .. 0.5 instead.
-        final float y = sigmoid(x, s) - 0.5f;
-
-        // To obtain the desired boundary conditions we need to scale
-        // the result so that it fills a range of -1 .. 1.
-        final float v = sigmoid(0.5f, s) - 0.5f;
-
-        // And finally remap the value back to a range of 0 .. 1.
-        return y / v * 0.5f + 0.5f;
-    }
-
-    private static float sigmoid(float x, float s) {
-        return 1.0f / (1.0f + FloatMath.exp(-x * s));
-    }
-
     private static FloatBuffer createNativeFloatBuffer(int size) {
         ByteBuffer bb = ByteBuffer.allocateDirect(size * 4);
         bb.order(ByteOrder.nativeOrder());
@@ -659,7 +661,7 @@
     private static boolean checkGlErrors(String func, boolean log) {
         boolean hadError = false;
         int error;
-        while ((error = GLES10.glGetError()) != GLES10.GL_NO_ERROR) {
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
             if (log) {
                 Slog.e(TAG, func + " failed: error " + error, new Throwable());
             }
@@ -670,7 +672,7 @@
 
     public void dump(PrintWriter pw) {
         pw.println();
-        pw.println("Electron Beam State:");
+        pw.println("Color Fade State:");
         pw.println("  mPrepared=" + mPrepared);
         pw.println("  mMode=" + mMode);
         pw.println("  mDisplayLayerStack=" + mDisplayLayerStack);
@@ -685,7 +687,7 @@
      * Updates the position and transformation of the matrix whenever the display
      * is rotated.  This is a little tricky because the display transaction
      * callback can be invoked on any thread, not necessarily the thread that
-     * owns the electron beam.
+     * owns the color fade.
      */
     private static final class NaturalSurfaceLayout implements DisplayTransactionListener {
         private final DisplayManagerInternal mDisplayManagerInternal;
@@ -725,7 +727,8 @@
                         mSurfaceControl.setMatrix(0, -1, 1, 0);
                         break;
                     case Surface.ROTATION_180:
-                        mSurfaceControl.setPosition(displayInfo.logicalWidth, displayInfo.logicalHeight);
+                        mSurfaceControl.setPosition(displayInfo.logicalWidth,
+                                displayInfo.logicalHeight);
                         mSurfaceControl.setMatrix(-1, 0, 0, -1);
                         break;
                     case Surface.ROTATION_270:
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2dd150a..e31f177 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1269,28 +1269,26 @@
                         + "greater than 0");
             }
 
+            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+            }
+            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
+                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+            }
+
             if (projection != null) {
                 try {
                     if (!getProjectionService().isValidMediaProjection(projection)) {
                         throw new SecurityException("Invalid media projection");
                     }
+                    flags = projection.applyVirtualDisplayFlags(flags);
                 } catch (RemoteException e) {
-                    throw new SecurityException("unable to validate media projection");
-                }
-                flags &= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
-                try {
-                    flags |= projection.getVirtualDisplayFlags();
-                } catch (RemoteException e) {
-                    throw new RuntimeException("unable to retrieve media projection flags");
+                    throw new SecurityException("unable to validate media projection or flags");
                 }
             }
 
-            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) != 0) {
-                if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0 ||
-                        (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
-                    throw new IllegalArgumentException("screen sharing virtual displays must not "
-                            + "be public or presentation displays");
-                }
+            if (callingUid != Process.SYSTEM_UID &&
+                    (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
                 if (!canProjectVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
                             + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
@@ -1298,16 +1296,6 @@
                             + "display.");
                 }
             }
-
-
-            if (callingUid != Process.SYSTEM_UID &&
-                    (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
-                if (!canProjectVideo(projection)) {
-                    throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
-                            + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
-                            + "MediaProjection token to create a public virtual display.");
-                }
-            }
             if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                 if (!canProjectSecureVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d0e4b33..9a67321 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -64,7 +64,7 @@
  * blocker as long as the display is not ready.  So most of the work done here
  * does not need to worry about holding a suspend blocker unless it happens
  * independently of the display ready signal.
- *
+   *
  * For debugging, you can make the electron beam and brightness animations run
  * slower by changing the "animator duration scale" option in Development Settings.
  */
@@ -78,14 +78,14 @@
     // We might want to turn this off if we cannot get a guarantee that the screen
     // actually turns on and starts showing new content after the call to set the
     // screen state returns.  Playing the animation can also be somewhat slow.
-    private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false;
+    private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
 
 
     // The minimum reduction in brightness when dimmed.
     private static final int SCREEN_DIM_MINIMUM_REDUCTION = 10;
 
-    private static final int ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS = 250;
-    private static final int ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS = 400;
+    private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
+    private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 600;
 
     private static final int MSG_UPDATE_POWER_STATE = 1;
     private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
@@ -107,6 +107,8 @@
 
     private final Object mLock = new Object();
 
+    private final Context mContext;
+
     // Our handler.
     private final DisplayControllerHandler mHandler;
 
@@ -146,7 +148,7 @@
 
     // True if we should fade the screen while turning it off, false if we should play
     // a stylish electron beam animation instead.
-    private boolean mElectronBeamFadesConfig;
+    private boolean mColorFadeFadesConfig;
 
     // The pending power request.
     // Initially null until the first call to requestPowerState.
@@ -223,8 +225,8 @@
     private AutomaticBrightnessController mAutomaticBrightnessController;
 
     // Animators.
-    private ObjectAnimator mElectronBeamOnAnimator;
-    private ObjectAnimator mElectronBeamOffAnimator;
+    private ObjectAnimator mColorFadeOnAnimator;
+    private ObjectAnimator mColorFadeOffAnimator;
     private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
 
     /**
@@ -240,6 +242,7 @@
         mLights = LocalServices.getService(LightsManager.class);
         mSensorManager = sensorManager;
         mBlanker = blanker;
+        mContext = context;
 
         final Resources resources = context.getResources();
 
@@ -287,7 +290,7 @@
 
         mScreenBrightnessRangeMinimum = screenBrightnessRangeMinimum;
 
-        mElectronBeamFadesConfig = resources.getBoolean(
+        mColorFadeFadesConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_animateScreenLights);
 
         if (!DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
@@ -378,17 +381,17 @@
         // In the future, we might manage multiple displays independently.
         mPowerState = new DisplayPowerState(mBlanker,
                 mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT),
-                new ElectronBeam(Display.DEFAULT_DISPLAY));
+                new ColorFade(Display.DEFAULT_DISPLAY));
 
-        mElectronBeamOnAnimator = ObjectAnimator.ofFloat(
-                mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 0.0f, 1.0f);
-        mElectronBeamOnAnimator.setDuration(ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS);
-        mElectronBeamOnAnimator.addListener(mAnimatorListener);
+        mColorFadeOnAnimator = ObjectAnimator.ofFloat(
+                mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f);
+        mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS);
+        mColorFadeOnAnimator.addListener(mAnimatorListener);
 
-        mElectronBeamOffAnimator = ObjectAnimator.ofFloat(
-                mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 1.0f, 0.0f);
-        mElectronBeamOffAnimator.setDuration(ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS);
-        mElectronBeamOffAnimator.addListener(mAnimatorListener);
+        mColorFadeOffAnimator = ObjectAnimator.ofFloat(
+                mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f);
+        mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS);
+        mColorFadeOffAnimator.addListener(mAnimatorListener);
 
         mScreenBrightnessRampAnimator = new RampAnimator<DisplayPowerState>(
                 mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS);
@@ -600,34 +603,34 @@
             // Wait for previous off animation to complete beforehand.
             // It is relatively short but if we cancel it and switch to the
             // on animation immediately then the results are pretty ugly.
-            if (!mElectronBeamOffAnimator.isStarted()) {
+            if (!mColorFadeOffAnimator.isStarted()) {
                 // Turn the screen on.  The contents of the screen may not yet
                 // be visible if the electron beam has not been dismissed because
                 // its last frame of animation is solid black.
                 setScreenState(Display.STATE_ON);
                 if (mPowerRequest.blockScreenOn
-                        && mPowerState.getElectronBeamLevel() == 0.0f) {
+                        && mPowerState.getColorFadeLevel() == 0.0f) {
                     blockScreenOn();
                 } else {
                     unblockScreenOn();
-                    if (USE_ELECTRON_BEAM_ON_ANIMATION && mPowerRequest.isBrightOrDim()) {
+                    if (USE_COLOR_FADE_ON_ANIMATION && mPowerRequest.isBrightOrDim()) {
                         // Perform screen on animation.
-                        if (!mElectronBeamOnAnimator.isStarted()) {
-                            if (mPowerState.getElectronBeamLevel() == 1.0f) {
-                                mPowerState.dismissElectronBeam();
-                            } else if (mPowerState.prepareElectronBeam(
-                                    mElectronBeamFadesConfig ?
-                                            ElectronBeam.MODE_FADE :
-                                                    ElectronBeam.MODE_WARM_UP)) {
-                                mElectronBeamOnAnimator.start();
+                        if (!mColorFadeOnAnimator.isStarted()) {
+                            if (mPowerState.getColorFadeLevel() == 1.0f) {
+                                mPowerState.dismissColorFade();
+                            } else if (mPowerState.prepareColorFade(mContext,
+                                    mColorFadeFadesConfig ?
+                                            ColorFade.MODE_FADE :
+                                                    ColorFade.MODE_WARM_UP)) {
+                                mColorFadeOnAnimator.start();
                             } else {
-                                mElectronBeamOnAnimator.end();
+                                mColorFadeOnAnimator.end();
                             }
                         }
                     } else {
                         // Skip screen on animation.
-                        mPowerState.setElectronBeamLevel(1.0f);
-                        mPowerState.dismissElectronBeam();
+                        mPowerState.setColorFadeLevel(1.0f);
+                        mPowerState.dismissColorFade();
                     }
                 }
             }
@@ -640,8 +643,8 @@
                     || mPowerState.getScreenState() != Display.STATE_ON) {
                 // Set screen state and dismiss the black surface without fanfare.
                 setScreenState(state);
-                mPowerState.setElectronBeamLevel(1.0f);
-                mPowerState.dismissElectronBeam();
+                mPowerState.setColorFadeLevel(1.0f);
+                mPowerState.dismissColorFade();
             }
         } else if (state == Display.STATE_DOZE_SUSPEND) {
             // Want screen dozing and suspended.
@@ -652,27 +655,27 @@
                     || mPowerState.getScreenState() == Display.STATE_DOZE_SUSPEND) {
                 // Set screen state and dismiss the black surface without fanfare.
                 setScreenState(state);
-                mPowerState.setElectronBeamLevel(1.0f);
-                mPowerState.dismissElectronBeam();
+                mPowerState.setColorFadeLevel(1.0f);
+                mPowerState.dismissColorFade();
             }
         } else {
             // Want screen off.
             // Wait for previous on animation to complete beforehand.
             unblockScreenOn();
-            if (!mElectronBeamOnAnimator.isStarted()) {
+            if (!mColorFadeOnAnimator.isStarted()) {
                 if (mPowerRequest.policy == DisplayPowerRequest.POLICY_OFF) {
                     // Perform screen off animation.
-                    if (!mElectronBeamOffAnimator.isStarted()) {
-                        if (mPowerState.getElectronBeamLevel() == 0.0f) {
+                    if (!mColorFadeOffAnimator.isStarted()) {
+                        if (mPowerState.getColorFadeLevel() == 0.0f) {
                             setScreenState(Display.STATE_OFF);
-                        } else if (mPowerState.prepareElectronBeam(
-                                mElectronBeamFadesConfig ?
-                                        ElectronBeam.MODE_FADE :
-                                                ElectronBeam.MODE_COOL_DOWN)
+                        } else if (mPowerState.prepareColorFade(mContext,
+                                mColorFadeFadesConfig ?
+                                        ColorFade.MODE_FADE :
+                                                ColorFade.MODE_COOL_DOWN)
                                 && mPowerState.getScreenState() != Display.STATE_OFF) {
-                            mElectronBeamOffAnimator.start();
+                            mColorFadeOffAnimator.start();
                         } else {
-                            mElectronBeamOffAnimator.end();
+                            mColorFadeOffAnimator.end();
                         }
                     }
                 } else {
@@ -687,8 +690,8 @@
         // which will be handled asynchronously.
         if (mustNotify
                 && !mScreenOnWasBlocked
-                && !mElectronBeamOnAnimator.isStarted()
-                && !mElectronBeamOffAnimator.isStarted()
+                && !mColorFadeOnAnimator.isStarted()
+                && !mColorFadeOffAnimator.isStarted()
                 && !mScreenBrightnessRampAnimator.isAnimating()
                 && mPowerState.waitUntilClean(mCleanListener)) {
             synchronized (mLock) {
@@ -936,13 +939,13 @@
         pw.println("  mScreenBrightnessRampAnimator.isAnimating()=" +
                 mScreenBrightnessRampAnimator.isAnimating());
 
-        if (mElectronBeamOnAnimator != null) {
-            pw.println("  mElectronBeamOnAnimator.isStarted()=" +
-                    mElectronBeamOnAnimator.isStarted());
+        if (mColorFadeOnAnimator != null) {
+            pw.println("  mColorFadeOnAnimator.isStarted()=" +
+                    mColorFadeOnAnimator.isStarted());
         }
-        if (mElectronBeamOffAnimator != null) {
-            pw.println("  mElectronBeamOffAnimator.isStarted()=" +
-                    mElectronBeamOffAnimator.isStarted());
+        if (mColorFadeOffAnimator != null) {
+            pw.println("  mColorFadeOffAnimator.isStarted()=" +
+                    mColorFadeOffAnimator.isStarted());
         }
 
         if (mPowerState != null) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 4821e74..6522b89 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -18,6 +18,7 @@
 
 import com.android.server.lights.Light;
 
+import android.content.Context;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
@@ -56,7 +57,7 @@
     private final Choreographer mChoreographer;
     private final DisplayBlanker mBlanker;
     private final Light mBacklight;
-    private final ElectronBeam mElectronBeam;
+    private final ColorFade mColorFade;
     private final PhotonicModulator mPhotonicModulator;
 
     private int mScreenState;
@@ -64,19 +65,19 @@
     private boolean mScreenReady;
     private boolean mScreenUpdatePending;
 
-    private boolean mElectronBeamPrepared;
-    private float mElectronBeamLevel;
-    private boolean mElectronBeamReady;
-    private boolean mElectronBeamDrawPending;
+    private boolean mColorFadePrepared;
+    private float mColorFadeLevel;
+    private boolean mColorFadeReady;
+    private boolean mColorFadeDrawPending;
 
     private Runnable mCleanListener;
 
-    public DisplayPowerState(DisplayBlanker blanker, Light backlight, ElectronBeam electronBeam) {
+    public DisplayPowerState(DisplayBlanker blanker, Light backlight, ColorFade electronBeam) {
         mHandler = new Handler(true /*async*/);
         mChoreographer = Choreographer.getInstance();
         mBlanker = blanker;
         mBacklight = backlight;
-        mElectronBeam = electronBeam;
+        mColorFade = electronBeam;
         mPhotonicModulator = new PhotonicModulator();
 
         // At boot time, we know that the screen is on and the electron beam
@@ -89,21 +90,21 @@
         mScreenBrightness = PowerManager.BRIGHTNESS_ON;
         scheduleScreenUpdate();
 
-        mElectronBeamPrepared = false;
-        mElectronBeamLevel = 1.0f;
-        mElectronBeamReady = true;
+        mColorFadePrepared = false;
+        mColorFadeLevel = 1.0f;
+        mColorFadeReady = true;
     }
 
-    public static final FloatProperty<DisplayPowerState> ELECTRON_BEAM_LEVEL =
+    public static final FloatProperty<DisplayPowerState> COLOR_FADE_LEVEL =
             new FloatProperty<DisplayPowerState>("electronBeamLevel") {
         @Override
         public void setValue(DisplayPowerState object, float value) {
-            object.setElectronBeamLevel(value);
+            object.setColorFadeLevel(value);
         }
 
         @Override
         public Float get(DisplayPowerState object) {
-            return object.getElectronBeamLevel();
+            return object.getColorFadeLevel();
         }
     };
 
@@ -176,26 +177,26 @@
      * @param mode The electron beam animation mode to prepare.
      * @return True if the electron beam was prepared.
      */
-    public boolean prepareElectronBeam(int mode) {
-        if (!mElectronBeam.prepare(mode)) {
-            mElectronBeamPrepared = false;
-            mElectronBeamReady = true;
+    public boolean prepareColorFade(Context context, int mode) {
+        if (!mColorFade.prepare(context, mode)) {
+            mColorFadePrepared = false;
+            mColorFadeReady = true;
             return false;
         }
 
-        mElectronBeamPrepared = true;
-        mElectronBeamReady = false;
-        scheduleElectronBeamDraw();
+        mColorFadePrepared = true;
+        mColorFadeReady = false;
+        scheduleColorFadeDraw();
         return true;
     }
 
     /**
      * Dismisses the electron beam surface.
      */
-    public void dismissElectronBeam() {
-        mElectronBeam.dismiss();
-        mElectronBeamPrepared = false;
-        mElectronBeamReady = true;
+    public void dismissColorFade() {
+        mColorFade.dismiss();
+        mColorFadePrepared = false;
+        mColorFadeReady = true;
     }
 
     /**
@@ -211,20 +212,20 @@
      *
      * @param level The level, ranges from 0.0 (full off) to 1.0 (full on).
      */
-    public void setElectronBeamLevel(float level) {
-        if (mElectronBeamLevel != level) {
+    public void setColorFadeLevel(float level) {
+        if (mColorFadeLevel != level) {
             if (DEBUG) {
-                Slog.d(TAG, "setElectronBeamLevel: level=" + level);
+                Slog.d(TAG, "setColorFadeLevel: level=" + level);
             }
 
-            mElectronBeamLevel = level;
+            mColorFadeLevel = level;
             if (mScreenState != Display.STATE_OFF) {
                 mScreenReady = false;
                 scheduleScreenUpdate(); // update backlight brightness
             }
-            if (mElectronBeamPrepared) {
-                mElectronBeamReady = false;
-                scheduleElectronBeamDraw();
+            if (mColorFadePrepared) {
+                mColorFadeReady = false;
+                scheduleColorFadeDraw();
             }
         }
     }
@@ -232,8 +233,8 @@
     /**
      * Gets the level of the electron beam steering current.
      */
-    public float getElectronBeamLevel() {
-        return mElectronBeamLevel;
+    public float getColorFadeLevel() {
+        return mColorFadeLevel;
     }
 
     /**
@@ -243,7 +244,7 @@
      * The listener always overrides any previously set listener.
      */
     public boolean waitUntilClean(Runnable listener) {
-        if (!mScreenReady || !mElectronBeamReady) {
+        if (!mScreenReady || !mColorFadeReady) {
             mCleanListener = listener;
             return false;
         } else {
@@ -259,13 +260,13 @@
         pw.println("  mScreenBrightness=" + mScreenBrightness);
         pw.println("  mScreenReady=" + mScreenReady);
         pw.println("  mScreenUpdatePending=" + mScreenUpdatePending);
-        pw.println("  mElectronBeamPrepared=" + mElectronBeamPrepared);
-        pw.println("  mElectronBeamLevel=" + mElectronBeamLevel);
-        pw.println("  mElectronBeamReady=" + mElectronBeamReady);
-        pw.println("  mElectronBeamDrawPending=" + mElectronBeamDrawPending);
+        pw.println("  mColorFadePrepared=" + mColorFadePrepared);
+        pw.println("  mColorFadeLevel=" + mColorFadeLevel);
+        pw.println("  mColorFadeReady=" + mColorFadeReady);
+        pw.println("  mColorFadeDrawPending=" + mColorFadeDrawPending);
 
         mPhotonicModulator.dump(pw);
-        mElectronBeam.dump(pw);
+        mColorFade.dump(pw);
     }
 
     private void scheduleScreenUpdate() {
@@ -280,17 +281,17 @@
         mHandler.post(mScreenUpdateRunnable);
     }
 
-    private void scheduleElectronBeamDraw() {
-        if (!mElectronBeamDrawPending) {
-            mElectronBeamDrawPending = true;
+    private void scheduleColorFadeDraw() {
+        if (!mColorFadeDrawPending) {
+            mColorFadeDrawPending = true;
             mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
-                    mElectronBeamDrawRunnable, null);
+                    mColorFadeDrawRunnable, null);
         }
     }
 
     private void invokeCleanListenerIfNeeded() {
         final Runnable listener = mCleanListener;
-        if (listener != null && mScreenReady && mElectronBeamReady) {
+        if (listener != null && mScreenReady && mColorFadeReady) {
             mCleanListener = null;
             listener.run();
         }
@@ -302,7 +303,7 @@
             mScreenUpdatePending = false;
 
             int brightness = mScreenState != Display.STATE_OFF
-                    && mElectronBeamLevel > 0f ? mScreenBrightness : 0;
+                    && mColorFadeLevel > 0f ? mScreenBrightness : 0;
             if (mPhotonicModulator.setState(mScreenState, brightness)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Screen ready");
@@ -317,16 +318,16 @@
         }
     };
 
-    private final Runnable mElectronBeamDrawRunnable = new Runnable() {
+    private final Runnable mColorFadeDrawRunnable = new Runnable() {
         @Override
         public void run() {
-            mElectronBeamDrawPending = false;
+            mColorFadeDrawPending = false;
 
-            if (mElectronBeamPrepared) {
-                mElectronBeam.draw(mElectronBeamLevel);
+            if (mColorFadePrepared) {
+                mColorFade.draw(mColorFadeLevel);
             }
 
-            mElectronBeamReady = true;
+            mColorFadeReady = true;
             invokeCleanListenerIfNeeded();
         }
     };
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 0ebd2de..72ac29a 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -262,14 +262,15 @@
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame
                 mInfo.flags = 0;
                 if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
-                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
-                    if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) == 0) {
-                        mInfo.flags |=  DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY
-                                | DisplayDeviceInfo.FLAG_NEVER_BLANK;
-                    }
-                } else if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
+                            | DisplayDeviceInfo.FLAG_NEVER_BLANK;
+                }
+                if ((mInfo.flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+                    mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
+                } else {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
                 }
+
                 if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
                 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index fb4fa7f..ad5b2ba 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -617,6 +617,7 @@
                         }
 
                         addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
+                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
 
                         // If there is AVR, initiate System Audio Auto initiation action,
                         // which turns on and off system audio according to last system
@@ -1300,6 +1301,7 @@
         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
         removeAction(DeviceDiscoveryAction.class);
         removeAction(HotplugDetectionAction.class);
+        removeAction(PowerStatusMonitorAction.class);
         // Remove recording actions.
         removeAction(OneTouchRecordAction.class);
         removeAction(TimerRecordingAction.class);
@@ -1521,4 +1523,22 @@
             }
         });
     }
+
+    void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+        HdmiDeviceInfo info = getDeviceInfo(logicalAddress);
+        if (info == null) {
+            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
+            return;
+        }
+
+        if (info.getDevicePowerStatus() == newPowerStatus) {
+            return;
+        }
+
+        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
+        // addDeviceInfo replaces old device info with new one if exists.
+        addDeviceInfo(newInfo);
+
+        // TODO: notify this update to others.
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index cccc44c..81b99f0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -317,7 +317,10 @@
                     if (logicalAddress == Constants.ADDR_UNREGISTERED) {
                         Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
                     } else {
-                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
+                        // Set POWER_STATUS_ON to all local devices because they share lifetime
+                        // with system.
+                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
+                                HdmiControlManager.POWER_STATUS_ON);
                         localDevice.setDeviceInfo(deviceInfo);
                         mCecController.addLocalDevice(deviceType, localDevice);
                         mCecController.addLogicalAddress(logicalAddress);
@@ -653,7 +656,7 @@
         }
     }
 
-    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
+    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
         // TODO: find better name instead of model name.
         String displayName = Build.MODEL;
         return new HdmiDeviceInfo(logicalAddress,
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index a52e0d2..23f19ff 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -266,4 +266,14 @@
         }
         return true;
     }
+
+    /**
+     * Clone {@link HdmiDeviceInfo} with new power status.
+     */
+    static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) {
+        return new HdmiDeviceInfo(info.getLogicalAddress(),
+                info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
+                info.getVendorId(), info.getDisplayName(), newPowerStatus);
+    }
+
 }
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
new file mode 100644
index 0000000..03fbb95
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.util.SparseIntArray;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+import java.util.List;
+
+/**
+ * Action that check each device's power status.
+ */
+public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
+    private static final String TAG = "PowerStatusMonitorAction";
+
+    // State that waits for <Report Power Status> once sending <Give Device Power Status>
+    // to all external devices.
+    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
+    // State that waits for next monitoring
+    private static final int STATE_WAIT_FOR_NEXT_MONITORING = 2;
+
+    private static final int INVALID_POWER_STATUS = POWER_STATUS_UNKNOWN - 1;
+
+    // Monitoring interval (60s)
+    private static final int MONITIROING_INTERNAL_MS = 60000;
+
+    // Timeout once sending <Give Device Power Status>
+    private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
+
+    // Container for current power status of all external devices.
+    // The key is a logical address a device and the value is current power status of it
+    // Whenever the action receives <Report Power Status> from a device,
+    // it removes an entry of the given device.
+    // If this is non-empty when timeout for STATE_WAIT_FOR_REPORT_POWER_STATUS happens,
+    // updates power status of all remaining devices into POWER_STATUS_UNKNOWN.
+    private final SparseIntArray mPowerStatus = new SparseIntArray();
+
+    PowerStatusMonitorAction(HdmiCecLocalDevice source) {
+        super(source);
+    }
+
+    @Override
+    boolean start() {
+        queryPowerStatus();
+        return true;
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAIT_FOR_REPORT_POWER_STATUS) {
+            return false;
+        }
+        return handleReportPowerStatus(cmd);
+    }
+
+    private boolean handleReportPowerStatus(HdmiCecMessage cmd) {
+        int sourceAddress = cmd.getSource();
+        int oldStatus = mPowerStatus.get(sourceAddress, INVALID_POWER_STATUS);
+        if (oldStatus == INVALID_POWER_STATUS) {
+            // if no device exists for incoming message, hands it over to other actions.
+            return false;
+        }
+        int newStatus = cmd.getParams()[0];
+        updatePowerStatus(sourceAddress, newStatus, true);
+        return true;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        switch (mState) {
+            case STATE_WAIT_FOR_NEXT_MONITORING:
+                queryPowerStatus();
+                break;
+            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
+                handleTimeout();
+                break;
+        }
+    }
+
+    private void handleTimeout() {
+        for (int i = 0; i < mPowerStatus.size(); ++i) {
+            int logicalAddress = mPowerStatus.keyAt(i);
+            updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, false);
+        }
+        mPowerStatus.clear();
+        mState = STATE_WAIT_FOR_NEXT_MONITORING;
+    }
+
+    private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) {
+        mPowerStatus.clear();
+        for (HdmiDeviceInfo info : deviceInfos) {
+            mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+        }
+    }
+
+    private void queryPowerStatus() {
+        List<HdmiDeviceInfo> deviceInfos = tv().getDeviceInfoList(false);
+        resetPowerStatus(deviceInfos);
+        for (HdmiDeviceInfo info : deviceInfos) {
+            final int logicalAddress = info.getLogicalAddress();
+            sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+                    logicalAddress),
+                    new SendMessageCallback() {
+                        @Override
+                        public void onSendCompleted(int error) {
+                            // If fails to send <Give Device Power Status>,
+                            // update power status into UNKNOWN.
+                            if (error != Constants.SEND_RESULT_SUCCESS) {
+                               updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+                            }
+                        }
+                    });
+        }
+
+        mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
+
+        // Add both timers, monitoring and timeout.
+        addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITIROING_INTERNAL_MS);
+        addTimer(STATE_WAIT_FOR_REPORT_POWER_STATUS, REPORT_POWER_STATUS_TIMEOUT_MS);
+    }
+
+    private void updatePowerStatus(int logicalAddress, int newStatus, boolean remove) {
+        tv().updateDevicePowerStatus(logicalAddress, newStatus);
+
+        if (remove) {
+            mPowerStatus.delete(logicalAddress);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 7b28699..289b5aa 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -103,7 +103,7 @@
             try {
                 hasPermission |= checkPermission(packageName,
                         android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
-                        || mAppOps.checkOpNoThrow(
+                        || mAppOps.noteOpNoThrow(
                                 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
                         == AppOpsManager.MODE_ALLOWED;
             } finally {
@@ -196,18 +196,27 @@
         }
 
         @Override // Binder call
-        public int getVirtualDisplayFlags() {
-            switch (mType) {
-                case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
-                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE;
-                case MediaProjectionManager.TYPE_MIRRORING:
-                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
-                            DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
-                case MediaProjectionManager.TYPE_PRESENTATION:
-                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
-                            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+        public int applyVirtualDisplayFlags(int flags) {
+            if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
+                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+                return flags;
+            } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
+                flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+                return flags;
+            } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
+                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+                return flags;
+            } else  {
+                throw new RuntimeException("Unknown MediaProjection type");
             }
-            throw new RuntimeException("Unknown MediaProjection type");
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b4faea1..c7e3fb7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -37,15 +37,17 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
-import android.content.pm.IPackageDeleteObserver;
+import android.content.Intent;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -199,6 +201,10 @@
         }
     }
 
+    public static boolean isStageFile(File file) {
+        return sStageFilter.accept(null, file.getName());
+    }
+
     @Deprecated
     public File allocateSessionDir() throws IOException {
         synchronized (mSessions) {
@@ -545,8 +551,21 @@
             int userId) {
         mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
 
-        // TODO: enforce installer of record or permission
-        mPm.deletePackage(packageName, observer, userId, flags);
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
+                == PackageManager.PERMISSION_GRANTED) {
+            // Sweet, call straight through!
+            mPm.deletePackage(packageName, observer, userId, flags);
+
+        } else {
+            // Take a short detour to confirm with user
+            final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
+            intent.setData(Uri.fromParts("package", packageName, null));
+            intent.putExtra(PackageInstaller.EXTRA_CALLBACK, observer.asBinder());
+            try {
+                observer.onUserActionRequired(intent);
+            } catch (RemoteException ignored) {
+            }
+        }
     }
 
     @Override
@@ -559,6 +578,15 @@
     }
 
     @Override
+    public void setPermissionsResult(int sessionId, boolean accepted) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
+
+        synchronized (mSessions) {
+            mSessions.get(sessionId).setPermissionsResult(accepted);
+        }
+    }
+
+    @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback");
         enforceCallerCanReadSessions();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5443fbc..a3184f0 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
@@ -30,6 +31,7 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ApkLite;
@@ -106,10 +108,24 @@
     @GuardedBy("mLock")
     private boolean mSealed = false;
     @GuardedBy("mLock")
-    private boolean mPermissionsConfirmed = false;
+    private boolean mPermissionsAccepted = false;
     @GuardedBy("mLock")
     private boolean mDestroyed = false;
 
+    private int mFinalStatus;
+    private String mFinalMessage;
+
+    /**
+     * Path to the resolved base APK for this session, which may point at an APK
+     * inside the session (when the session defines the base), or it may point
+     * at the existing base APK (when adding splits to an existing app).
+     * <p>
+     * This is used when confirming permissions, since we can't fully stage the
+     * session inside an ASEC before confirming with user.
+     */
+    @GuardedBy("mLock")
+    private String mResolvedBaseCodePath;
+
     @GuardedBy("mLock")
     private ArrayList<FileBridge> mBridges = new ArrayList<>();
 
@@ -134,12 +150,7 @@
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Install failed: " + e);
                     destroyInternal();
-                    try {
-                        mRemoteObserver.onPackageInstalled(mPackageName, e.error, e.getMessage(),
-                                null);
-                    } catch (RemoteException ignored) {
-                    }
-                    mCallback.onSessionFinished(PackageInstallerSession.this, false);
+                    dispatchSessionFinished(e.error, e.getMessage(), null);
                 }
 
                 return true;
@@ -169,9 +180,9 @@
 
         if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES,
                 installerPackageName) == PackageManager.PERMISSION_GRANTED) {
-            mPermissionsConfirmed = true;
+            mPermissionsAccepted = true;
         } else {
-            mPermissionsConfirmed = false;
+            mPermissionsAccepted = false;
         }
 
         computeProgressLocked();
@@ -182,7 +193,9 @@
 
         info.sessionId = sessionId;
         info.installerPackageName = installerPackageName;
+        info.resolvedBaseCodePath = mResolvedBaseCodePath;
         info.progress = mProgress;
+        info.sealed = mSealed;
         info.open = openCount.get() > 0;
 
         info.mode = params.mode;
@@ -355,11 +368,19 @@
 
         Preconditions.checkNotNull(mPackageName);
         Preconditions.checkNotNull(mSignatures);
+        Preconditions.checkNotNull(mResolvedBaseCodePath);
 
-        if (!mPermissionsConfirmed) {
-            // TODO: async confirm permissions with user
-            // when they confirm, we'll kick off another install() pass
-            throw new SecurityException("Caller must hold INSTALL permission");
+        if (!mPermissionsAccepted) {
+            // User needs to accept permissions; give installer an intent they
+            // can use to involve user.
+            final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
+            intent.setPackage("com.android.packageinstaller");
+            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+            try {
+                mRemoteObserver.onUserActionRequired(intent);
+            } catch (RemoteException ignored) {
+            }
+            return;
         }
 
         // Inherit any packages and native libraries from existing install that
@@ -386,12 +407,7 @@
             public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                     Bundle extras) {
                 destroyInternal();
-                try {
-                    remoteObserver.onPackageInstalled(basePackageName, returnCode, msg, extras);
-                } catch (RemoteException ignored) {
-                }
-                final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
-                mCallback.onSessionFinished(PackageInstallerSession.this, success);
+                dispatchSessionFinished(returnCode, msg, extras);
             }
         };
 
@@ -409,6 +425,7 @@
         mPackageName = null;
         mVersionCode = -1;
         mSignatures = null;
+        mResolvedBaseCodePath = null;
 
         final File[] files = sessionStageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -445,18 +462,25 @@
                     info.signatures);
 
             // Take this opportunity to enforce uniform naming
-            final String name;
+            final String targetName;
             if (info.splitName == null) {
-                name = "base.apk";
+                targetName = "base.apk";
             } else {
-                name = "split_" + info.splitName + ".apk";
+                targetName = "split_" + info.splitName + ".apk";
             }
-            if (!FileUtils.isValidExtFilename(name)) {
+            if (!FileUtils.isValidExtFilename(targetName)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                        "Invalid filename: " + name);
+                        "Invalid filename: " + targetName);
             }
-            if (!file.getName().equals(name)) {
-                file.renameTo(new File(file.getParentFile(), name));
+
+            final File targetFile = new File(sessionStageDir, targetName);
+            if (!file.equals(targetFile)) {
+                file.renameTo(targetFile);
+            }
+
+            // Base is coming from session
+            if (info.splitName == null) {
+                mResolvedBaseCodePath = targetFile.getAbsolutePath();
             }
         }
 
@@ -472,13 +496,18 @@
             }
 
         } else {
-            // Partial installs must be consistent with existing install.
+            // Partial installs must be consistent with existing install
             final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
             if (app == null) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Missing existing base package for " + mPackageName);
             }
 
+            // Base might be inherited from existing install
+            if (mResolvedBaseCodePath == null) {
+                mResolvedBaseCodePath = app.getBaseCodePath();
+            }
+
             final ApkLite info;
             try {
                 info = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
@@ -537,6 +566,21 @@
         if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage");
     }
 
+    void setPermissionsResult(boolean accepted) {
+        if (!mSealed) {
+            throw new SecurityException("Must be sealed to accept permissions");
+        }
+
+        if (accepted) {
+            // Mark and kick off another install pass
+            mPermissionsAccepted = true;
+            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
+        } else {
+            destroyInternal();
+            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
+        }
+    }
+
     @Override
     public void close() {
         if (openCount.decrementAndGet() == 0) {
@@ -546,11 +590,23 @@
 
     @Override
     public void abandon() {
-        try {
-            destroyInternal();
-        } finally {
-            mCallback.onSessionFinished(this, false);
+        destroyInternal();
+        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
+    }
+
+    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
+        mFinalStatus = returnCode;
+        mFinalMessage = msg;
+
+        if (mRemoteObserver != null) {
+            try {
+                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
+            } catch (RemoteException ignored) {
+            }
         }
+
+        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
+        mCallback.onSessionFinished(this, success);
     }
 
     private void destroyInternal() {
@@ -578,9 +634,11 @@
         pw.printPair("mClientProgress", mClientProgress);
         pw.printPair("mProgress", mProgress);
         pw.printPair("mSealed", mSealed);
-        pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed);
+        pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
         pw.printPair("mDestroyed", mDestroyed);
         pw.printPair("mBridges", mBridges.size());
+        pw.printPair("mFinalStatus", mFinalStatus);
+        pw.printPair("mFinalMessage", mFinalMessage);
         pw.println();
 
         pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4bf6636..36dec3e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -85,7 +85,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
-import android.app.PackageDeleteObserver;
 import android.app.admin.IDevicePolicyManager;
 import android.app.backup.IBackupManager;
 import android.content.BroadcastReceiver;
@@ -115,6 +114,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageParser.ActivityIntentInfo;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
@@ -1402,7 +1402,9 @@
 
             boolean didDexOptLibraryOrTool = false;
 
-            final List<String> instructionSets = getAllInstructionSets();
+            final List<String> allInstructionSets = getAllInstructionSets();
+            final String[] dexCodeInstructionSets =
+                getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));
 
             /**
              * Ensure all external libraries have had dexopt run on them.
@@ -1412,7 +1414,7 @@
                 // (and framework jars) into all available architectures. It's possible
                 // to compile them only when we come across an app that uses them (there's
                 // already logic for that in scanPackageLI) but that adds some complexity.
-                for (String instructionSet : instructionSets) {
+                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                     for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
                         final String lib = libEntry.path;
                         if (lib == null) {
@@ -1421,16 +1423,16 @@
 
                         try {
                             byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
-                                                                                 instructionSet,
+                                                                                 dexCodeInstructionSet,
                                                                                  false);
                             if (dexoptRequired != DexFile.UP_TO_DATE) {
                                 alreadyDexOpted.add(lib);
 
                                 // The list of "shared libraries" we have at this point is
                                 if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
-                                    mInstaller.dexopt(lib, Process.SYSTEM_UID, true, instructionSet);
+                                    mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                                 } else {
-                                    mInstaller.patchoat(lib, Process.SYSTEM_UID, true, instructionSet);
+                                    mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                                 }
                                 didDexOptLibraryOrTool = true;
                             }
@@ -1465,7 +1467,7 @@
                 // TODO: We could compile these only for the most preferred ABI. We should
                 // first double check that the dex files for these commands are not referenced
                 // by other system apps.
-                for (String instructionSet : instructionSets) {
+                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                     for (int i=0; i<frameworkFiles.length; i++) {
                         File libPath = new File(frameworkDir, frameworkFiles[i]);
                         String path = libPath.getPath();
@@ -1479,13 +1481,13 @@
                         }
                         try {
                             byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
-                                                                                 instructionSet,
+                                                                                 dexCodeInstructionSet,
                                                                                  false);
                             if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
-                                mInstaller.dexopt(path, Process.SYSTEM_UID, true, instructionSet);
+                                mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                                 didDexOptLibraryOrTool = true;
                             } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) {
-                                mInstaller.patchoat(path, Process.SYSTEM_UID, true, instructionSet);
+                                mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                                 didDexOptLibraryOrTool = true;
                             }
                         } catch (FileNotFoundException e) {
@@ -1509,8 +1511,8 @@
                 // small maintenance release update that the library and tool
                 // jars may be unchanged but APK could be removed resulting in
                 // unused dalvik-cache files.
-                for (String instructionSet : instructionSets) {
-                    mInstaller.pruneDexCache(instructionSet);
+                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+                    mInstaller.pruneDexCache(dexCodeInstructionSet);
                 }
 
                 // Additionally, delete all dex files from the root directory
@@ -4084,7 +4086,8 @@
         }
 
         for (File file : files) {
-            final boolean isPackage = isApkFile(file) || file.isDirectory();
+            final boolean isPackage = (isApkFile(file) || file.isDirectory())
+                    && !PackageInstallerService.isStageFile(file);
             if (!isPackage) {
                 // Ignore entries which are not apk's
                 continue;
@@ -4631,12 +4634,13 @@
         // 1.) we need to dexopt, either because we are forced or it is needed
         // 2.) we are defering a needed dexopt
         // 3.) we are skipping an unneeded dexopt
-        for (String path : paths) {
-            for (String instructionSet : instructionSets) {
-                if (!forceDex && pkg.mDexOptPerformed.contains(instructionSet)) {
-                    continue;
-                }
+        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+            if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+                continue;
+            }
 
+            for (String path : paths) {
                 try {
                     // This will return DEXOPT_NEEDED if we either cannot find any odex file for this
                     // patckage or the one we find does not match the image checksum (i.e. it was
@@ -4644,38 +4648,36 @@
                     // odex file and it matches the checksum of the image but not its base address,
                     // meaning we need to move it.
                     final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
-                            pkg.packageName, instructionSet, defer);
+                            pkg.packageName, dexCodeInstructionSet, defer);
                     if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
                         Log.i(TAG, "Running dexopt on: " + path + " pkg="
-                                + pkg.applicationInfo.packageName + " isa=" + instructionSet);
+                                + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet);
                         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                         final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
-                                pkg.packageName, instructionSet);
+                                pkg.packageName, dexCodeInstructionSet);
 
                         if (ret < 0) {
                             // Don't bother running dexopt again if we failed, it will probably
                             // just result in an error again. Also, don't bother dexopting for other
                             // paths & ISAs.
                             return DEX_OPT_FAILED;
-                        } else {
-                            performedDexOpt = true;
-                            pkg.mDexOptPerformed.add(instructionSet);
                         }
+
+                        performedDexOpt = true;
                     } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
                         Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
                         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                         final int ret = mInstaller.patchoat(path, sharedGid, !isForwardLocked(pkg),
-                                pkg.packageName, instructionSet);
+                                pkg.packageName, dexCodeInstructionSet);
 
                         if (ret < 0) {
                             // Don't bother running patchoat again if we failed, it will probably
                             // just result in an error again. Also, don't bother dexopting for other
                             // paths & ISAs.
                             return DEX_OPT_FAILED;
-                        } else {
-                            performedDexOpt = true;
-                            pkg.mDexOptPerformed.add(instructionSet);
                         }
+
+                        performedDexOpt = true;
                     }
 
                     // We're deciding to defer a needed dexopt. Don't bother dexopting for other
@@ -4702,6 +4704,13 @@
                     return DEX_OPT_FAILED;
                 }
             }
+
+            // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
+            // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
+            // it isn't required. We therefore mark that this package doesn't need dexopt unless
+            // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
+            // it.
+            pkg.mDexOptPerformed.add(dexCodeInstructionSet);
         }
 
         // If we've gotten here, we're sure that no error occurred and that we haven't
@@ -4763,6 +4772,23 @@
         return allInstructionSets;
     }
 
+    /**
+     * Returns the instruction set that should be used to compile dex code. In the presence of
+     * a native bridge this might be different than the one shared libraries use.
+     */
+    private static String getDexCodeInstructionSet(String sharedLibraryIsa) {
+        String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa);
+        return (dexCodeIsa.isEmpty() ? sharedLibraryIsa : dexCodeIsa);
+    }
+
+    private static String[] getDexCodeInstructionSets(String[] instructionSets) {
+        HashSet<String> dexCodeInstructionSets = new HashSet<String>(instructionSets.length);
+        for (String instructionSet : instructionSets) {
+            dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet));
+        }
+        return dexCodeInstructionSets.toArray(new String[dexCodeInstructionSets.size()]);
+    }
+
     @Override
     public void forceDexOpt(String packageName) {
         enforceSystemOrRoot("forceDexOpt");
@@ -6182,7 +6208,8 @@
                             ps.pkg.applicationInfo.primaryCpuAbi = null;
                             return;
                         } else {
-                            mInstaller.rmdex(ps.codePathString, getPreferredInstructionSet());
+                            mInstaller.rmdex(ps.codePathString,
+                                             getDexCodeInstructionSet(getPreferredInstructionSet()));
                         }
                     }
                 }
@@ -9411,10 +9438,10 @@
                 if (instructionSets == null) {
                     throw new IllegalStateException("instructionSet == null");
                 }
-
+                String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
                 for (String codePath : allCodePaths) {
-                    for (String instructionSet : instructionSets) {
-                        int retCode = mInstaller.rmdex(codePath, instructionSet);
+                    for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+                        int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet);
                         if (retCode < 0) {
                             Slog.w(TAG, "Couldn't remove dex file for package: "
                                     + " at location " + codePath + ", retcode=" + retCode);
@@ -9694,8 +9721,9 @@
             if (instructionSets == null) {
                 throw new IllegalStateException("instructionSet == null");
             }
-            for (String instructionSet : instructionSets) {
-                int retCode = mInstaller.rmdex(sourceFile, instructionSet);
+            String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+                int retCode = mInstaller.rmdex(sourceFile, dexCodeInstructionSet);
                 if (retCode < 0) {
                     Slog.w(TAG, "Couldn't remove dex file for package: "
                             + " at location "
@@ -10198,9 +10226,10 @@
         // TODO: extend to move split APK dex files
         if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
             final String[] instructionSets = getAppDexInstructionSets(newPackage.applicationInfo);
-            for (String instructionSet : instructionSets) {
+            String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                 int retCode = mInstaller.movedex(oldCodePath, newPackage.baseCodePath,
-                        instructionSet);
+                        dexCodeInstructionSet);
                 if (retCode != 0) {
                 /*
                  * Programs may be lazily run through dexopt, so the
@@ -10211,8 +10240,8 @@
                  * file from a previous version of the package.
                  */
                     newPackage.mDexOptPerformed.clear();
-                    mInstaller.rmdex(oldCodePath, instructionSet);
-                    mInstaller.rmdex(newPackage.baseCodePath, instructionSet);
+                    mInstaller.rmdex(oldCodePath, dexCodeInstructionSet);
+                    mInstaller.rmdex(newPackage.baseCodePath, dexCodeInstructionSet);
                 }
             }
         }
@@ -11359,9 +11388,9 @@
         // not just the first level.
         // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not
         // just the primary.
+        String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps));
         int res = mInstaller.getSizeInfo(packageName, userHandle, p.baseCodePath, libDirRoot,
-                publicSrcDir, asecPath, getAppDexInstructionSets(ps),
-                pStats);
+                publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
         if (res < 0) {
             return false;
         }
@@ -13450,20 +13479,4 @@
             return false;
         }
     }
-
-    private static class LegacyPackageDeleteObserver extends PackageDeleteObserver {
-        private final IPackageDeleteObserver mLegacy;
-
-        public LegacyPackageDeleteObserver(IPackageDeleteObserver legacy) {
-            mLegacy = legacy;
-        }
-
-        @Override
-        public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
-            try {
-                mLegacy.packageDeleted(basePackageName, returnCode);
-            } catch (RemoteException ignored) {
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index ee20b3c..0523af7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -93,7 +93,6 @@
                     mTrusted = true;
                     mMessage = (CharSequence) msg.obj;
                     boolean initiatedByUser = msg.arg1 != 0;
-                    // TODO: Handle initiatedByUser.
                     long durationMs = msg.getData().getLong(DATA_DURATION);
                     if (durationMs > 0) {
                         mHandler.removeMessages(MSG_TRUST_TIMEOUT);
@@ -102,7 +101,7 @@
                     mTrustManagerService.mArchive.logGrantTrust(mUserId, mName,
                             (mMessage != null ? mMessage.toString() : null),
                             durationMs, initiatedByUser);
-                    mTrustManagerService.updateTrust(mUserId);
+                    mTrustManagerService.updateTrust(mUserId, initiatedByUser);
                     break;
                 case MSG_TRUST_TIMEOUT:
                     if (DEBUG) Slog.v(TAG, "Trust timed out : " + mName.flattenToShortString());
@@ -115,7 +114,7 @@
                     if (msg.what == MSG_REVOKE_TRUST) {
                         mTrustManagerService.mArchive.logRevokeTrust(mUserId, mName);
                     }
-                    mTrustManagerService.updateTrust(mUserId);
+                    mTrustManagerService.updateTrust(mUserId, false);
                     break;
                 case MSG_RESTART_TIMEOUT:
                     unbind();
@@ -130,7 +129,7 @@
                             if (DEBUG) Log.v(TAG, "Re-enabling agent because it acknowledged "
                                     + "enabled features: " + mName);
                             mTrustDisabledByDpm = false;
-                            mTrustManagerService.updateTrust(mUserId);
+                            mTrustManagerService.updateTrust(mUserId, false);
                         }
                     } else {
                         if (DEBUG) Log.w(TAG, "Ignoring MSG_SET_TRUST_AGENT_FEATURES_COMPLETED "
@@ -144,7 +143,7 @@
                         mMessage = null;
                     }
                     mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust);
-                    mTrustManagerService.updateTrust(mUserId);
+                    mTrustManagerService.updateTrust(mUserId, false);
                     break;
             }
         }
@@ -282,7 +281,7 @@
         }
         if (mTrustDisabledByDpm != trustDisabled) {
             mTrustDisabledByDpm = trustDisabled;
-            mTrustManagerService.updateTrust(mUserId);
+            mTrustManagerService.updateTrust(mUserId, false);
         }
         return trustDisabled;
     }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index d3b8d5d..badead6 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -148,13 +148,13 @@
     private void updateTrustAll() {
         List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
         for (UserInfo userInfo : userInfos) {
-            updateTrust(userInfo.id);
+            updateTrust(userInfo.id, false);
         }
     }
 
-    public void updateTrust(int userId) {
+    public void updateTrust(int userId, boolean initiatedByUser) {
         dispatchOnTrustManagedChanged(aggregateIsTrustManaged(userId), userId);
-        dispatchOnTrustChanged(aggregateIsTrusted(userId), userId);
+        dispatchOnTrustChanged(aggregateIsTrusted(userId), userId, initiatedByUser);
     }
 
     void refreshAgentList() {
@@ -272,7 +272,7 @@
             }
         }
         if (trustMayHaveChanged) {
-            updateTrust(userId);
+            updateTrust(userId, false);
         }
         refreshAgentList();
     }
@@ -375,7 +375,7 @@
 
         if (successful && !mUserHasAuthenticatedSinceBoot.get(userId)) {
             mUserHasAuthenticatedSinceBoot.put(userId, true);
-            updateTrust(userId);
+            updateTrust(userId, false);
         }
     }
 
@@ -386,7 +386,7 @@
             updateTrustAll();
         } else {
             mUserHasAuthenticatedSinceBoot.put(userId, false);
-            updateTrust(userId);
+            updateTrust(userId, false);
         }
     }
 
@@ -410,10 +410,11 @@
         }
     }
 
-    private void dispatchOnTrustChanged(boolean enabled, int userId) {
+    private void dispatchOnTrustChanged(boolean enabled, int userId, boolean initiatedByUser) {
+        if (!enabled) initiatedByUser = false;
         for (int i = 0; i < mTrustListeners.size(); i++) {
             try {
-                mTrustListeners.get(i).onTrustChanged(enabled, userId);
+                mTrustListeners.get(i).onTrustChanged(enabled, userId, initiatedByUser);
             } catch (DeadObjectException e) {
                 Slog.d(TAG, "Removing dead TrustListener.");
                 mTrustListeners.remove(i);
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 74f725f..71f43b4 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -37,13 +37,12 @@
 import android.media.AudioPortConfig;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
-import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvContract;
+import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvStreamConfig;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -59,12 +58,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * A helper class for TvInputManagerService to handle TV input hardware.
@@ -82,11 +79,11 @@
     private final TvInputHal mHal = new TvInputHal(this);
     private final SparseArray<Connection> mConnections = new SparseArray<>();
     private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
-    private List<HdmiDeviceInfo> mHdmiCecDeviceList = new LinkedList<>();
+    private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>();
     /* A map from a device ID to the matching TV input ID. */
     private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
     /* A map from a HDMI logical address to the matching TV input ID. */
-    private final SparseArray<String> mHdmiCecInputIdMap = new SparseArray<>();
+    private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
     private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
 
     private final AudioManager mAudioManager;
@@ -119,7 +116,7 @@
                 try {
                     mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
                     mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
-                    mHdmiCecDeviceList.addAll(mHdmiControlService.getInputDevices());
+                    mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices());
                     mHdmiControlService.setInputChangeListener(mHdmiInputChangeListener);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
@@ -163,12 +160,11 @@
             buildHardwareListLocked();
             TvInputHardwareInfo info = connection.getHardwareInfoLocked();
             if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
-                // Remove HDMI CEC devices linked with this hardware.
-                for (Iterator<HdmiDeviceInfo> it = mHdmiCecDeviceList.iterator();
-                        it.hasNext(); ) {
+                // Remove HDMI devices linked with this hardware.
+                for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
                     HdmiDeviceInfo deviceInfo = it.next();
                     if (deviceInfo.getPortId() == info.getHdmiPortId()) {
-                        mHandler.obtainMessage(ListenerHandler.HDMI_CEC_DEVICE_REMOVED, 0, 0,
+                        mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
                                 deviceInfo).sendToTarget();
                         it.remove();
                     }
@@ -220,9 +216,9 @@
         }
     }
 
-    public List<HdmiDeviceInfo> getHdmiCecInputDeviceList() {
+    public List<HdmiDeviceInfo> getHdmiDeviceList() {
         synchronized (mLock) {
-            return Collections.unmodifiableList(mHdmiCecDeviceList);
+            return Collections.unmodifiableList(mHdmiDeviceList);
         }
     }
 
@@ -283,7 +279,7 @@
         return -1;
     }
 
-    public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) {
+    public void addHdmiTvInput(int logicalAddress, TvInputInfo info) {
         if (info.getType() != TvInputInfo.TYPE_HDMI) {
             throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
         }
@@ -293,13 +289,13 @@
             if (parentIndex < 0) {
                 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
             }
-            String oldInputId = mHdmiCecInputIdMap.get(logicalAddress);
+            String oldInputId = mHdmiInputIdMap.get(logicalAddress);
             if (oldInputId != null) {
                 Slog.w(TAG, "Trying to override previous registration: old = "
                         + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = "
                         + info + ":" + logicalAddress);
             }
-            mHdmiCecInputIdMap.put(logicalAddress, info.getId());
+            mHdmiInputIdMap.put(logicalAddress, info.getId());
             mInputMap.put(info.getId(), info);
         }
     }
@@ -311,9 +307,9 @@
             if (hardwareIndex >= 0) {
                 mHardwareInputIdMap.removeAt(hardwareIndex);
             }
-            int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId);
-            if (cecIndex >= 0) {
-                mHdmiCecInputIdMap.removeAt(cecIndex);
+            int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
+            if (deviceIndex >= 0) {
+                mHdmiInputIdMap.removeAt(deviceIndex);
             }
         }
     }
@@ -864,16 +860,16 @@
         public void onStateChanged(String inputId, int state);
         public void onHardwareDeviceAdded(TvInputHardwareInfo info);
         public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
-        public void onHdmiCecDeviceAdded(HdmiDeviceInfo cecDevice);
-        public void onHdmiCecDeviceRemoved(HdmiDeviceInfo cecDevice);
+        public void onHdmiDeviceAdded(HdmiDeviceInfo device);
+        public void onHdmiDeviceRemoved(HdmiDeviceInfo device);
     }
 
     private class ListenerHandler extends Handler {
         private static final int STATE_CHANGED = 1;
         private static final int HARDWARE_DEVICE_ADDED = 2;
         private static final int HARDWARE_DEVICE_REMOVED = 3;
-        private static final int HDMI_CEC_DEVICE_ADDED = 4;
-        private static final int HDMI_CEC_DEVICE_REMOVED = 5;
+        private static final int HDMI_DEVICE_ADDED = 4;
+        private static final int HDMI_DEVICE_REMOVED = 5;
 
         @Override
         public final void handleMessage(Message msg) {
@@ -894,14 +890,14 @@
                     mListener.onHardwareDeviceRemoved(info);
                     break;
                 }
-                case HDMI_CEC_DEVICE_ADDED: {
+                case HDMI_DEVICE_ADDED: {
                     HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
-                    mListener.onHdmiCecDeviceAdded(info);
+                    mListener.onHdmiDeviceAdded(info);
                     break;
                 }
-                case HDMI_CEC_DEVICE_REMOVED: {
+                case HDMI_DEVICE_REMOVED: {
                     HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
-                    mListener.onHdmiCecDeviceRemoved(info);
+                    mListener.onHdmiDeviceRemoved(info);
                     break;
                 }
                 default: {
@@ -939,21 +935,21 @@
         public void onStatusChanged(HdmiDeviceInfo deviceInfo, boolean activated) {
             synchronized (mLock) {
                 if (activated) {
-                    if (!mHdmiCecDeviceList.contains(deviceInfo)) {
-                        mHdmiCecDeviceList.add(deviceInfo);
+                    if (!mHdmiDeviceList.contains(deviceInfo)) {
+                        mHdmiDeviceList.add(deviceInfo);
                     } else {
                         Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
                         return;
                     }
                 } else {
-                    if (!mHdmiCecDeviceList.remove(deviceInfo)) {
+                    if (!mHdmiDeviceList.remove(deviceInfo)) {
                         Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
                         return;
                     }
                 }
                 Message msg = mHandler.obtainMessage(
-                        activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED
-                        : ListenerHandler.HDMI_CEC_DEVICE_REMOVED,
+                        activated ? ListenerHandler.HDMI_DEVICE_ADDED
+                        : ListenerHandler.HDMI_DEVICE_REMOVED,
                         0, 0, deviceInfo);
                 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
                     msg.sendToTarget();
@@ -970,7 +966,7 @@
             String inputId;
             synchronized (mLock) {
                 if (device.isCecDevice()) {
-                    inputId = mHdmiCecInputIdMap.get(device.getLogicalAddress());
+                    inputId = mHdmiInputIdMap.get(device.getLogicalAddress());
                 } else {
                     TvInputHardwareInfo hardwareInfo =
                             findHardwareInfoForHdmiPortLocked(device.getPortId());
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 38e4997..c5e30d5 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -35,7 +35,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -283,7 +282,6 @@
         userState.inputMap.clear();
         userState.inputMap = inputMap;
 
-        Resources r = Resources.getSystem();
         userState.ratingSystemXmlUriSet.clear();
         userState.ratingSystemXmlUriSet.add(TvContentRating.SYSTEM_CONTENT_RATING_SYSTEM_XML);
         for (TvInputState state : userState.inputMap.values()) {
@@ -1074,39 +1072,50 @@
             try {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    if (sessionToken == userState.mainSessionToken) {
+                    if (userState.mainSessionToken == sessionToken) {
                         return;
                     }
 
-                    SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
-                            resolvedUserId);
-                    ServiceState serviceState = getServiceStateLocked(
-                            sessionState.mInfo.getComponent(), resolvedUserId);
-                    ITvInputSession session = getSessionLocked(sessionState);
+                    SessionState newMainSessionState = getSessionStateLocked(
+                            sessionToken, callingUid, resolvedUserId);
+                    if (newMainSessionState.mHardwareSessionToken != null) {
+                        newMainSessionState = getSessionStateLocked(
+                                newMainSessionState.mHardwareSessionToken,
+                                Process.SYSTEM_UID, resolvedUserId);
+                    }
+                    ServiceState newMainServiceState = getServiceStateLocked(
+                            newMainSessionState.mInfo.getComponent(), resolvedUserId);
+                    ITvInputSession newMainSession = getSessionLocked(newMainSessionState);
 
-                    ServiceState prevMainServiceState = null;
-                    ITvInputSession prevMainSession = null;
+                    ServiceState oldMainServiceState = null;
+                    ITvInputSession oldMainSession = null;
                     if (userState.mainSessionToken != null) {
-                        SessionState prevMainSessionState = getSessionStateLocked(
+                        SessionState oldMainSessionState = getSessionStateLocked(
                                 userState.mainSessionToken, Process.SYSTEM_UID, resolvedUserId);
-                        prevMainServiceState = getServiceStateLocked(
-                                prevMainSessionState.mInfo.getComponent(), resolvedUserId);
-                        prevMainSession = getSessionLocked(prevMainSessionState);
+                        if (oldMainSessionState.mHardwareSessionToken != null) {
+                            oldMainSessionState = getSessionStateLocked(
+                                    oldMainSessionState.mHardwareSessionToken,
+                                    Process.SYSTEM_UID, resolvedUserId);
+                        }
+                        oldMainServiceState = getServiceStateLocked(
+                                oldMainSessionState.mInfo.getComponent(), resolvedUserId);
+                        oldMainSession = getSessionLocked(oldMainSessionState);
                     }
 
                     userState.mainSessionToken = sessionToken;
 
-                    // Inform the new main session first. See {@link TvInputService#onSetMain}.
-                    if (serviceState.mIsHardware) {
+                    // Inform the new main session first.
+                    // See {@link TvInputService#onSetMainSession}.
+                    if (newMainServiceState.mIsHardware) {
                         try {
-                            session.setMainSession(true);
+                            newMainSession.setMainSession(true);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "error in setMainSession", e);
                         }
                     }
-                    if (prevMainSession != null && prevMainServiceState.mIsHardware) {
+                    if (oldMainSession != null && oldMainServiceState.mIsHardware) {
                         try {
-                            prevMainSession.setMainSession(false);
+                            oldMainSession.setMainSession(false);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "error in setMainSession", e);
                         }
@@ -1251,6 +1260,7 @@
                         args.arg1 = sessionState.mLogUri;
                         args.arg2 = ContentUris.parseId(channelUri);
                         args.arg3 = currentTime;
+                        args.arg4 = sessionState;
                         mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in tune", e);
@@ -1672,6 +1682,7 @@
                     }
                     pw.decreaseIndent();
 
+                    pw.println("mainSessionToken: " + userState.mainSessionToken);
                     pw.decreaseIndent();
                 }
             }
@@ -1922,13 +1933,13 @@
                         }
                     }
 
-                    List<HdmiDeviceInfo> cecDeviceInfoList =
-                            mTvInputHardwareManager.getHdmiCecInputDeviceList();
-                    for (HdmiDeviceInfo cecDeviceInfo : cecDeviceInfoList) {
+                    List<HdmiDeviceInfo> deviceInfoList =
+                            mTvInputHardwareManager.getHdmiDeviceList();
+                    for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
                         try {
-                            serviceState.mService.notifyHdmiCecDeviceAdded(cecDeviceInfo);
+                            serviceState.mService.notifyHdmiDeviceAdded(deviceInfo);
                         } catch (RemoteException e) {
-                            Slog.e(TAG, "error in notifyHdmiCecDeviceAdded", e);
+                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
                         }
                     }
                 }
@@ -2014,11 +2025,11 @@
         }
 
         @Override
-        public void addHdmiCecTvInput(int logicalAddress, TvInputInfo inputInfo) {
+        public void addHdmiTvInput(int logicalAddress, TvInputInfo inputInfo) {
             ensureHardwarePermission();
             ensureValidInput(inputInfo);
             synchronized (mLock) {
-                mTvInputHardwareManager.addHdmiCecTvInput(logicalAddress, inputInfo);
+                mTvInputHardwareManager.addHdmiTvInput(logicalAddress, inputInfo);
                 addTvInputLocked(inputInfo);
             }
         }
@@ -2064,7 +2075,8 @@
                     Uri uri = (Uri) args.arg1;
                     long channelId = (long) args.arg2;
                     long time = (long) args.arg3;
-                    onOpenEntry(uri, channelId, time);
+                    SessionState sessionState = (SessionState) args.arg4;
+                    onOpenEntry(uri, channelId, time, sessionState);
                     args.recycle();
                     return;
                 }
@@ -2073,7 +2085,8 @@
                     Uri uri = (Uri) args.arg1;
                     long channelId = (long) args.arg2;
                     long time = (long) args.arg3;
-                    onUpdateEntry(uri, channelId, time);
+                    SessionState sessionState = (SessionState) args.arg4;
+                    onUpdateEntry(uri, channelId, time, sessionState);
                     args.recycle();
                     return;
                 }
@@ -2092,7 +2105,17 @@
             }
         }
 
-        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
+        private void onOpenEntry(Uri logUri, long channelId, long watchStarttime,
+                SessionState sessionState) {
+            if (!isChannelSearchable(channelId)) {
+                // Do not log anything about non-searchable channels.
+                synchronized (mLock) {
+                    sessionState.mLogUri = null;
+                }
+                mContentResolver.delete(logUri, null, null);
+                return;
+            }
+
             String[] projection = {
                     TvContract.Programs.COLUMN_TITLE,
                     TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
@@ -2120,11 +2143,11 @@
                     long endTime = cursor.getLong(2);
                     values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
                     values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
-                    mContentResolver.update(uri, values, null, null);
+                    mContentResolver.update(logUri, values, null, null);
 
                     // Schedule an update when the current program ends.
                     SomeArgs args = SomeArgs.obtain();
-                    args.arg1 = uri;
+                    args.arg1 = logUri;
                     args.arg2 = channelId;
                     args.arg3 = endTime;
                     Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
@@ -2137,7 +2160,7 @@
             }
         }
 
-        private void onUpdateEntry(Uri uri, long channelId, long time) {
+        private void onUpdateEntry(Uri uri, long channelId, long time, SessionState sessionState) {
             String[] projection = {
                     TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
                     TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
@@ -2181,7 +2204,7 @@
                 }
             }
             // Re-open the current log entry with the next program information.
-            onOpenEntry(uri, channelId, time);
+            onOpenEntry(uri, channelId, time, sessionState);
         }
 
         private void onCloseEntry(Uri uri, long watchEndTime) {
@@ -2189,6 +2212,26 @@
             values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
             mContentResolver.update(uri, values, null, null);
         }
+
+        private boolean isChannelSearchable(long channelId) {
+            String[] projection = { TvContract.Channels.COLUMN_SEARCHABLE };
+            String selection = TvContract.Channels._ID + "=?";
+            String[] selectionArgs = { String.valueOf(channelId) };
+            Cursor cursor = null;
+            try {
+                cursor = mContentResolver.query(TvContract.Channels.CONTENT_URI, projection,
+                        selection, selectionArgs, null);
+                if (cursor != null && cursor.moveToNext()) {
+                    return cursor.getLong(0) == 1 ? true : false;
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            // Unless explicitly specified non-searchable, by default the channel is searchable.
+            return true;
+        }
     }
 
     final class HardwareListener implements TvInputHardwareManager.Listener {
@@ -2232,32 +2275,32 @@
         }
 
         @Override
-        public void onHdmiCecDeviceAdded(HdmiDeviceInfo cecDeviceInfo) {
+        public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
                     if (!serviceState.mIsHardware || serviceState.mService == null) continue;
                     try {
-                        serviceState.mService.notifyHdmiCecDeviceAdded(cecDeviceInfo);
+                        serviceState.mService.notifyHdmiDeviceAdded(deviceInfo);
                     } catch (RemoteException e) {
-                        Slog.e(TAG, "error in notifyHdmiCecDeviceAdded", e);
+                        Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
                     }
                 }
             }
         }
 
         @Override
-        public void onHdmiCecDeviceRemoved(HdmiDeviceInfo cecDeviceInfo) {
+        public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
                     if (!serviceState.mIsHardware || serviceState.mService == null) continue;
                     try {
-                        serviceState.mService.notifyHdmiCecDeviceRemoved(cecDeviceInfo);
+                        serviceState.mService.notifyHdmiDeviceRemoved(deviceInfo);
                     } catch (RemoteException e) {
-                        Slog.e(TAG, "error in notifyHdmiCecDeviceRemoved", e);
+                        Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 304d2d4..4e711ba 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2347,6 +2347,7 @@
             origId = Binder.clearCallingIdentity();
 
             if (addToken) {
+                Slog.w("BadTokenDebug", "addWindow: Adding token=" + token + " attrs.token=" + attrs.token);
                 mTokenMap.put(attrs.token, token);
             }
             win.attach();
@@ -2429,7 +2430,7 @@
             // relayout to be displayed, so we'll do it there.
 
             if (focusChanged) {
-                finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/);
+                mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
             }
             mInputMonitor.updateInputWindowsLw(false /*force*/);
 
@@ -2546,12 +2547,15 @@
                 if (displayContent != null) {
                     displayContent.layoutNeeded = true;
                 }
-                updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
-                        false /*updateInputWindows*/);
+                final boolean focusChanged = updateFocusedWindowLocked(
+                        UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
                 performLayoutAndPlaceSurfacesLocked();
                 if (win.mAppToken != null) {
                     win.mAppToken.updateReportedVisibilityLocked();
                 }
+                if (focusChanged) {
+                    mInputMonitor.updateInputWindowsLw(false /*force*/);
+                }
                 //dump();
                 Binder.restoreCallingIdentity(origId);
                 return;
@@ -2625,7 +2629,9 @@
                 + token.windows.size());
         if (token.windows.size() == 0) {
             if (!token.explicit) {
-                mTokenMap.remove(token.token);
+                WindowToken wtoken = mTokenMap.remove(token.token);
+                Slog.w("BadTokenDebug", "removeWindowInnerLocked: Removing token=" + token + " removed=" +
+                        wtoken + " Callers=" + Debug.getCallers(4));
             } else if (atoken != null) {
                 atoken.firstWindowDrawn = false;
             }
@@ -3411,6 +3417,7 @@
                 return;
             }
             wtoken = new WindowToken(this, token, type, true);
+            Slog.w("BadTokenDebug", "addWindowToken: Adding token=" + token + " wtoken=" + wtoken);
             mTokenMap.put(token, wtoken);
             if (type == TYPE_WALLPAPER) {
                 mWallpaperTokens.add(wtoken);
@@ -3429,6 +3436,8 @@
         synchronized(mWindowMap) {
             DisplayContent displayContent = null;
             WindowToken wtoken = mTokenMap.remove(token);
+            Slog.w("BadTokenDebug", "removeWindowToken: Removing token=" + token + " removed=" + wtoken
+                    + " Callers=" + Debug.getCallers(3));
             if (wtoken != null) {
                 boolean delayed = false;
                 if (!wtoken.hidden) {
@@ -3545,6 +3554,7 @@
                 task.addAppToken(addPos, atoken);
             }
 
+            Slog.w("BadTokenDebug", "addAppToken: Adding token=" + token.asBinder() + " atoken=" + atoken);
             mTokenMap.put(token.asBinder(), atoken);
 
             // Application tokens start out hidden.
@@ -3910,7 +3920,7 @@
             final boolean changed = mFocusedApp != newFocus;
             if (changed) {
                 mFocusedApp = newFocus;
-                mInputMonitor.setFocusedAppLw(null);
+                mInputMonitor.setFocusedAppLw(newFocus);
             }
 
             if (moveFocusNow && changed) {
@@ -4633,6 +4643,7 @@
         final long origId = Binder.clearCallingIdentity();
         synchronized(mWindowMap) {
             WindowToken basewtoken = mTokenMap.remove(token);
+            Slog.w("BadTokenDebug", "removeAppToke: Removing token=" + token + " removed=" + basewtoken);
             if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) {
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken);
                 delayed = setTokenVisibilityLocked(wtoken, null, false,
@@ -8917,7 +8928,7 @@
                     && !moveInputMethodWindowsIfNeededLocked(true)) {
                 assignLayersLocked(windows);
             }
-            updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/);
+            updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, true /*updateInputWindows*/);
             mFocusMayChange = false;
         }
 
@@ -9152,10 +9163,11 @@
         final long currentTime = SystemClock.uptimeMillis();
 
         int i;
+        boolean updateInputWindowsNeeded = false;
 
         if (mFocusMayChange) {
             mFocusMayChange = false;
-            updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+            updateInputWindowsNeeded = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                     false /*updateInputWindows*/);
         }
 
@@ -9517,6 +9529,7 @@
             mFocusMayChange = false;
             if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
                     false /*updateInputWindows*/)) {
+                updateInputWindowsNeeded = true;
                 defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
             }
         }
@@ -9697,6 +9710,9 @@
             mDisplayContents.valueAt(displayNdx).checkForDeferredActions();
         }
 
+        if (updateInputWindowsNeeded) {
+            mInputMonitor.updateInputWindowsLw(false /*force*/);
+        }
         setFocusedStackFrame();
 
         // Check to see if we are now in a state where the screen should
@@ -10012,7 +10028,7 @@
             if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
                 // If we defer assigning layers, then the caller is responsible for
                 // doing this part.
-                finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows);
+                mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
             }
 
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -10021,10 +10037,6 @@
         return false;
     }
 
-    private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) {
-        mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
-    }
-
     private WindowState computeFocusedWindowLocked() {
         if (mAnimator.mUniverseBackground != null
                 && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7a7eaf1..fc96991 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2315,30 +2315,39 @@
         }
         enforceCrossUserPermission(userHandle);
         synchronized (this) {
-            int count = 0;
+            ActiveAdmin admin = (who != null) ? getActiveAdminUncheckedLocked(who, userHandle)
+                    : getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle);
+            return admin != null ? admin.maximumFailedPasswordsForWipe : 0;
+        }
+    }
 
-            if (who != null) {
-                ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
-                return admin != null ? admin.maximumFailedPasswordsForWipe : count;
-            }
+    /**
+     * Returns the admin with the strictest policy on maximum failed passwords for this user and all
+     * profiles that are visible from this user. If the policy for the primary and any other profile
+     * are equal, it returns the admin for the primary profile.
+     * Returns {@code null} if none of them have that policy set.
+     */
+    private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked(int userHandle) {
+        int count = 0;
+        ActiveAdmin strictestAdmin = null;
+        for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
+            DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier());
+            for (ActiveAdmin admin : policy.mAdminList) {
+                if (admin.maximumFailedPasswordsForWipe ==
+                        ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+                    continue;  // No max number of failed passwords policy set for this profile.
+                }
 
-            // Return strictest policy for this user and profiles that are visible from this user.
-            List<UserInfo> profiles = mUserManager.getProfiles(userHandle);
-            for (UserInfo userInfo : profiles) {
-                DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier());
-                final int N = policy.mAdminList.size();
-                for (int i=0; i<N; i++) {
-                    ActiveAdmin admin = policy.mAdminList.get(i);
-                    if (count == 0) {
-                        count = admin.maximumFailedPasswordsForWipe;
-                    } else if (admin.maximumFailedPasswordsForWipe != 0
-                            && count > admin.maximumFailedPasswordsForWipe) {
-                        count = admin.maximumFailedPasswordsForWipe;
-                    }
+                // We always favor the primary profile if several profiles have the same value set.
+                if (count == 0 ||
+                        count > admin.maximumFailedPasswordsForWipe ||
+                        (userInfo.isPrimary() && count >= admin.maximumFailedPasswordsForWipe)) {
+                    count = admin.maximumFailedPasswordsForWipe;
+                    strictestAdmin = admin;
                 }
             }
-            return count;
         }
+        return strictestAdmin;
     }
 
     public boolean resetPassword(String password, int flags, int userHandle) {
@@ -2713,7 +2722,9 @@
                 public void run() {
                     try {
                         ActivityManagerNative.getDefault().switchUser(UserHandle.USER_OWNER);
-                        mUserManager.removeUser(userHandle);
+                        if (!mUserManager.removeUser(userHandle)) {
+                            Slog.w(LOG_TAG, "Couldn't remove user " + userHandle);
+                        }
                     } catch (RemoteException re) {
                         // Shouldn't happen
                     }
@@ -2837,9 +2848,15 @@
                 policy.mFailedPasswordAttempts++;
                 saveSettingsLocked(userHandle);
                 if (mHasFeature) {
-                    int max = getMaximumFailedPasswordsForWipe(null, userHandle);
+                    ActiveAdmin strictestAdmin =
+                            getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle);
+                    int max = strictestAdmin != null
+                            ? strictestAdmin.maximumFailedPasswordsForWipe : 0;
                     if (max > 0 && policy.mFailedPasswordAttempts >= max) {
-                        wipeDeviceOrUserLocked(0, userHandle);
+                        // Wipe the user/profile associated with the policy that was violated. This
+                        // is not necessarily calling user: if the policy that fired was from a
+                        // managed profile rather than the main user profile, we wipe former only.
+                        wipeDeviceOrUserLocked(0, strictestAdmin.getUserHandle().getIdentifier());
                     }
                     sendAdminCommandToSelfAndProfilesLocked(
                             DeviceAdminReceiver.ACTION_PASSWORD_FAILED,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index b4c221f..f0ecafe 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -41,12 +41,12 @@
     static final boolean DBG = true;
 
     private static final String NAME = "sound_model.db";
-    private static final int VERSION = 3;
+    private static final int VERSION = 4;
 
     public static interface SoundModelContract {
         public static final String TABLE = "sound_model";
-        public static final String KEY_KEYPHRASE_ID = "keyphrase_id";
         public static final String KEY_MODEL_UUID = "model_uuid";
+        public static final String KEY_KEYPHRASE_ID = "keyphrase_id";
         public static final String KEY_TYPE = "type";
         public static final String KEY_DATA = "data";
         public static final String KEY_RECOGNITION_MODES = "recognition_modes";
@@ -58,8 +58,8 @@
     // Table Create Statement
     private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE "
             + SoundModelContract.TABLE + "("
-            + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER PRIMARY KEY,"
-            + SoundModelContract.KEY_MODEL_UUID + " TEXT,"
+            + SoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+            + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER,"
             + SoundModelContract.KEY_TYPE + " INTEGER,"
             + SoundModelContract.KEY_DATA + " BLOB,"
             + SoundModelContract.KEY_RECOGNITION_MODES + " INTEGER,"
@@ -122,10 +122,16 @@
     /**
      * Deletes the sound model and associated keyphrases.
      */
-    public boolean deleteKeyphraseSoundModel(int keyphraseId) {
+    public boolean deleteKeyphraseSoundModel(UUID modelUuid) {
+        if (modelUuid == null) {
+            Slog.w(TAG, "Model UUID must be specified for deletion");
+            return false;
+        }
+
         synchronized(this) {
             SQLiteDatabase db = getWritableDatabase();
-            String soundModelClause = SoundModelContract.KEY_KEYPHRASE_ID + "=" + keyphraseId;
+            String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "="
+                    + modelUuid.toString();
 
             try {
                 return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
@@ -151,52 +157,56 @@
 
             try {
                 if (c.moveToFirst()) {
-                    int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
-                    if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
-                        Slog.w(TAG, "No KeyphraseSoundModel available for the given keyphrase");
-                        return null;
-                    }
-
-                    String modelUuid = c.getString(
-                            c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
-                    if (modelUuid == null) {
-                        Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
-                        return null;
-                    }
-
-                    byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
-                    int recognitionModes = c.getInt(
-                            c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
-                    int[] users = getArrayForCommaSeparatedString(
-                            c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
-                    String locale = c.getString(c.getColumnIndex(SoundModelContract.KEY_LOCALE));
-                    String text = c.getString(
-                            c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
-
-                    // Only add keyphrases meant for the current user.
-                    if (users == null) {
-                        // No users present in the keyphrase.
-                        Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
-                        return null;
-                    }
-                    boolean isAvailableForCurrentUser = false;
-                    int currentUser = mUserManager.getUserHandle();
-                    for (int user : users) {
-                        if (currentUser == user) {
-                            isAvailableForCurrentUser = true;
-                            break;
+                    do {
+                        int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
+                        if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
+                            Slog.w(TAG, "Ignoring sound model since it's type is incorrect");
+                            continue;
                         }
-                    }
-                    if (!isAvailableForCurrentUser) {
-                        Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
-                        return null;
-                    }
 
-                    Keyphrase[] keyphrases = new Keyphrase[1];
-                    keyphrases[0] = new Keyphrase(
-                            keyphraseId, recognitionModes, locale, text, users);
-                    return new KeyphraseSoundModel(UUID.fromString(modelUuid),
-                            null /* FIXME use vendor UUID */, data, keyphrases);
+                        String modelUuid = c.getString(
+                                c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
+                        if (modelUuid == null) {
+                            Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
+                            continue;
+                        }
+
+                        byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
+                        int recognitionModes = c.getInt(
+                                c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
+                        int[] users = getArrayForCommaSeparatedString(
+                                c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
+                        String locale = c.getString(
+                                c.getColumnIndex(SoundModelContract.KEY_LOCALE));
+                        String text = c.getString(
+                                c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+
+                        // Only add keyphrases meant for the current user.
+                        if (users == null) {
+                            // No users present in the keyphrase.
+                            Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
+                            continue;
+                        }
+
+                        boolean isAvailableForCurrentUser = false;
+                        int currentUser = mUserManager.getUserHandle();
+                        for (int user : users) {
+                            if (currentUser == user) {
+                                isAvailableForCurrentUser = true;
+                                break;
+                            }
+                        }
+                        if (!isAvailableForCurrentUser) {
+                            Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
+                            continue;
+                        }
+
+                        Keyphrase[] keyphrases = new Keyphrase[1];
+                        keyphrases[0] = new Keyphrase(
+                                keyphraseId, recognitionModes, locale, text, users);
+                        return new KeyphraseSoundModel(UUID.fromString(modelUuid),
+                                null /* FIXME use vendor UUID */, data, keyphrases);
+                    } while (c.moveToNext());
                 }
                 Slog.w(TAG, "No SoundModel available for the given keyphrase");
             } finally {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index fd36bfc..994f758 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -28,9 +28,12 @@
 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.RemoteException;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
-import android.util.SparseArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.UUID;
 
@@ -53,26 +56,37 @@
     public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
     public static final int STATUS_OK = SoundTrigger.STATUS_OK;
 
-    private static final int INVALID_SOUND_MODEL_HANDLE = -1;
+    private static final int INVALID_VALUE = Integer.MIN_VALUE;
 
-    /** The {@link DspInfo} for the system, or null if none exists. */
+    /** The {@link ModuleProperties} for the system, or null if none exists. */
     final ModuleProperties moduleProperties;
 
     /** The properties for the DSP module */
     private final SoundTriggerModule mModule;
+    private final Object mLock = new Object();
+    private final TelephonyManager mTelephonyManager;
+    private final PhoneStateListener mPhoneStateListener;
 
-    // Use a RemoteCallbackList here?
-    private final SparseArray<IRecognitionStatusCallback> mActiveListeners;
-
-    private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+    // TODO: Since many layers currently only deal with one recognition
+    // we simplify things by assuming one listener here too.
+    private IRecognitionStatusCallback mActiveListener;
+    private int mKeyphraseId = INVALID_VALUE;
+    private int mCurrentSoundModelHandle = INVALID_VALUE;
     private UUID mCurrentSoundModelUuid = null;
     // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
     private RecognitionConfig mRecognitionConfig = null;
+    private boolean mRequested = false;
+    private boolean mCallActive = false;
+    // Indicates if the native sound trigger service is disabled or not.
+    // This is an indirect indication of the microphone being open in some other application.
+    private boolean mServiceDisabled = false;
+    private boolean mStarted = false;
 
-    SoundTriggerHelper() {
+    SoundTriggerHelper(TelephonyManager telephonyManager) {
         ArrayList <ModuleProperties> modules = new ArrayList<>();
         int status = SoundTrigger.listModules(modules);
-        mActiveListeners = new SparseArray<>(1);
+        mTelephonyManager = telephonyManager;
+        mPhoneStateListener = new MyCallStateListener();
         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
             Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
             moduleProperties = null;
@@ -85,17 +99,6 @@
     }
 
     /**
-     * @return True, if a recognition for the given {@link Keyphrase} is active.
-     */
-    synchronized boolean isKeyphraseActive(Keyphrase keyphrase) {
-        if (keyphrase == null) {
-            Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase");
-            return false;
-        }
-        return mActiveListeners.get(keyphrase.id) != null;
-    }
-
-    /**
      * Starts recognition for the given keyphraseId.
      *
      * @param keyphraseId The identifier of the keyphrase for which
@@ -104,85 +107,94 @@
      * @param listener The listener for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    synchronized int startRecognition(int keyphraseId,
+    int startRecognition(int keyphraseId,
             KeyphraseSoundModel soundModel,
             IRecognitionStatusCallback listener,
             RecognitionConfig recognitionConfig) {
-        if (DBG) {
-            Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
-                    + " soundModel=" + soundModel + ", listener=" + listener
-                    + ", recognitionConfig=" + recognitionConfig);
-            Slog.d(TAG, "moduleProperties=" + moduleProperties);
-            Slog.d(TAG, "# of current listeners=" + mActiveListeners.size());
-            Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
-            Slog.d(TAG, "current SoundModel UUID="
-                    + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid));
-        }
-        if (moduleProperties == null || mModule == null) {
-            Slog.w(TAG, "Attempting startRecognition without the capability");
+        if (soundModel == null || listener == null || recognitionConfig == null) {
             return STATUS_ERROR;
         }
 
-        if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE
-                && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
-            Slog.w(TAG, "Unloading previous sound model");
-            int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
-            if (status != SoundTrigger.STATUS_OK) {
-                Slog.w(TAG, "unloadSoundModel call failed with " + status);
-                return status;
+        synchronized (mLock) {
+            if (DBG) {
+                Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
+                        + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
+                        + ", recognitionConfig=" + recognitionConfig);
+                Slog.d(TAG, "moduleProperties=" + moduleProperties);
+                Slog.d(TAG, "current listener="
+                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+                Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
+                Slog.d(TAG, "current SoundModel UUID="
+                        + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid));
             }
-            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-            mCurrentSoundModelUuid = null;
-        }
 
-        // If the previous recognition was by a different listener,
-        // Notify them that it was stopped.
-        IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId);
-        if (oldListener != null && oldListener.asBinder() != listener.asBinder()) {
-            Slog.w(TAG, "Canceling previous recognition");
-            try {
-                oldListener.onError(STATUS_ERROR);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in onDetectionStopped");
+            if (!mStarted) {
+                // Get the current call state synchronously for the first recognition.
+                mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+                // Register for call state changes when the first call to start recognition occurs.
+                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
             }
-            mActiveListeners.remove(keyphraseId);
-        }
 
-        // Load the sound model if the current one is null.
-        int soundModelHandle = mCurrentSoundModelHandle;
-        if (mCurrentSoundModelHandle == INVALID_SOUND_MODEL_HANDLE
-                || mCurrentSoundModelUuid == null) {
-            int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE };
-            int status = mModule.loadSoundModel(soundModel, handle);
-            if (status != SoundTrigger.STATUS_OK) {
-                Slog.w(TAG, "loadSoundModel call failed with " + status);
-                return status;
-            }
-            if (handle[0] == INVALID_SOUND_MODEL_HANDLE) {
-                Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+            if (moduleProperties == null || mModule == null) {
+                Slog.w(TAG, "Attempting startRecognition without the capability");
                 return STATUS_ERROR;
             }
-            soundModelHandle = handle[0];
-        } else {
-            if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
-        }
 
-        // Start the recognition.
-        int status = mModule.startRecognition(soundModelHandle, recognitionConfig);
-        if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "startRecognition failed with " + status);
-            return status;
-        }
+            if (mCurrentSoundModelHandle != INVALID_VALUE
+                    && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
+                Slog.w(TAG, "Unloading previous sound model");
+                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "unloadSoundModel call failed with " + status);
+                }
+                mCurrentSoundModelHandle = INVALID_VALUE;
+                mCurrentSoundModelUuid = null;
+                mStarted = false;
+            }
 
-        // Everything went well!
-        mCurrentSoundModelHandle = soundModelHandle;
-        mCurrentSoundModelUuid = soundModel.uuid;
-        mRecognitionConfig = recognitionConfig;
-        // Register the new listener. This replaces the old one.
-        // There can only be a maximum of one active listener for a keyphrase
-        // at any given time.
-        mActiveListeners.put(keyphraseId, listener);
-        return STATUS_OK;
+            // If the previous recognition was by a different listener,
+            // Notify them that it was stopped.
+            if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
+                Slog.w(TAG, "Canceling previous recognition");
+                try {
+                    mActiveListener.onError(STATUS_ERROR);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onDetectionStopped");
+                }
+                mActiveListener = null;
+            }
+
+            // Load the sound model if the current one is null.
+            int soundModelHandle = mCurrentSoundModelHandle;
+            if (mCurrentSoundModelHandle == INVALID_VALUE
+                    || mCurrentSoundModelUuid == null) {
+                int[] handle = new int[] { INVALID_VALUE };
+                int status = mModule.loadSoundModel(soundModel, handle);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "loadSoundModel call failed with " + status);
+                    return status;
+                }
+                if (handle[0] == INVALID_VALUE) {
+                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+                    return STATUS_ERROR;
+                }
+                soundModelHandle = handle[0];
+            } else {
+                if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
+            }
+
+            // Start the recognition.
+            mRequested = true;
+            mKeyphraseId = keyphraseId;
+            mCurrentSoundModelHandle = soundModelHandle;
+            mCurrentSoundModelUuid = soundModel.uuid;
+            mRecognitionConfig = recognitionConfig;
+            // Register the new listener. This replaces the old one.
+            // There can only be a maximum of one active listener at any given time.
+            mActiveListener = listener;
+
+            return updateRecognitionLocked(false /* don't notify for synchronous calls */);
+        }
     }
 
     /**
@@ -195,173 +207,325 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-        if (DBG) {
-            Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
-                    + ", listener=" + listener);
-            Slog.d(TAG, "# of current listeners = " + mActiveListeners.size());
-        }
-
-        if (moduleProperties == null || mModule == null) {
-            Slog.w(TAG, "Attempting stopRecognition without the capability");
-            return STATUS_ERROR;
-        }
-
-        IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId);
+    int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
         if (listener == null) {
-            Slog.w(TAG, "Attempting stopRecognition without a valid listener");
             return STATUS_ERROR;
-        } if (currentListener == null) {
-            // startRecognition hasn't been called or it failed.
-            Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
-            return STATUS_ERROR;
-        } else if (currentListener.asBinder() != listener.asBinder()) {
-            // We don't allow a different listener to stop the recognition than the one
-            // that started it.
-            Slog.w(TAG, "Attempting stopRecognition for another recognition");
-            return STATUS_ERROR;
-        } else {
+        }
+
+        synchronized (mLock) {
+            if (DBG) {
+                Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
+                        + ", listener=" + listener.asBinder());
+                Slog.d(TAG, "current listener="
+                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+            }
+
+            if (moduleProperties == null || mModule == null) {
+                Slog.w(TAG, "Attempting stopRecognition without the capability");
+                return STATUS_ERROR;
+            }
+
+            if (mActiveListener == null) {
+                // startRecognition hasn't been called or it failed.
+                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+                return STATUS_ERROR;
+            }
+            if (mActiveListener.asBinder() != listener.asBinder()) {
+                // We don't allow a different listener to stop the recognition than the one
+                // that started it.
+                Slog.w(TAG, "Attempting stopRecognition for another recognition");
+                return STATUS_ERROR;
+            }
+
             // Stop recognition if it's the current one, ignore otherwise.
-            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            mRequested = false;
+            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
             if (status != SoundTrigger.STATUS_OK) {
-                Slog.w(TAG, "stopRecognition call failed with " + status);
                 return status;
             }
+
             status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "unloadSoundModel call failed with " + status);
-                return status;
             }
 
-            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-            mCurrentSoundModelUuid = null;
-
-            mActiveListeners.remove(keyphraseId);
-            return STATUS_OK;
+            // Clear the internal state once the recognition has been stopped.
+            // Unload sound model call may fail in scenarios, and we'd still want
+            // to reload the sound model.
+            internalClearStateLocked();
+            return status;
         }
     }
 
-    synchronized void stopAllRecognitions() {
-        if (moduleProperties == null || mModule == null) {
-            return;
-        }
+    /**
+     * Stops all recognitions active currently and clears the internal state.
+     */
+    void stopAllRecognitions() {
+        synchronized (mLock) {
+            if (moduleProperties == null || mModule == null) {
+                return;
+            }
 
-        if (mCurrentSoundModelHandle == INVALID_SOUND_MODEL_HANDLE) {
-            return;
-        }
+            if (mCurrentSoundModelHandle == INVALID_VALUE) {
+                return;
+            }
 
-        int status = mModule.stopRecognition(mCurrentSoundModelHandle);
-        if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "stopRecognition call failed with " + status);
-        }
-        status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
-        if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "unloadSoundModel call failed with " + status);
-        }
+            mRequested = false;
+            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
+            status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadSoundModel call failed with " + status);
+            }
 
-        mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-        mCurrentSoundModelUuid = null;
-
-        mActiveListeners.clear();
+            internalClearStateLocked();
+        }
     }
 
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
-        if (event == null) {
+        if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
             Slog.w(TAG, "Invalid recognition event!");
             return;
         }
 
         if (DBG) Slog.d(TAG, "onRecognition: " + event);
-        switch (event.status) {
-            // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
-            case SoundTrigger.RECOGNITION_STATUS_ABORT: // fall-through
-            case SoundTrigger.RECOGNITION_STATUS_FAILURE:
-                try {
-                    synchronized (this) {
-                        for (int i = 0; i < mActiveListeners.size(); i++) {
-                            mActiveListeners.valueAt(i).onError(STATUS_ERROR);
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onDetectionStopped");
-                }
-                break;
-            case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
-                if (!(event instanceof KeyphraseRecognitionEvent)) {
-                    Slog.w(TAG, "Invalid recognition event!");
-                    return;
-                }
-
-                KeyphraseRecognitionExtra[] keyphraseExtras =
-                        ((KeyphraseRecognitionEvent) event).keyphraseExtras;
-                if (keyphraseExtras == null || keyphraseExtras.length == 0) {
-                    Slog.w(TAG, "Invalid keyphrase recognition event!");
-                    return;
-                }
-                // TODO: Handle more than one keyphrase extras.
-                int keyphraseId = keyphraseExtras[0].id;
-                try {
-                    synchronized(this) {
-                        // Check which keyphrase triggered, and fire the appropriate event.
-                        IRecognitionStatusCallback listener = mActiveListeners.get(keyphraseId);
-                        if (listener != null) {
-                            listener.onDetected((KeyphraseRecognitionEvent) event);
-                        } else {
-                            Slog.w(TAG, "received onRecognition event without any listener for it");
-                            return;
-                        }
-
-                        // FIXME: Remove this block if the lower layer supports multiple triggers.
-                        if (mRecognitionConfig != null
-                                && mRecognitionConfig.allowMultipleTriggers) {
-                            int status = mModule.startRecognition(
-                                    mCurrentSoundModelHandle, mRecognitionConfig);
-                            if (status != STATUS_OK) {
-                                Slog.w(TAG, "Error in restarting recognition after a trigger");
-                                listener.onError(status);
-                            }
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onDetectionStopped");
-                }
-                break;
+        synchronized (mLock) {
+            if (mActiveListener == null) {
+                Slog.w(TAG, "received onRecognition event without any listener for it");
+                return;
+            }
+            switch (event.status) {
+                // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
+                case SoundTrigger.RECOGNITION_STATUS_ABORT:
+                    onRecognitionAbortLocked();
+                    break;
+                case SoundTrigger.RECOGNITION_STATUS_FAILURE:
+                    onRecognitionFailureLocked();
+                    break;
+                case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
+                    onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                    break;
+            }
         }
     }
 
+    @Override
     public void onSoundModelUpdate(SoundModelEvent event) {
         if (event == null) {
             Slog.w(TAG, "Invalid sound model event!");
             return;
         }
-
         if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
-
-        //TODO: implement sound model update
+        synchronized (mLock) {
+            onSoundModelUpdatedLocked(event);
+        }
     }
 
+    @Override
     public void onServiceStateChange(int state) {
         if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
-
-        //TODO: implement service state update
+        synchronized (mLock) {
+            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
+        }
     }
 
     @Override
     public void onServiceDied() {
-        synchronized (this) {
-            try {
-                for (int i = 0; i < mActiveListeners.size(); i++) {
-                    mActiveListeners.valueAt(i).onError(SoundTrigger.STATUS_DEAD_OBJECT);
-                }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in onDetectionStopped");
+        Slog.e(TAG, "onServiceDied!!");
+        synchronized (mLock) {
+            onServiceDiedLocked();
+        }
+    }
+
+    class MyCallStateListener extends PhoneStateListener {
+        @Override
+        public void onCallStateChanged(int state, String arg1) {
+            if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
+            synchronized (mLock) {
+                onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state);
             }
-            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-            mCurrentSoundModelUuid = null;
-            // Remove all listeners.
-            mActiveListeners.clear();
+        }
+    }
+
+    private void onCallStateChangedLocked(boolean callActive) {
+        if (mCallActive == callActive) {
+            // We consider multiple call states as being active
+            // so we check if something really changed or not here.
+            return;
+        }
+        mCallActive = callActive;
+        updateRecognitionLocked(true /* notify */);
+    }
+
+    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
+        // TODO: Handle sound model update here.
+    }
+
+    private void onServiceStateChangedLocked(boolean disabled) {
+        if (disabled == mServiceDisabled) {
+            return;
+        }
+        mServiceDisabled = disabled;
+        updateRecognitionLocked(true /* notify */);
+    }
+
+    private void onRecognitionAbortLocked() {
+        Slog.w(TAG, "Recognition aborted");
+        // No-op
+        // This is handled via service state changes instead.
+    }
+
+    private void onRecognitionFailureLocked() {
+        Slog.w(TAG, "Recognition failure");
+        try {
+            if (mActiveListener != null) {
+                mActiveListener.onError(STATUS_ERROR);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onError", e);
+        } finally {
+            internalClearStateLocked();
+        }
+    }
+
+    private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+        Slog.i(TAG, "Recognition success");
+        KeyphraseRecognitionExtra[] keyphraseExtras =
+                ((KeyphraseRecognitionEvent) event).keyphraseExtras;
+        if (keyphraseExtras == null || keyphraseExtras.length == 0) {
+            Slog.w(TAG, "Invalid keyphrase recognition event!");
+            return;
+        }
+        // TODO: Handle more than one keyphrase extras.
+        if (mKeyphraseId != keyphraseExtras[0].id) {
+            Slog.w(TAG, "received onRecognition event for a different keyphrase");
+            return;
+        }
+
+        try {
+            if (mActiveListener != null) {
+                mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onDetected", e);
+        }
+
+        mStarted = false;
+        mRequested = mRecognitionConfig.allowMultipleTriggers;
+        // TODO: Remove this block if the lower layer supports multiple triggers.
+        if (mRequested) {
+            updateRecognitionLocked(true /* notify */);
+        }
+    }
+
+    private void onServiceDiedLocked() {
+        try {
+            if (mActiveListener != null) {
+                mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onError", e);
+        } finally {
+            internalClearStateLocked();
+        }
+    }
+
+    private int updateRecognitionLocked(boolean notify) {
+        if (mModule == null || moduleProperties == null
+                || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
+            // Nothing to do here.
+            return STATUS_OK;
+        }
+
+        boolean start = mRequested && !mCallActive && !mServiceDisabled;
+        if (start == mStarted) {
+            // No-op.
+            return STATUS_OK;
+        }
+
+        // See if the recognition needs to be started.
+        if (start) {
+            // Start recognition.
+            int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "startRecognition failed with " + status);
+                // Notify of error if needed.
+                if (notify) {
+                    try {
+                        mActiveListener.onError(status);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onError", e);
+                    }
+                }
+            } else {
+                mStarted = true;
+                // Notify of resume if needed.
+                if (notify) {
+                    try {
+                        mActiveListener.onRecognitionResumed();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
+                    }
+                }
+            }
+            return status;
+        } else {
+            // Stop recognition.
+            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "stopRecognition call failed with " + status);
+                if (notify) {
+                    try {
+                        mActiveListener.onError(status);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onError", e);
+                    }
+                }
+            } else {
+                mStarted = false;
+                // Notify of pause if needed.
+                if (notify) {
+                    try {
+                        mActiveListener.onRecognitionPaused();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                    }
+                }
+            }
+            return status;
+        }
+    }
+
+    private void internalClearStateLocked() {
+        mStarted = false;
+        mRequested = false;
+
+        mKeyphraseId = INVALID_VALUE;
+        mCurrentSoundModelHandle = INVALID_VALUE;
+        mCurrentSoundModelUuid = null;
+        mRecognitionConfig = null;
+        mActiveListener = null;
+
+        // Unregister from call state changes.
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            pw.print("  module properties=");
+            pw.println(moduleProperties == null ? "null" : moduleProperties);
+            pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
+            pw.print("  sound model handle="); pw.println(mCurrentSoundModelHandle);
+            pw.print("  sound model UUID=");
+            pw.println(mCurrentSoundModelUuid == null ? "null" : mCurrentSoundModelUuid);
+            pw.print("  current listener=");
+            pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());
+
+            pw.print("  requested="); pw.println(mRequested);
+            pw.print("  started="); pw.println(mStarted);
+            pw.print("  call active="); pw.println(mCallActive);
+            pw.print("  service disabled="); pw.println(mServiceDisabled);
         }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 75d41aa..0d24793 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -38,6 +38,7 @@
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
 
 import com.android.internal.app.IVoiceInteractionManagerService;
@@ -67,7 +68,8 @@
         mContext = context;
         mResolver = context.getContentResolver();
         mDbHelper = new DatabaseHelper(context);
-        mSoundTriggerHelper = new SoundTriggerHelper();
+        mSoundTriggerHelper = new SoundTriggerHelper(
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
     }
 
     @Override
@@ -301,19 +303,22 @@
             }
 
             final long caller = Binder.clearCallingIdentity();
+            boolean deleted = false;
             try {
-                if (mDbHelper.deleteKeyphraseSoundModel(keyphraseId)) {
+                KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
+                if (soundModel != null) {
+                    deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid);
+                }
+                return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
+            } finally {
+                if (deleted) {
                     synchronized (this) {
                         // Notify the voice interaction service of a change in sound models.
                         if (mImpl != null && mImpl.mService != null) {
                             mImpl.notifySoundModelsChangedLocked();
                         }
                     }
-                    return SoundTriggerHelper.STATUS_OK;
-                } else {
-                    return SoundTriggerHelper.STATUS_ERROR;
                 }
-            } finally {
                 Binder.restoreCallingIdentity(caller);
             }
         }
@@ -427,6 +432,7 @@
                 }
                 mImpl.dumpLocked(fd, pw, args);
             }
+            mSoundTriggerHelper.dump(fd, pw, args);
         }
 
         class SettingsObserver extends ContentObserver {
diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java
index 4e9e604..55cb8b1 100644
--- a/telecomm/java/android/telecomm/Call.java
+++ b/telecomm/java/android/telecomm/Call.java
@@ -69,6 +69,11 @@
      */
     public static final int STATE_PRE_DIAL_WAIT = 8;
 
+    /**
+     * The state of an outgoing {@code Call}, before Telecomm broadcast intent has returned.
+     */
+    public static final int STATE_CONNECTING = 9;
+
     public static class Details {
         private final Uri mHandle;
         private final int mHandlePresentation;
@@ -249,7 +254,7 @@
         /**
          * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
          *
-         * TODO(ihab): Provide previous state also?
+         * TODO: Provide previous state also?
          *
          * @param call The {@code Call} invoking this method.
          * @param state The new state of the {@code Call}.
@@ -453,7 +458,7 @@
     /**
      * Notifies this {@code Call} that the phone account user interface element was touched.
      *
-     * TODO(ihab): Figure out if and how we can generalize this
+     * TODO: Figure out if and how we can generalize this
      */
     public void phoneAccountClicked() {
         mInCallAdapter.phoneAccountClicked(mTelecommCallId);
@@ -771,6 +776,8 @@
         switch (parcelableCallState) {
             case NEW:
                 return STATE_NEW;
+            case CONNECTING:
+                return STATE_CONNECTING;
             case PRE_DIAL_WAIT:
                 return STATE_PRE_DIAL_WAIT;
             case DIALING:
diff --git a/telecomm/java/android/telecomm/CallState.java b/telecomm/java/android/telecomm/CallState.java
index 9cb05db..cfa78d4 100644
--- a/telecomm/java/android/telecomm/CallState.java
+++ b/telecomm/java/android/telecomm/CallState.java
@@ -32,6 +32,12 @@
     NEW,
 
     /**
+     * Indicates an outgoing call has been initiated and is waiting for the broadcast intent to
+     * return and provide call details before proceeding.
+     */
+    CONNECTING,
+
+    /**
      * Indicates that the call is about to go into the outgoing and dialing state but is waiting for
      * user input before it proceeds. For example, where no default {@link PhoneAccount} is set,
      * this is the state where the InCallUI is waiting for the user to select a
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index d347aad..b323646 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -273,7 +273,7 @@
     }
 
     /**
-     * TODO(santoscordon): Needs documentation.
+     * TODO: Needs documentation.
      */
     public final void setParentConnection(Connection parentConnection) {
         Log.d(this, "parenting %s to %s", this, parentConnection);
@@ -458,7 +458,7 @@
     }
 
     /**
-     * TODO(santoscordon): Needs documentation.
+     * TODO: Needs documentation.
      */
     public final void setPostDialWait(String remaining) {
         for (Listener l : mListeners) {
@@ -654,7 +654,7 @@
     public void onSwapWithBackgroundCall() {}
 
     /**
-     * TODO(santoscordon): Needs documentation.
+     * TODO: Needs documentation.
      */
     public void onChildrenChanged(List<Connection> children) {}
 
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index fddc9b0..5e6bf87 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -647,7 +647,7 @@
             return;
         }
 
-        // TODO(santoscordon): Find existing conference call and invoke split(connection).
+        // TODO: Find existing conference call and invoke split(connection).
     }
 
     private void swapWithBackgroundCall(String callId) {
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index 279e47d..08eb03a 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -28,8 +28,6 @@
  * given call IDs to execute commands such as {@link #answerCall} for incoming calls or
  * {@link #disconnectCall} for active calls the user would like to end. Some commands are only
  * appropriate for calls in certain states; please consult each method for such limitations.
- * TODO(santoscordon): Needs more/better comments once the API is finalized.
- * TODO(santoscordon): Specify the adapter will stop functioning when there are no more calls.
  */
 public final class InCallAdapter {
     private final IInCallAdapter mAdapter;
@@ -56,8 +54,6 @@
 
     /**
      * Instructs Telecomm to reject the specified call.
-     * TODO(santoscordon): Add reject-with-text-message parameter when that feature
-     * is ported over.
      *
      * @param callId The identifier of the call to reject.
      * @param rejectWithMessage Whether to reject with a text message.
diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java
index 83e2957..a88e1cc 100644
--- a/telecomm/java/android/telecomm/InCallService.java
+++ b/telecomm/java/android/telecomm/InCallService.java
@@ -35,7 +35,7 @@
  * This service is implemented by any app that wishes to provide the user-interface for managing
  * phone calls. Telecomm binds to this service while there exists a live (active or incoming) call,
  * and uses it to notify the in-call app of any live and and recently disconnected calls.
- * TODO(santoscordon): What happens if two or more apps on a given device implement this interface?
+ * TODO: What happens if two or more apps on a given device implement this interface?
  */
 public abstract class InCallService extends Service {
     private static final int MSG_SET_IN_CALL_ADAPTER = 1;
diff --git a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
index f5c4c34..5bf59c7 100644
--- a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
+++ b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
@@ -97,11 +97,11 @@
         String packageName = mArgs.getString(PACKAGE_TO_LAUNCH);
         if (packageName != null) {
             Log.d(TAG, "Launching app " + packageName);
-            Collection<ProcessErrorStateInfo> err = launchActivity(packageName);
+            ProcessErrorStateInfo err = launchActivity(packageName);
             // Make sure there are no errors when launching the application,
             // otherwise raise an
             // exception with the first error encountered.
-            assertNull(getFirstError(err), err);
+            assertNull(getStackTrace(err), err);
             assertTrue("App crashed after launch.", processStillUp(packageName));
         } else {
             Log.d(TAG, "Missing argument, use " + PACKAGE_TO_LAUNCH +
@@ -110,20 +110,32 @@
     }
 
     /**
-     * Gets the first error in collection and return the long message for it.
+     * Gets the stack trace for the error.
      *
-     * @param in {@link Collection} of {@link ProcessErrorStateInfo} to parse.
+     * @param in {@link ProcessErrorStateInfo} to parse.
      * @return {@link String} the long message of the error.
      */
-    private String getFirstError(Collection<ProcessErrorStateInfo> in) {
+    private String getStackTrace(ProcessErrorStateInfo in) {
         if (in == null) {
             return null;
+        } else {
+            return in.stackTrace;
         }
-        ProcessErrorStateInfo err = in.iterator().next();
-        if (err != null) {
-            return err.stackTrace;
+    }
+
+    /**
+     * Returns the process name that the package is going to use.
+     *
+     * @param packageName name of the package
+     * @return process name of the package
+     */
+    private String getProcessName(String packageName) {
+        try {
+            PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
+            return pi.applicationInfo.processName;
+        } catch (NameNotFoundException e) {
+            return packageName;
         }
-        return null;
     }
 
     /**
@@ -134,7 +146,7 @@
      * @return {@link Collection} of {@link ProcessErrorStateInfo} detected
      *         during the app launch.
      */
-    private Collection<ProcessErrorStateInfo> launchActivity(String packageName) {
+    private ProcessErrorStateInfo launchActivity(String packageName) {
         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
         homeIntent.addCategory(Intent.CATEGORY_HOME);
         homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -146,16 +158,7 @@
             return null;
         }
 
-        // We check for any Crash or ANR dialogs that are already up, and we
-        // ignore them. This is
-        // so that we don't report crashes that were caused by prior apps (which
-        // those particular
-        // tests should have caught and reported already). Otherwise, test
-        // failures would cascade
-        // from the initial broken app to many/all of the tests following that
-        // app's launch.
-        final Collection<ProcessErrorStateInfo> preErr =
-                mActivityManager.getProcessesInErrorState();
+        String processName = getProcessName(packageName);
 
         // Launch Activity
         mContext.startActivity(intent);
@@ -179,13 +182,16 @@
         // possible to occur.
         final Collection<ProcessErrorStateInfo> postErr =
                 mActivityManager.getProcessesInErrorState();
-        // Take the difference between the error processes we see now, and the
-        // ones that were
-        // present when we started
-        if (preErr != null && postErr != null) {
-            postErr.removeAll(preErr);
+
+        if (postErr == null) {
+            return null;
         }
-        return postErr;
+        for (ProcessErrorStateInfo error : postErr) {
+            if (error.processName.equals(processName)) {
+                return error;
+            }
+        }
+        return null;
     }
 
     /**
@@ -195,22 +201,16 @@
      * @return True if package is running, false otherwise.
      */
     private boolean processStillUp(String packageName) {
-        try {
-            PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, 0);
-            String processName = packageInfo.applicationInfo.processName;
-            List<RunningAppProcessInfo> runningApps = mActivityManager.getRunningAppProcesses();
-            for (RunningAppProcessInfo app : runningApps) {
-                if (app.processName.equalsIgnoreCase(processName)) {
-                    Log.d(TAG, "Found process " + app.processName);
-                    return true;
-                }
+        String processName = getProcessName(packageName);
+        List<RunningAppProcessInfo> runningApps = mActivityManager.getRunningAppProcesses();
+        for (RunningAppProcessInfo app : runningApps) {
+            if (app.processName.equalsIgnoreCase(processName)) {
+                Log.d(TAG, "Found process " + app.processName);
+                return true;
             }
-            Log.d(TAG, "Failed to find process " + processName + " with package name "
-                    + packageName);
-        } catch (NameNotFoundException e) {
-            Log.w(TAG, "Failed to find package " + packageName);
-            return false;
         }
+        Log.d(TAG, "Failed to find process " + processName + " with package name "
+                + packageName);
         return false;
     }
 }
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index feecfde..890d68d 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -86,10 +86,10 @@
         mRouter.setRoutingCallback(new RoutingCallback(), null);
 
         mSession = new MediaSession(mContext, "OneMedia");
-        mSession.addCallback(mCallback);
-        mSession.addTransportControlsCallback(new TransportCallback());
+        mSession.setCallback(mCallback);
         mSession.setPlaybackState(mPlaybackState);
-        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
+                | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
         mSession.setMediaRouter(mRouter);
         mSession.setActive(true);
     }
@@ -230,26 +230,6 @@
 
     private class SessionCb extends MediaSession.Callback {
         @Override
-        public void onMediaButtonEvent(Intent mediaRequestIntent) {
-            if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
-                KeyEvent event = (KeyEvent) mediaRequestIntent
-                        .getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-                switch (event.getKeyCode()) {
-                    case KeyEvent.KEYCODE_MEDIA_PLAY:
-                        Log.d(TAG, "play button received");
-                        mRenderer.onPlay();
-                        break;
-                    case KeyEvent.KEYCODE_MEDIA_PAUSE:
-                        Log.d(TAG, "pause button received");
-                        mRenderer.onPause();
-                        break;
-                }
-            }
-        }
-    }
-
-    private class TransportCallback extends MediaSession.TransportControlsCallback {
-        @Override
         public void onPlay() {
             mRenderer.onPlay();
         }
@@ -315,7 +295,7 @@
                         updateState(PlaybackState.STATE_NONE);
                         break;
                 }
-            } 
+            }
         }
     }
 }
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
index 5b1f6ab..bbf1a17 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
@@ -28,8 +28,7 @@
                 l-5.046875,0.0 0.0-1.0Z" />
         <path
             android:name="two"
-            android:fillColor="#ffff00"
-            android:fillOpacity="0"
+            android:fillColor="#00ffff00"
             android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0-5.5625,0.0 0.0-1.0q 0.671875-0.6875 1.828125-1.859375
                         q 1.1718752-1.1875 1.4687502-1.53125 0.578125-0.625 0.796875-1.0625
                         q 0.234375-0.453125 0.234375-0.875 0.0-0.703125-0.5-1.140625
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml
index 8cabca8..1aad743 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml
@@ -21,10 +21,10 @@
 
     <group>
         <path
-            android:fillOpacity="0.9"
+            android:fillColor="#E6000000"
             android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.582 -8.0,-8.0s3.58,-8.0 8.0,-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z" />
         <path
-            android:fillOpacity="0.9"
+            android:fillColor="#E6000000"
             android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75,-1.249999 -4.5529995,-2.7320004z" />
     </group>
 
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index 02610f8..77c0c32 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -20,7 +20,7 @@
 import android.os.Bundle;
 import android.service.voice.AlwaysOnHotwordDetector;
 import android.service.voice.AlwaysOnHotwordDetector.Callback;
-import android.service.voice.AlwaysOnHotwordDetector.TriggerAudio;
+import android.service.voice.AlwaysOnHotwordDetector.EventPayload;
 import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
@@ -37,7 +37,7 @@
         }
 
         @Override
-        public void onDetected(TriggerAudio triggerAudio) {
+        public void onDetected(EventPayload eventPayload) {
             Log.i(TAG, "onDetected");
         }
 
@@ -45,6 +45,16 @@
         public void onError() {
             Log.i(TAG, "onError");
         }
+
+        @Override
+        public void onRecognitionPaused() {
+            Log.i(TAG, "onRecognitionPaused");
+        }
+
+        @Override
+        public void onRecognitionResumed() {
+            Log.i(TAG, "onRecognitionResumed");
+        }
     };
 
     private AlwaysOnHotwordDetector mHotwordDetector;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 4f1d15e..ec284c5 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1739,7 +1739,7 @@
 
 static status_t writeLayoutClasses(
     FILE* fp, const sp<AaptAssets>& assets,
-    const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
+    const sp<AaptSymbols>& symbols, int indent, bool includePrivate, bool nonConstantId)
 {
     const char* indentStr = getIndentSpace(indent);
     if (!includePrivate) {
@@ -1957,8 +1957,13 @@
                         getSymbolName(name8).string());
                 fprintf(fp, "%s*/\n", indentStr);
                 ann.printAnnotations(fp, indentStr);
+
+                const char * id_format = nonConstantId ?
+                        "%spublic static int %s_%s = %d;\n" :
+                        "%spublic static final int %s_%s = %d;\n";
+
                 fprintf(fp,
-                        "%spublic static final int %s_%s = %d;\n",
+                        id_format,
                         indentStr, nclassName.string(),
                         flattenSymbol(name8).string(), (int)pos);
             }
@@ -2177,7 +2182,7 @@
     }
 
     if (styleableSymbols != NULL) {
-        err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
+        err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate, nonConstantId);
         if (err != NO_ERROR) {
             return err;
         }
diff --git a/tools/layoutlib/rename_font/build_font_single.py b/tools/layoutlib/rename_font/build_font_single.py
new file mode 100755
index 0000000..d648b04
--- /dev/null
+++ b/tools/layoutlib/rename_font/build_font_single.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python
+
+# 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.
+
+"""
+Rename the PS name of the input font.
+
+OpenType fonts (*.otf) are not currently supported. They are copied to the destination without renaming.
+XML files are also copied in case they are passed there by mistake.
+
+Usage: build_font.py /path/to/input_font.ttf /path/to/output_font.ttf
+
+"""
+
+import glob
+import os
+import re
+import shutil
+import sys
+import xml.etree.ElementTree as etree
+
+# Prevent .pyc files from being created.
+sys.dont_write_bytecode = True
+
+# fontTools is available at platform/external/fonttools
+from fontTools import ttx
+
+
+class FontInfo(object):
+  family = None
+  style = None
+  version = None
+  ends_in_regular = False
+  fullname = None
+
+
+class InvalidFontException(Exception):
+  pass
+
+
+# A constant to copy the font without modifying. This is useful when running
+# locally and speed up the time to build the SDK.
+COPY_ONLY = False
+
+# These constants represent the value of nameID parameter in the namerecord for
+# different information.
+# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b
+NAMEID_FAMILY = 1
+NAMEID_STYLE = 2
+NAMEID_FULLNAME = 4
+NAMEID_VERSION = 5
+
+# A list of extensions to process.
+EXTENSIONS = ['.ttf', '.otf', '.xml']
+
+def main(argv):
+  if len(argv) < 2:
+    sys.exit('Usage: build_font.py /path/to/input/font.ttf /path/to/out/font.ttf')
+  dest_path = argv[-1]
+  input_path = argv[0]
+  extension = os.path.splitext(input_path)[1].lower()
+  if extension in EXTENSIONS:
+    if not COPY_ONLY and extension == '.ttf':
+      convert_font(input_path, dest_path)
+      return
+    shutil.copy(input_path, dest_path)
+
+
+def convert_font(input_path, dest_path):
+  filename = os.path.basename(input_path)
+  print 'Converting font: ' + filename
+  # the path to the output file. The file name is the fontfilename.ttx
+  ttx_path = dest_path[:-1] + 'x'
+  try:
+    # run ttx to generate an xml file in the output folder which represents all
+    # its info
+    ttx_args = ['-q', '-o', ttx_path, input_path]
+    ttx.main(ttx_args)
+    # now parse the xml file to change its PS name.
+    tree = etree.parse(ttx_path)
+    root = tree.getroot()
+    for name in root.iter('name'):
+      update_tag(name, get_font_info(name))
+    tree.write(ttx_path, xml_declaration=True, encoding='utf-8')
+    # generate the udpated font now.
+    ttx_args = ['-q', '-o', dest_path, ttx_path]
+    ttx.main(ttx_args)
+  except InvalidFontException:
+    # In case of invalid fonts, we exit.
+    print filename + ' is not a valid font'
+    raise
+  except Exception as e:
+    print 'Error converting font: ' + filename
+    print e
+    # Some fonts are too big to be handled by the ttx library.
+    # Just copy paste them.
+    shutil.copy(input_path, dest_path)
+  try:
+    # delete the temp ttx file is it exists.
+    os.remove(ttx_path)
+  except OSError:
+    pass
+
+
+def get_font_info(tag):
+  """ Returns a list of FontInfo representing the various sets of namerecords
+      found in the name table of the font. """
+  fonts = []
+  font = None
+  last_name_id = sys.maxint
+  for namerecord in tag.iter('namerecord'):
+    if 'nameID' in namerecord.attrib:
+      name_id = int(namerecord.attrib['nameID'])
+      # A new font should be created for each platform, encoding and language
+      # id. But, since the nameIDs are sorted, we use the easy approach of
+      # creating a new one when the nameIDs reset.
+      if name_id <= last_name_id and font is not None:
+        fonts.append(font)
+        font = None
+      last_name_id = name_id
+      if font is None:
+        font = FontInfo()
+      if name_id == NAMEID_FAMILY:
+        font.family = namerecord.text.strip()
+      if name_id == NAMEID_STYLE:
+        font.style = namerecord.text.strip()
+      if name_id == NAMEID_FULLNAME:
+        font.ends_in_regular = ends_in_regular(namerecord.text)
+        font.fullname = namerecord.text.strip()
+      if name_id == NAMEID_VERSION:
+        font.version = get_version(namerecord.text)
+  if font is not None:
+    fonts.append(font)
+  return fonts
+
+
+def update_tag(tag, fonts):
+  last_name_id = sys.maxint
+  fonts_iterator = fonts.__iter__()
+  font = None
+  for namerecord in tag.iter('namerecord'):
+    if 'nameID' in namerecord.attrib:
+      name_id = int(namerecord.attrib['nameID'])
+      if name_id <= last_name_id:
+        font = fonts_iterator.next()
+        font = update_font_name(font)
+      last_name_id = name_id
+      if name_id == NAMEID_FAMILY:
+        namerecord.text = font.family
+      if name_id == NAMEID_FULLNAME:
+        namerecord.text = font.fullname
+
+
+def update_font_name(font):
+  """ Compute the new font family name and font fullname. If the font has a
+      valid version, it's sanitized and appended to the font family name. The
+      font fullname is then created by joining the new family name and the
+      style. If the style is 'Regular', it is appended only if the original font
+      had it. """
+  if font.family is None or font.style is None:
+    raise InvalidFontException('Font doesn\'t have proper family name or style')
+  if font.version is not None:
+    new_family = font.family + font.version
+  else:
+    new_family = font.family
+  if font.style is 'Regular' and not font.ends_in_regular:
+    font.fullname = new_family
+  else:
+    font.fullname = new_family + ' ' + font.style
+  font.family = new_family
+  return font
+
+
+def ends_in_regular(string):
+  """ According to the specification, the font fullname should not end in
+      'Regular' for plain fonts. However, some fonts don't obey this rule. We
+      keep the style info, to minimize the diff. """
+  string = string.strip().split()[-1]
+  return string is 'Regular'
+
+
+def get_version(string):
+  # The string must begin with 'Version n.nn '
+  # to extract n.nn, we return the second entry in the split strings.
+  string = string.strip()
+  if not string.startswith('Version '):
+    raise InvalidFontException('mal-formed font version')
+  return sanitize(string.split()[1])
+
+
+def sanitize(string):
+  return re.sub(r'[^\w-]+', '', string)
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index dfed174..1ca54bc 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -343,6 +343,7 @@
      * Wi-Fi configurations changed, {@link #EXTRA_WIFI_CONFIGURATION} will not be present.
      * @hide
      */
+    @SystemApi
     public static final String CONFIGURED_NETWORKS_CHANGED_ACTION =
         "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
     /**
@@ -351,6 +352,7 @@
      * broadcast is sent.
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
     /**
      * Multiple network configurations have changed.
@@ -358,6 +360,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
     /**
      * The lookup key for an integer indicating the reason a Wi-Fi network configuration
@@ -365,23 +368,27 @@
      * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_CHANGE_REASON = "changeReason";
     /**
      * The configuration is new and was added.
      * @hide
      */
+    @SystemApi
     public static final int CHANGE_REASON_ADDED = 0;
     /**
      * The configuration was removed and is no longer present in the system's list of
      * configured networks.
      * @hide
      */
+    @SystemApi
     public static final int CHANGE_REASON_REMOVED = 1;
     /**
      * The configuration has changed as a result of explicit action or because the system
      * took an automated action such as disabling a malfunctioning configuration.
      * @hide
      */
+    @SystemApi
     public static final int CHANGE_REASON_CONFIG_CHANGE = 2;
     /**
      * An access point scan has completed, and results are available from the supplicant.
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index c5c44b5..e7bcb23 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -100,7 +100,7 @@
      */
     public static class ChannelSpec {
         /**
-         * channel frequency in KHz; for example channel 1 is specified as 2412
+         * channel frequency in MHz; for example channel 1 is specified as 2412
          */
         public int frequency;
         /**
@@ -158,6 +158,7 @@
             dest.writeInt(band);
             dest.writeInt(periodInMs);
             dest.writeInt(reportEvents);
+            dest.writeInt(numBssidsPerScan);
 
             if (channels != null) {
                 dest.writeInt(channels.length);
@@ -181,6 +182,7 @@
                         settings.band = in.readInt();
                         settings.periodInMs = in.readInt();
                         settings.reportEvents = in.readInt();
+                        settings.numBssidsPerScan = in.readInt();
                         int num_channels = in.readInt();
                         settings.channels = new ChannelSpec[num_channels];
                         for (int i = 0; i < num_channels; i++) {