Merge "Fix copying folders using keyboard shortcuts" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index cb5d4b3..0177e4f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13541,6 +13541,7 @@
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
+    field public static final int TYPE_DEVICE_PRIVATE_BASE = 65536; // 0x10000
     field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
     field public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; // 0x14
     field public static final int TYPE_GRAVITY = 9; // 0x9
@@ -22279,7 +22280,7 @@
     method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void unsubscribe(java.lang.String);
-    method public void unsubscribe(java.lang.String, android.os.Bundle);
+    method public void unsubscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
     field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
     field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
   }
@@ -23052,6 +23053,7 @@
     method public abstract void onStartRecording(android.net.Uri);
     method public abstract void onStopRecording();
     method public abstract void onTune(android.net.Uri);
+    method public void onTune(android.net.Uri, android.os.Bundle);
   }
 
   public static abstract class TvInputService.Session implements android.view.KeyEvent.Callback {
@@ -23101,6 +23103,7 @@
     method public void startRecording(android.net.Uri);
     method public void stopRecording();
     method public void tune(java.lang.String, android.net.Uri);
+    method public void tune(java.lang.String, android.net.Uri, android.os.Bundle);
   }
 
   public static abstract class TvRecordingClient.RecordingCallback {
@@ -30267,7 +30270,7 @@
     method public android.print.PrinterInfo build();
     method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo);
     method public android.print.PrinterInfo.Builder setDescription(java.lang.String);
-    method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon();
+    method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon(boolean);
     method public android.print.PrinterInfo.Builder setIconResourceId(int);
     method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
     method public android.print.PrinterInfo.Builder setName(java.lang.String);
@@ -30319,6 +30322,7 @@
     method public boolean isStarted();
     method public void setProgress(float);
     method public void setStatus(java.lang.CharSequence);
+    method public void setStatus(int);
     method public boolean setTag(java.lang.String);
     method public boolean start();
   }
@@ -30395,6 +30399,7 @@
   public class BlockedNumberContract {
     method public static boolean canCurrentUserBlockNumbers(android.content.Context);
     method public static boolean isBlocked(android.content.Context, java.lang.String);
+    method public static int unblock(android.content.Context, java.lang.String);
     field public static final java.lang.String AUTHORITY = "com.android.blockednumber";
     field public static final android.net.Uri AUTHORITY_URI;
   }
@@ -32387,6 +32392,7 @@
     field public static final java.lang.String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE";
     field public static final java.lang.String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
     field public static final java.lang.String ACTION_VOICE_INPUT_SETTINGS = "android.settings.VOICE_INPUT_SETTINGS";
+    field public static final java.lang.String ACTION_VPN_SETTINGS = "android.settings.VPN_SETTINGS";
     field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
@@ -34576,7 +34582,7 @@
     method public boolean onMenuOpened(int, android.view.Menu);
     method public void onPanelClosed(int, android.view.Menu);
     method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
-    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu);
+    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public boolean onSearchRequested(android.view.SearchEvent);
     method public boolean onSearchRequested();
     method public void onWakeUp();
@@ -36007,9 +36013,11 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void pullExternalCall();
+    method public final void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
+    method public final void removeExtras(java.util.List<java.lang.String>);
     method public void sendCallEvent(java.lang.String, android.os.Bundle);
     method public void splitFromConference();
     method public void stopDtmfTone();
@@ -36138,6 +36146,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -36150,6 +36159,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
@@ -36158,14 +36168,17 @@
     method public void onStopDtmfTone();
     method public void onSwap();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
     method public final void removeConnection(android.telecom.Connection);
+    method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void setActive();
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setConnectionTime(long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setOnHold();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -36191,6 +36204,7 @@
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -36203,6 +36217,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
@@ -36213,6 +36228,9 @@
     method public void onStateChanged(int);
     method public void onStopDtmfTone();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public static java.lang.String propertiesToString(int);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -36221,9 +36239,10 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setInitialized();
     method public final void setInitializing();
     method public final void setNextPostDialChar(char);
@@ -36237,12 +36256,11 @@
     method public static java.lang.String stateToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
-    field public static final int CAPABILITY_CAN_PULL_CALL = 33554432; // 0x2000000
     field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
     field public static final int CAPABILITY_HOLD = 1; // 0x1
-    field public static final int CAPABILITY_IS_EXTERNAL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
@@ -36260,6 +36278,7 @@
     field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
     field public static final int STATE_DISCONNECTED = 6; // 0x6
@@ -36482,6 +36501,7 @@
     method public void disconnect();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final java.util.List<android.telecom.RemoteConnection> getConnections();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
@@ -36504,6 +36524,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
     method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onDestroyed(android.telecom.RemoteConference);
     method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -36522,6 +36543,7 @@
     method public android.telecom.RemoteConference getConference();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public int getConnectionCapabilities();
+    method public int getConnectionProperties();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public int getState();
@@ -36551,6 +36573,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
     method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
     method public void onDestroyed(android.telecom.RemoteConnection);
     method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
     method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -41640,9 +41663,11 @@
   }
 
   public final class KeyboardShortcutInfo implements android.os.Parcelable {
+    ctor public KeyboardShortcutInfo(java.lang.CharSequence, int, int);
     ctor public KeyboardShortcutInfo(java.lang.CharSequence, char, int);
     method public int describeContents();
     method public char getBaseCharacter();
+    method public int getKeycode();
     method public java.lang.CharSequence getLabel();
     method public int getModifiers();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43653,7 +43678,7 @@
     method public abstract boolean onMenuOpened(int, android.view.Menu);
     method public abstract void onPanelClosed(int, android.view.Menu);
     method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
-    method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu);
+    method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public abstract boolean onSearchRequested();
     method public abstract boolean onSearchRequested(android.view.SearchEvent);
     method public abstract void onWindowAttributesChanged(android.view.WindowManager.LayoutParams);
@@ -51113,41 +51138,11 @@
     method public java.io.File directory();
     method public java.lang.ProcessBuilder directory(java.io.File);
     method public java.util.Map<java.lang.String, java.lang.String> environment();
-    method public java.lang.ProcessBuilder inheritIO();
-    method public java.lang.ProcessBuilder redirectError(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectError(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectError();
     method public boolean redirectErrorStream();
     method public java.lang.ProcessBuilder redirectErrorStream(boolean);
-    method public java.lang.ProcessBuilder redirectInput(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectInput(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectInput();
-    method public java.lang.ProcessBuilder redirectOutput(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectOutput(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectOutput();
     method public java.lang.Process start() throws java.io.IOException;
   }
 
-  public static abstract class ProcessBuilder.Redirect {
-    method public static java.lang.ProcessBuilder.Redirect appendTo(java.io.File);
-    method public java.io.File file();
-    method public static java.lang.ProcessBuilder.Redirect from(java.io.File);
-    method public static java.lang.ProcessBuilder.Redirect to(java.io.File);
-    method public abstract java.lang.ProcessBuilder.Redirect.Type type();
-    field public static final java.lang.ProcessBuilder.Redirect INHERIT;
-    field public static final java.lang.ProcessBuilder.Redirect PIPE;
-  }
-
-  public static final class ProcessBuilder.Redirect.Type extends java.lang.Enum {
-    method public static java.lang.ProcessBuilder.Redirect.Type valueOf(java.lang.String);
-    method public static final java.lang.ProcessBuilder.Redirect.Type[] values();
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type APPEND;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type INHERIT;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type PIPE;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type READ;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type WRITE;
-  }
-
   public abstract interface Readable {
     method public abstract int read(java.nio.CharBuffer) throws java.io.IOException;
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index a59a704..7956826 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -13942,6 +13942,7 @@
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
+    field public static final int TYPE_DEVICE_PRIVATE_BASE = 65536; // 0x10000
     field public static final int TYPE_DYNAMIC_SENSOR_META = 32; // 0x20
     field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
     field public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; // 0x14
@@ -23844,7 +23845,7 @@
     method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void unsubscribe(java.lang.String);
-    method public void unsubscribe(java.lang.String, android.os.Bundle);
+    method public void unsubscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
     field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
     field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
   }
@@ -32582,7 +32583,7 @@
     method public android.print.PrinterInfo build();
     method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo);
     method public android.print.PrinterInfo.Builder setDescription(java.lang.String);
-    method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon();
+    method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon(boolean);
     method public android.print.PrinterInfo.Builder setIconResourceId(int);
     method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
     method public android.print.PrinterInfo.Builder setName(java.lang.String);
@@ -32634,6 +32635,7 @@
     method public boolean isStarted();
     method public void setProgress(float);
     method public void setStatus(java.lang.CharSequence);
+    method public void setStatus(int);
     method public boolean setTag(java.lang.String);
     method public boolean start();
   }
@@ -32710,6 +32712,7 @@
   public class BlockedNumberContract {
     method public static boolean canCurrentUserBlockNumbers(android.content.Context);
     method public static boolean isBlocked(android.content.Context, java.lang.String);
+    method public static int unblock(android.content.Context, java.lang.String);
     field public static final java.lang.String AUTHORITY = "com.android.blockednumber";
     field public static final android.net.Uri AUTHORITY_URI;
   }
@@ -34834,6 +34837,7 @@
     field public static final java.lang.String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE";
     field public static final java.lang.String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
     field public static final java.lang.String ACTION_VOICE_INPUT_SETTINGS = "android.settings.VOICE_INPUT_SETTINGS";
+    field public static final java.lang.String ACTION_VPN_SETTINGS = "android.settings.VPN_SETTINGS";
     field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
@@ -37026,7 +37030,7 @@
     method public boolean onMenuOpened(int, android.view.Menu);
     method public void onPanelClosed(int, android.view.Menu);
     method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
-    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu);
+    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public boolean onSearchRequested(android.view.SearchEvent);
     method public boolean onSearchRequested();
     method public void onWakeUp();
@@ -38570,9 +38574,11 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void pullExternalCall();
+    method public final void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
+    method public final void removeExtras(java.util.List<java.lang.String>);
     method public deprecated void removeListener(android.telecom.Call.Listener);
     method public void sendCallEvent(java.lang.String, android.os.Bundle);
     method public void splitFromConference();
@@ -38709,6 +38715,7 @@
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final deprecated long getConnectTimeMillis();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -38723,6 +38730,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
@@ -38731,15 +38739,18 @@
     method public void onStopDtmfTone();
     method public void onSwap();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
     method public final void removeConnection(android.telecom.Connection);
+    method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void setActive();
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final deprecated void setConnectTimeMillis(long);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setConnectionTime(long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setOnHold();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -38766,6 +38777,7 @@
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -38779,6 +38791,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
@@ -38789,6 +38802,9 @@
     method public void onStateChanged(int);
     method public void onStopDtmfTone();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public static java.lang.String propertiesToString(int);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -38797,9 +38813,10 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setInitialized();
     method public final void setInitializing();
     method public final void setNextPostDialChar(char);
@@ -38813,12 +38830,11 @@
     method public static java.lang.String stateToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
-    field public static final int CAPABILITY_CAN_PULL_CALL = 33554432; // 0x2000000
     field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
     field public static final int CAPABILITY_HOLD = 1; // 0x1
-    field public static final int CAPABILITY_IS_EXTERNAL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
@@ -38836,6 +38852,7 @@
     field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
     field public static final int STATE_DISCONNECTED = 6; // 0x6
@@ -39113,6 +39130,7 @@
     method public void disconnect();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final java.util.List<android.telecom.RemoteConnection> getConnections();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
@@ -39136,6 +39154,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
     method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onDestroyed(android.telecom.RemoteConference);
     method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -39154,6 +39173,7 @@
     method public android.telecom.RemoteConference getConference();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public int getConnectionCapabilities();
+    method public int getConnectionProperties();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public int getState();
@@ -39184,6 +39204,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
     method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
     method public void onDestroyed(android.telecom.RemoteConnection);
     method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
     method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -44366,9 +44387,11 @@
   }
 
   public final class KeyboardShortcutInfo implements android.os.Parcelable {
+    ctor public KeyboardShortcutInfo(java.lang.CharSequence, int, int);
     ctor public KeyboardShortcutInfo(java.lang.CharSequence, char, int);
     method public int describeContents();
     method public char getBaseCharacter();
+    method public int getKeycode();
     method public java.lang.CharSequence getLabel();
     method public int getModifiers();
     method public void writeToParcel(android.os.Parcel, int);
@@ -46380,7 +46403,7 @@
     method public abstract boolean onMenuOpened(int, android.view.Menu);
     method public abstract void onPanelClosed(int, android.view.Menu);
     method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
-    method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu);
+    method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public abstract boolean onSearchRequested();
     method public abstract boolean onSearchRequested(android.view.SearchEvent);
     method public abstract void onWindowAttributesChanged(android.view.WindowManager.LayoutParams);
@@ -54178,41 +54201,11 @@
     method public java.io.File directory();
     method public java.lang.ProcessBuilder directory(java.io.File);
     method public java.util.Map<java.lang.String, java.lang.String> environment();
-    method public java.lang.ProcessBuilder inheritIO();
-    method public java.lang.ProcessBuilder redirectError(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectError(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectError();
     method public boolean redirectErrorStream();
     method public java.lang.ProcessBuilder redirectErrorStream(boolean);
-    method public java.lang.ProcessBuilder redirectInput(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectInput(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectInput();
-    method public java.lang.ProcessBuilder redirectOutput(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectOutput(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectOutput();
     method public java.lang.Process start() throws java.io.IOException;
   }
 
-  public static abstract class ProcessBuilder.Redirect {
-    method public static java.lang.ProcessBuilder.Redirect appendTo(java.io.File);
-    method public java.io.File file();
-    method public static java.lang.ProcessBuilder.Redirect from(java.io.File);
-    method public static java.lang.ProcessBuilder.Redirect to(java.io.File);
-    method public abstract java.lang.ProcessBuilder.Redirect.Type type();
-    field public static final java.lang.ProcessBuilder.Redirect INHERIT;
-    field public static final java.lang.ProcessBuilder.Redirect PIPE;
-  }
-
-  public static final class ProcessBuilder.Redirect.Type extends java.lang.Enum {
-    method public static java.lang.ProcessBuilder.Redirect.Type valueOf(java.lang.String);
-    method public static final java.lang.ProcessBuilder.Redirect.Type[] values();
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type APPEND;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type INHERIT;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type PIPE;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type READ;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type WRITE;
-  }
-
   public abstract interface Readable {
     method public abstract int read(java.nio.CharBuffer) throws java.io.IOException;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 87e6e53..264b5eb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13551,6 +13551,7 @@
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
+    field public static final int TYPE_DEVICE_PRIVATE_BASE = 65536; // 0x10000
     field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
     field public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; // 0x14
     field public static final int TYPE_GRAVITY = 9; // 0x9
@@ -22344,7 +22345,7 @@
     method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback);
     method public void unsubscribe(java.lang.String);
-    method public void unsubscribe(java.lang.String, android.os.Bundle);
+    method public void unsubscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
     field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
     field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
   }
@@ -23117,6 +23118,7 @@
     method public abstract void onStartRecording(android.net.Uri);
     method public abstract void onStopRecording();
     method public abstract void onTune(android.net.Uri);
+    method public void onTune(android.net.Uri, android.os.Bundle);
   }
 
   public static abstract class TvInputService.Session implements android.view.KeyEvent.Callback {
@@ -23166,6 +23168,7 @@
     method public void startRecording(android.net.Uri);
     method public void stopRecording();
     method public void tune(java.lang.String, android.net.Uri);
+    method public void tune(java.lang.String, android.net.Uri, android.os.Bundle);
   }
 
   public static abstract class TvRecordingClient.RecordingCallback {
@@ -30259,7 +30262,7 @@
     method public android.print.PrinterId getPrinterId();
     method public float getProgress();
     method public int getState();
-    method public java.lang.CharSequence getStatus();
+    method public java.lang.CharSequence getStatus(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
     field public static final int STATE_BLOCKED = 4; // 0x4
@@ -30336,7 +30339,7 @@
     method public android.print.PrinterInfo build();
     method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo);
     method public android.print.PrinterInfo.Builder setDescription(java.lang.String);
-    method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon();
+    method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon(boolean);
     method public android.print.PrinterInfo.Builder setIconResourceId(int);
     method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
     method public android.print.PrinterInfo.Builder setName(java.lang.String);
@@ -30388,6 +30391,7 @@
     method public boolean isStarted();
     method public void setProgress(float);
     method public void setStatus(java.lang.CharSequence);
+    method public void setStatus(int);
     method public boolean setTag(java.lang.String);
     method public boolean start();
   }
@@ -30464,6 +30468,7 @@
   public class BlockedNumberContract {
     method public static boolean canCurrentUserBlockNumbers(android.content.Context);
     method public static boolean isBlocked(android.content.Context, java.lang.String);
+    method public static int unblock(android.content.Context, java.lang.String);
     field public static final java.lang.String AUTHORITY = "com.android.blockednumber";
     field public static final android.net.Uri AUTHORITY_URI;
   }
@@ -32456,6 +32461,7 @@
     field public static final java.lang.String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE";
     field public static final java.lang.String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
     field public static final java.lang.String ACTION_VOICE_INPUT_SETTINGS = "android.settings.VOICE_INPUT_SETTINGS";
+    field public static final java.lang.String ACTION_VPN_SETTINGS = "android.settings.VPN_SETTINGS";
     field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
@@ -34647,7 +34653,7 @@
     method public boolean onMenuOpened(int, android.view.Menu);
     method public void onPanelClosed(int, android.view.Menu);
     method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
-    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu);
+    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public boolean onSearchRequested(android.view.SearchEvent);
     method public boolean onSearchRequested();
     method public void onWakeUp();
@@ -36078,9 +36084,11 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void pullExternalCall();
+    method public final void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
+    method public final void removeExtras(java.util.List<java.lang.String>);
     method public void sendCallEvent(java.lang.String, android.os.Bundle);
     method public void splitFromConference();
     method public void stopDtmfTone();
@@ -36209,6 +36217,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
     method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -36221,6 +36230,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
@@ -36229,14 +36239,17 @@
     method public void onStopDtmfTone();
     method public void onSwap();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
     method public final void removeConnection(android.telecom.Connection);
+    method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void setActive();
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setConnectionTime(long);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setOnHold();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -36262,6 +36275,7 @@
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -36274,6 +36288,7 @@
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
@@ -36284,6 +36299,9 @@
     method public void onStateChanged(int);
     method public void onStopDtmfTone();
     method public void onUnhold();
+    method public final void putExtras(android.os.Bundle);
+    method public final void removeExtras(java.util.List<java.lang.String>);
+    method public static java.lang.String propertiesToString(int);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -36292,9 +36310,10 @@
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
+    method public final void setConnectionProperties(int);
     method public final void setDialing();
     method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setExtras(android.os.Bundle);
+    method public final deprecated void setExtras(android.os.Bundle);
     method public final void setInitialized();
     method public final void setInitializing();
     method public final void setNextPostDialChar(char);
@@ -36308,12 +36327,11 @@
     method public static java.lang.String stateToString(int);
     field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
     field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
-    field public static final int CAPABILITY_CAN_PULL_CALL = 33554432; // 0x2000000
     field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
+    field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
     field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
     field public static final int CAPABILITY_HOLD = 1; // 0x1
-    field public static final int CAPABILITY_IS_EXTERNAL_CALL = 16777216; // 0x1000000
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
@@ -36331,6 +36349,7 @@
     field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
     field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
     field public static final int STATE_DISCONNECTED = 6; // 0x6
@@ -36553,6 +36572,7 @@
     method public void disconnect();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
+    method public final int getConnectionProperties();
     method public final java.util.List<android.telecom.RemoteConnection> getConnections();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
@@ -36575,6 +36595,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
     method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
     method public void onDestroyed(android.telecom.RemoteConference);
     method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -36593,6 +36614,7 @@
     method public android.telecom.RemoteConference getConference();
     method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
     method public int getConnectionCapabilities();
+    method public int getConnectionProperties();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public int getState();
@@ -36622,6 +36644,7 @@
     method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
     method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
     method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+    method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
     method public void onDestroyed(android.telecom.RemoteConnection);
     method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
     method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -41713,9 +41736,11 @@
   }
 
   public final class KeyboardShortcutInfo implements android.os.Parcelable {
+    ctor public KeyboardShortcutInfo(java.lang.CharSequence, int, int);
     ctor public KeyboardShortcutInfo(java.lang.CharSequence, char, int);
     method public int describeContents();
     method public char getBaseCharacter();
+    method public int getKeycode();
     method public java.lang.CharSequence getLabel();
     method public int getModifiers();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43726,7 +43751,7 @@
     method public abstract boolean onMenuOpened(int, android.view.Menu);
     method public abstract void onPanelClosed(int, android.view.Menu);
     method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
-    method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu);
+    method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public abstract boolean onSearchRequested();
     method public abstract boolean onSearchRequested(android.view.SearchEvent);
     method public abstract void onWindowAttributesChanged(android.view.WindowManager.LayoutParams);
@@ -51186,41 +51211,11 @@
     method public java.io.File directory();
     method public java.lang.ProcessBuilder directory(java.io.File);
     method public java.util.Map<java.lang.String, java.lang.String> environment();
-    method public java.lang.ProcessBuilder inheritIO();
-    method public java.lang.ProcessBuilder redirectError(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectError(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectError();
     method public boolean redirectErrorStream();
     method public java.lang.ProcessBuilder redirectErrorStream(boolean);
-    method public java.lang.ProcessBuilder redirectInput(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectInput(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectInput();
-    method public java.lang.ProcessBuilder redirectOutput(java.lang.ProcessBuilder.Redirect);
-    method public java.lang.ProcessBuilder redirectOutput(java.io.File);
-    method public java.lang.ProcessBuilder.Redirect redirectOutput();
     method public java.lang.Process start() throws java.io.IOException;
   }
 
-  public static abstract class ProcessBuilder.Redirect {
-    method public static java.lang.ProcessBuilder.Redirect appendTo(java.io.File);
-    method public java.io.File file();
-    method public static java.lang.ProcessBuilder.Redirect from(java.io.File);
-    method public static java.lang.ProcessBuilder.Redirect to(java.io.File);
-    method public abstract java.lang.ProcessBuilder.Redirect.Type type();
-    field public static final java.lang.ProcessBuilder.Redirect INHERIT;
-    field public static final java.lang.ProcessBuilder.Redirect PIPE;
-  }
-
-  public static final class ProcessBuilder.Redirect.Type extends java.lang.Enum {
-    method public static java.lang.ProcessBuilder.Redirect.Type valueOf(java.lang.String);
-    method public static final java.lang.ProcessBuilder.Redirect.Type[] values();
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type APPEND;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type INHERIT;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type PIPE;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type READ;
-    enum_constant public static final java.lang.ProcessBuilder.Redirect.Type WRITE;
-  }
-
   public abstract interface Readable {
     method public abstract int read(java.nio.CharBuffer) throws java.io.IOException;
   }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index ea53e59..c597ed2 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -68,14 +68,11 @@
 
 // ---------------------------------------------------------------------------
 
-BootAnimation::BootAnimation() : Thread(false), mZip(NULL), mClockEnabled(true) {
+BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true) {
     mSession = new SurfaceComposerClient();
 }
 
 BootAnimation::~BootAnimation() {
-    if (mZip != NULL) {
-        delete mZip;
-    }
 }
 
 void BootAnimation::onFirstRef() {
@@ -288,19 +285,15 @@
 
     bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
 
-    ZipFileRO* zipFile = NULL;
-    if ((encryptedAnimation &&
-            (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
-            ((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
-
-            ((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
-            ((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||
-
-            ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
-            ((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
-        mZip = zipFile;
+    if (encryptedAnimation && (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) {
+        mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE;
     }
-
+    else if (access(OEM_BOOTANIMATION_FILE, R_OK) == 0) {
+        mZipFileName = OEM_BOOTANIMATION_FILE;
+    }
+    else if (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) {
+        mZipFileName = SYSTEM_BOOTANIMATION_FILE;
+    }
     return NO_ERROR;
 }
 
@@ -309,7 +302,7 @@
     bool r;
     // We have no bootanimation file, so we use the stock android logo
     // animation.
-    if (mZip == NULL) {
+    if (mZipFileName.isEmpty()) {
         r = android();
     } else {
         r = movie();
@@ -429,16 +422,17 @@
     return true;
 }
 
-bool BootAnimation::readFile(const char* name, String8& outString)
+
+static bool readFile(ZipFileRO* zip, const char* name, String8& outString)
 {
-    ZipEntryRO entry = mZip->findEntryByName(name);
+    ZipEntryRO entry = zip->findEntryByName(name);
     ALOGE_IF(!entry, "couldn't find %s", name);
     if (!entry) {
         return false;
     }
 
-    FileMap* entryMap = mZip->createEntryFileMap(entry);
-    mZip->releaseEntry(entry);
+    FileMap* entryMap = zip->createEntryFileMap(entry);
+    zip->releaseEntry(entry);
     ALOGE_IF(!entryMap, "entryMap is null");
     if (!entryMap) {
         return false;
@@ -512,18 +506,18 @@
     glBindTexture(GL_TEXTURE_2D, 0);
 }
 
-bool BootAnimation::movie()
+bool BootAnimation::parseAnimationDesc(Animation& animation)
 {
     String8 desString;
 
-    if (!readFile("desc.txt", desString)) {
+    if (!readFile(animation.zip, "desc.txt", desString)) {
         return false;
     }
     char const* s = desString.string();
 
     // Create and initialize an AudioPlayer if we have an audio_conf.txt file
     String8 audioConf;
-    if (readFile("audio_conf.txt", audioConf)) {
+    if (readFile(animation.zip, "audio_conf.txt", audioConf)) {
         mAudioPlayer = new AudioPlayer;
         if (!mAudioPlayer->init(audioConf.string())) {
             ALOGE("mAudioPlayer.init failed");
@@ -531,8 +525,6 @@
         }
     }
 
-    Animation animation;
-
     // Parse the description file
     for (;;) {
         const char* endl = strstr(s, "\n");
@@ -564,6 +556,7 @@
             part.path = path;
             part.clockPosY = clockPosY;
             part.audioFile = NULL;
+            part.animation = NULL;
             if (!parseColor(color, part.backgroundColor)) {
                 ALOGE("> invalid color '#%s'", color);
                 part.backgroundColor[0] = 0.0f;
@@ -572,13 +565,29 @@
             }
             animation.parts.add(part);
         }
-
+        else if (strcmp(l, "$SYSTEM") == 0) {
+            // ALOGD("> SYSTEM");
+            Animation::Part part;
+            part.playUntilComplete = false;
+            part.count = 1;
+            part.pause = 0;
+            part.audioFile = NULL;
+            part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE));
+            if (part.animation != NULL)
+                animation.parts.add(part);
+        }
         s = ++endl;
     }
 
+    return true;
+}
+
+bool BootAnimation::preloadZip(Animation& animation)
+{
     // read all the data structures
     const size_t pcount = animation.parts.size();
     void *cookie = NULL;
+    ZipFileRO* mZip = animation.zip;
     if (!mZip->startIteration(&cookie)) {
         return false;
     }
@@ -624,6 +633,16 @@
 
     mZip->endIteration(cookie);
 
+    return true;
+}
+
+bool BootAnimation::movie()
+{
+
+    Animation* animation = loadAnimation(mZipFileName);
+    if (animation == NULL)
+        return false;
+
     // Blend required to draw time on top of animation frames.
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     glShadeModel(GL_FLAT);
@@ -645,6 +664,19 @@
         mClockEnabled = clockTextureInitialized;
     }
 
+    playAnimation(*animation);
+    releaseAnimation(animation);
+
+    if (clockTextureInitialized) {
+        glDeleteTextures(1, &mClock.name);
+    }
+
+    return false;
+}
+
+bool BootAnimation::playAnimation(const Animation& animation)
+{
+    const size_t pcount = animation.parts.size();
     const int xc = (mWidth - animation.width) / 2;
     const int yc = ((mHeight - animation.height) / 2);
     nsecs_t frameDuration = s2ns(1) / animation.fps;
@@ -657,6 +689,14 @@
         const size_t fcount = part.frames.size();
         glBindTexture(GL_TEXTURE_2D, 0);
 
+        // Handle animation package
+        if (part.animation != NULL) {
+            playAnimation(*part.animation);
+            if (exitPending())
+                break;
+            continue; //to next part
+        }
+
         for (int r=0 ; !part.count || r<part.count ; r++) {
             // Exit any non playuntil complete parts immediately
             if(exitPending() && !part.playUntilComplete)
@@ -744,14 +784,46 @@
             }
         }
     }
-
-    if (clockTextureInitialized) {
-        glDeleteTextures(1, &mClock.name);
-    }
-
-    return false;
+    return true;
 }
 
+void BootAnimation::releaseAnimation(Animation* animation) const
+{
+    for (Vector<Animation::Part>::iterator it = animation->parts.begin(),
+         e = animation->parts.end(); it != e; ++it) {
+        if (it->animation)
+            releaseAnimation(it->animation);
+    }
+    if (animation->zip)
+        delete animation->zip;
+    delete animation;
+}
+
+BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn)
+{
+    if (mLoadedFiles.indexOf(fn) >= 0) {
+        ALOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
+            fn.string());
+        return NULL;
+    }
+    ZipFileRO *zip = ZipFileRO::open(fn);
+    if (zip == NULL) {
+        ALOGE("Failed to open animation zip \"%s\": %s",
+            fn.string(), strerror(errno));
+        return NULL;
+    }
+
+    Animation *animation =  new Animation;
+    animation->fileName = fn;
+    animation->zip = zip;
+    mLoadedFiles.add(animation->fileName);
+
+    parseAnimationDesc(*animation);
+    preloadZip(*animation);
+
+    mLoadedFiles.remove(fn);
+    return animation;
+}
 // ---------------------------------------------------------------------------
 
 }
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 83e2b38..d49e1ec 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -76,19 +76,27 @@
             bool playUntilComplete;
             float backgroundColor[3];
             FileMap* audioFile;
+            Animation* animation;
         };
         int fps;
         int width;
         int height;
         Vector<Part> parts;
+        String8 audioConf;
+        String8 fileName;
+        ZipFileRO* zip;
     };
 
     status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
     status_t initTexture(const Animation::Frame& frame);
     bool android();
-    bool readFile(const char* name, String8& outString);
     bool movie();
     void drawTime(const Texture& clockTex, const int yPos);
+    Animation* loadAnimation(const String8&);
+    bool playAnimation(const Animation&);
+    void releaseAnimation(Animation*) const;
+    bool parseAnimationDesc(Animation&);
+    bool preloadZip(Animation &animation);
 
     void checkExit();
 
@@ -104,8 +112,9 @@
     EGLDisplay  mSurface;
     sp<SurfaceControl> mFlingerSurfaceControl;
     sp<Surface> mFlingerSurface;
-    ZipFileRO   *mZip;
     bool        mClockEnabled;
+    String8     mZipFileName;
+    SortedVector<String8> mLoadedFiles;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 663f297..c6a5152 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -967,7 +967,7 @@
         AnimationHandler animationHandler = AnimationHandler.getInstance();
         animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
 
-        if (mStartDelay == 0) {
+        if (mStartDelay == 0 || mSeekFraction >= 0) {
             // If there's no start delay, init the animation and notify start listeners right away
             // to be consistent with the previous behavior. Otherwise, postpone this until the first
             // frame after the start delay.
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cc1d68e..ee17e8a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -57,6 +57,7 @@
 import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.ShapeDrawable;
+import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.net.Uri;
@@ -91,6 +92,8 @@
 import android.view.ContextThemeWrapper;
 import android.view.DragEvent;
 import android.view.DropPermissions;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
@@ -1679,10 +1682,16 @@
     }
 
     @Override
-    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
+    public void onProvideKeyboardShortcuts(
+            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
         if (menu == null) {
           return;
         }
+        final InputDevice inputDevice = InputManager.getInstance().getInputDevice(deviceId);
+        if (inputDevice == null) {
+            return;
+        }
+        final KeyCharacterMap keyCharacterMap = inputDevice.getKeyCharacterMap();
         KeyboardShortcutGroup group = null;
         int menuSize = menu.size();
         for (int i = 0; i < menuSize; ++i) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2d33a2c..6380801 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -1790,7 +1791,7 @@
 
         public int taskWidth;
         public int taskHeight;
-        public int screenOrientation;
+        public int screenOrientation = Configuration.ORIENTATION_UNDEFINED;
 
         public TaskThumbnailInfo() {
             // Do nothing
@@ -1807,7 +1808,16 @@
         public void reset() {
             taskWidth = 0;
             taskHeight = 0;
-            screenOrientation = 0;
+            screenOrientation = Configuration.ORIENTATION_UNDEFINED;
+        }
+
+        /**
+         * Copies from another ThumbnailInfo.
+         */
+        public void copyFrom(TaskThumbnailInfo o) {
+            taskWidth = o.taskWidth;
+            taskHeight = o.taskHeight;
+            screenOrientation = o.screenOrientation;
         }
 
         /** @hide */
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 5b7dae6..ed590e6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -272,12 +272,17 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<PermissionInfo> queryPermissionsByGroup(String group, int flags)
             throws NameNotFoundException {
         try {
-            List<PermissionInfo> pi = mPM.queryPermissionsByGroup(group, flags).getList();
-            if (pi != null) {
-                return pi;
+            ParceledListSlice<PermissionInfo> parceledList =
+                    mPM.queryPermissionsByGroup(group, flags);
+            if (parceledList != null) {
+                List<PermissionInfo> pi = parceledList.getList();
+                if (pi != null) {
+                    return pi;
+                }
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -288,7 +293,7 @@
 
     @Override
     public PermissionGroupInfo getPermissionGroupInfo(String name,
-                                                      int flags) throws NameNotFoundException {
+            int flags) throws NameNotFoundException {
         try {
             PermissionGroupInfo pgi = mPM.getPermissionGroupInfo(name, flags);
             if (pgi != null) {
@@ -302,9 +307,15 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
         try {
-            return mPM.getAllPermissionGroups(flags).getList();
+            ParceledListSlice<PermissionGroupInfo> parceledList =
+                    mPM.getAllPermissionGroups(flags);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -439,9 +450,15 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public FeatureInfo[] getSystemAvailableFeatures() {
         try {
-            final List<FeatureInfo> list = mPM.getSystemAvailableFeatures().getList();
+            ParceledListSlice<FeatureInfo> parceledList =
+                    mPM.getSystemAvailableFeatures();
+            if (parceledList == null) {
+                return new FeatureInfo[0];
+            }
+            final List<FeatureInfo> list = parceledList.getList();
             final FeatureInfo[] res = new FeatureInfo[list.size()];
             for (int i = 0; i < res.length; i++) {
                 res[i] = list.get(i);
@@ -636,10 +653,15 @@
 
     /** @hide */
     @Override
+    @SuppressWarnings("unchecked")
     public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
         try {
-            ParceledListSlice<PackageInfo> slice = mPM.getInstalledPackages(flags, userId);
-            return slice.getList();
+            ParceledListSlice<PackageInfo> parceledList =
+                    mPM.getInstalledPackages(flags, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -651,9 +673,12 @@
             String[] permissions, int flags) {
         final int userId = mContext.getUserId();
         try {
-            ParceledListSlice<PackageInfo> slice = mPM.getPackagesHoldingPermissions(
-                    permissions, flags, userId);
-            return slice.getList();
+            ParceledListSlice<PackageInfo> parceledList =
+                    mPM.getPackagesHoldingPermissions(permissions, flags, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -664,8 +689,12 @@
     public List<ApplicationInfo> getInstalledApplications(int flags) {
         final int userId = mContext.getUserId();
         try {
-            ParceledListSlice<ApplicationInfo> slice = mPM.getInstalledApplications(flags, userId);
-            return slice.getList();
+            ParceledListSlice<ApplicationInfo> parceledList =
+                    mPM.getInstalledApplications(flags, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -770,20 +799,25 @@
 
     /** @hide Same as above but for a specific user */
     @Override
+    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
-                                                   int flags, int userId) {
+            int flags, int userId) {
         try {
-            return mPM.queryIntentActivities(
-                intent,
-                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags,
-                userId).getList();
+            ParceledListSlice<ResolveInfo> parceledList =
+                    mPM.queryIntentActivities(intent,
+                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                            flags, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentActivityOptions(
         ComponentName caller, Intent[] specifics, Intent intent,
         int flags) {
@@ -807,10 +841,13 @@
         }
 
         try {
-            return mPM
-                    .queryIntentActivityOptions(caller, specifics, specificTypes, intent,
-                            intent.resolveTypeIfNeeded(resolver), flags, mContext.getUserId())
-                    .getList();
+            ParceledListSlice<ResolveInfo> parceledList =
+                    mPM.queryIntentActivityOptions(caller, specifics, specificTypes, intent,
+                    intent.resolveTypeIfNeeded(resolver), flags, mContext.getUserId());
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -820,13 +857,17 @@
      * @hide
      */
     @Override
+    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) {
         try {
-            return mPM.queryIntentReceivers(
-                intent,
-                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags,
-                userId).getList();
+            ParceledListSlice<ResolveInfo> parceledList =
+                    mPM.queryIntentReceivers(intent,
+                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                            flags,  userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -851,13 +892,17 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
         try {
-            return mPM.queryIntentServices(
-                intent,
-                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags,
-                userId).getList();
+            ParceledListSlice<ResolveInfo> parceledList =
+                    mPM.queryIntentServices(intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -869,12 +914,18 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentContentProvidersAsUser(
             Intent intent, int flags, int userId) {
         try {
-            return mPM.queryIntentContentProviders(intent,
-                    intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, userId)
-                    .getList();
+            ParceledListSlice<ResolveInfo> parceledList =
+                    mPM.queryIntentContentProviders(intent,
+                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                            flags, userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -901,12 +952,13 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<ProviderInfo> queryContentProviders(String processName,
-                                                    int uid, int flags) {
+            int uid, int flags) {
         try {
-            ParceledListSlice<ProviderInfo> slice
-                    = mPM.queryContentProviders(processName, uid, flags);
-            return slice != null ? slice.getList() : null;
+            ParceledListSlice<ProviderInfo> slice =
+                    mPM.queryContentProviders(processName, uid, flags);
+            return slice != null ? slice.getList() : Collections.<ProviderInfo>emptyList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -930,10 +982,16 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<InstrumentationInfo> queryInstrumentation(
         String targetPackage, int flags) {
         try {
-            return mPM.queryInstrumentation(targetPackage, flags).getList();
+            ParceledListSlice<InstrumentationInfo> parceledList =
+                    mPM.queryInstrumentation(targetPackage, flags);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1575,18 +1633,30 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
         try {
-            return mPM.getIntentFilterVerifications(packageName).getList();
+            ParceledListSlice<IntentFilterVerificationInfo> parceledList =
+                    mPM.getIntentFilterVerifications(packageName);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<IntentFilter> getAllIntentFilters(String packageName) {
         try {
-            return mPM.getAllIntentFilters(packageName).getList();
+            ParceledListSlice<IntentFilter> parceledList =
+                    mPM.getAllIntentFilters(packageName);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 79461b4..0bb1097 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -1082,13 +1082,6 @@
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
-    }
-
-    /**
      * @return The activity associated with this dialog, or null if there is no associated activity.
      */
     private ComponentName getAssociatedActivity() {
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index ddd0ae9..d89c0e0 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -537,12 +537,10 @@
                 setTransitioningViewsVisiblity(View.INVISIBLE, false);
             }
             TransitionManager.beginDelayedTransition(decorView, transition);
-            if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
-                mSharedElements.get(0).invalidate();
-            }
             if (startEnterTransition) {
-                setTransitioningViewsVisiblity(View.VISIBLE, true);
+                setTransitioningViewsVisiblity(View.VISIBLE, false);
             }
+            decorView.invalidate();
         } else {
             transitionStarted();
         }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index d54ffa0..ce017f6 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -268,7 +268,8 @@
         if (transition != null && decorView != null && mTransitioningViews != null) {
             setTransitioningViewsVisiblity(View.VISIBLE, false);
             TransitionManager.beginDelayedTransition(decorView, transition);
-            setTransitioningViewsVisiblity(View.INVISIBLE, true);
+            setTransitioningViewsVisiblity(View.INVISIBLE, false);
+            decorView.invalidate();
         } else {
             transitionStarted();
         }
@@ -367,7 +368,7 @@
             scheduleGhostVisibilityChange(View.VISIBLE);
             setGhostVisibility(View.VISIBLE);
             if (viewsTransition != null) {
-                setTransitioningViewsVisiblity(View.INVISIBLE, true);
+                setTransitioningViewsVisiblity(View.INVISIBLE, false);
             }
             decorView.invalidate();
         } else {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 6870bbf..f7a4557 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1429,16 +1429,20 @@
         final Context context = getContext();
         final int version = context != null ? context.getApplicationInfo().targetSdkVersion : 0;
         if (version >= Build.VERSION_CODES.N) {
-            if (savedInstanceState != null) {
-                Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
-                if (p != null) {
-                    if (mChildFragmentManager == null) {
-                        instantiateChildFragmentManager();
-                    }
-                    mChildFragmentManager.restoreAllState(p, mChildNonConfig);
-                    mChildNonConfig = null;
-                    mChildFragmentManager.dispatchCreate();
+            restoreChildFragmentState(savedInstanceState, true);
+        }
+    }
+
+    void restoreChildFragmentState(@Nullable Bundle savedInstanceState, boolean provideNonConfig) {
+        if (savedInstanceState != null) {
+            Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
+            if (p != null) {
+                if (mChildFragmentManager == null) {
+                    instantiateChildFragmentManager();
                 }
+                mChildFragmentManager.restoreAllState(p, provideNonConfig ? mChildNonConfig : null);
+                mChildNonConfig = null;
+                mChildFragmentManager.dispatchCreate();
             }
         }
     }
@@ -1692,6 +1696,18 @@
      */
     public void onDetach() {
         mCalled = true;
+
+        // Destroy the child FragmentManager if we still have it here.
+        // We won't unless we're retaining our instance and if we do,
+        // our child FragmentManager instance state will have already been saved.
+        if (mChildFragmentManager != null) {
+            if (!mRetaining) {
+                throw new IllegalStateException("Child FragmentManager of " + this + " was not "
+                        + " destroyed and this fragment is not retaining instance");
+            }
+            mChildFragmentManager.dispatchDestroy();
+            mChildFragmentManager = null;
+        }
     }
 
     /**
@@ -2252,16 +2268,7 @@
         final Context context = getContext();
         final int version = context != null ? context.getApplicationInfo().targetSdkVersion : 0;
         if (version < Build.VERSION_CODES.N) {
-            if (savedInstanceState != null) {
-                Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
-                if (p != null) {
-                    if (mChildFragmentManager == null) {
-                        instantiateChildFragmentManager();
-                    }
-                    mChildFragmentManager.restoreAllState(p, null);
-                    mChildFragmentManager.dispatchCreate();
-                }
-            }
+            restoreChildFragmentState(savedInstanceState, false);
         }
     }
 
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 0631943..2852baf 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -941,6 +941,9 @@
 
                     if (!f.mRetaining) {
                         f.performCreate(f.mSavedFragmentState);
+                    } else {
+                        f.restoreChildFragmentState(f.mSavedFragmentState, true);
+                        f.mState = Fragment.CREATED;
                     }
                     f.mRetaining = false;
                     if (f.mFromLayout) {
@@ -1009,6 +1012,9 @@
                         f.mSavedFragmentState = null;
                     }
                 case Fragment.ACTIVITY_CREATED:
+                    if (newState > Fragment.ACTIVITY_CREATED) {
+                        f.mState = Fragment.STOPPED;
+                    }
                 case Fragment.STOPPED:
                     if (newState > Fragment.STOPPED) {
                         if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
@@ -1108,7 +1114,7 @@
                             if (!f.mRetaining) {
                                 f.performDestroy();
                             } else {
-                                f.mState = Fragment.INITIALIZING;
+                                f.mState = Fragment.CREATED;
                             }
 
                             f.mCalled = false;
@@ -1124,7 +1130,6 @@
                                     f.mHost = null;
                                     f.mParentFragment = null;
                                     f.mFragmentManager = null;
-                                    f.mChildFragmentManager = null;
                                 }
                             }
                         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cb1bee5..8423de8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
@@ -568,6 +567,12 @@
     public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
 
     /**
+     * Special value of {@link #color} used as a place holder for an invalid color.
+     */
+    @ColorInt
+    private static final int COLOR_INVALID = 1;
+
+    /**
      * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 
      * the notification's presence and contents in untrusted situations (namely, on the secure 
      * lockscreen).
@@ -2092,6 +2097,12 @@
         private boolean mColorUtilInited = false;
 
         /**
+         * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}.
+         */
+        private int mCachedContrastColor = COLOR_INVALID;
+        private int mCachedContrastColorIsFor = COLOR_INVALID;
+
+        /**
          * Constructs a new Builder with the defaults:
          *
 
@@ -3074,7 +3085,7 @@
                         R.id.progress, ColorStateList.valueOf(mContext.getColor(
                                 R.color.notification_progress_background_color)));
                 if (mN.color != COLOR_DEFAULT) {
-                    ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
+                    ColorStateList colorStateList = ColorStateList.valueOf(resolveContrastColor());
                     contentView.setProgressTintList(R.id.progress, colorStateList);
                     contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
                 }
@@ -3135,10 +3146,10 @@
         }
 
         private void bindExpandButton(RemoteViews contentView) {
-            contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+            contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(),
                     PorterDuff.Mode.SRC_ATOP, -1);
             contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
-                    resolveColor());
+                    resolveContrastColor());
         }
 
         private void bindHeaderChronometerAndTime(RemoteViews contentView) {
@@ -3180,7 +3191,7 @@
                 return;
             }
             contentView.setTextViewText(R.id.app_name_text, appName);
-            contentView.setTextColor(R.id.app_name_text, resolveColor());
+            contentView.setTextColor(R.id.app_name_text, resolveContrastColor());
         }
 
         private void bindSmallIcon(RemoteViews contentView) {
@@ -3405,7 +3416,7 @@
                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
             }
             if (mN.color != COLOR_DEFAULT) {
-                button.setTextColor(R.id.action0, mN.color);
+                button.setTextColor(R.id.action0, resolveContrastColor());
             }
             return button;
         }
@@ -3432,12 +3443,12 @@
         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView) {
             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
             if (colorable) {
-                contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
+                contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(),
                         PorterDuff.Mode.SRC_ATOP, -1);
 
             }
             contentView.setInt(R.id.notification_header, "setOriginalIconColor",
-                    colorable ? resolveColor() : NotificationHeaderView.NO_COLOR);
+                    colorable ? resolveContrastColor() : NotificationHeaderView.NO_COLOR);
         }
 
         /**
@@ -3449,7 +3460,7 @@
             if (largeIcon != null && isLegacy()
                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 // resolve color will fall back to the default when legacy
-                contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
+                contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(),
                         PorterDuff.Mode.SRC_ATOP, -1);
             }
         }
@@ -3460,11 +3471,14 @@
             }
         }
 
-        int resolveColor() {
-            if (mN.color == COLOR_DEFAULT) {
-                return mContext.getColor(R.color.notification_icon_default_color);
+        int resolveContrastColor() {
+            if (mCachedContrastColorIsFor == mN.color && mCachedContrastColor != COLOR_INVALID) {
+                return mCachedContrastColor;
             }
-            return mN.color;
+            final int contrasted = NotificationColorUtil.resolveContrastColor(mContext, mN.color);
+
+            mCachedContrastColorIsFor = mN.color;
+            return mCachedContrastColor = contrasted;
         }
 
         /**
@@ -4427,7 +4441,7 @@
 
                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
                     final RemoteViews button = generateMediaActionButton(action,
-                            mBuilder.resolveColor());
+                            mBuilder.resolveContrastColor());
                     view.addView(com.android.internal.R.id.media_actions, button);
                 }
             }
@@ -4460,7 +4474,7 @@
                 big.removeAllViews(com.android.internal.R.id.media_actions);
                 for (int i = 0; i < actionCount; i++) {
                     final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
-                            mBuilder.resolveColor());
+                            mBuilder.resolveContrastColor());
                     big.addView(com.android.internal.R.id.media_actions, button);
                 }
             }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index dabc652..e3fb161 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -454,8 +454,21 @@
      */
     boolean performDexOptIfNeeded(String packageName, String instructionSet);
 
-    boolean performDexOpt(String packageName, String instructionSet, boolean useProfiles,
-            boolean extractOnly, boolean force);
+    /**
+     * Ask the package manager to perform a dex-opt for the given reason. The package
+     * manager will map the reason to a compiler filter according to the current system
+     * configuration.
+     */
+    boolean performDexOpt(String packageName, String instructionSet, boolean checkProfiles,
+            int compileReason, boolean force);
+    /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
+    boolean performDexOptMode(String packageName, String instructionSet, boolean checkProfiles,
+            String targetCompilerFilter, boolean force);
 
     void forceDexOpt(String packageName);
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index db7ca2a..84605bb 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1158,10 +1158,15 @@
         StrictJarFile jarFile = null;
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
+            // Ignore signature stripping protections when verifying APKs from system partition.
+            // For those APKs we only care about extracting signer certificates, and don't care
+            // about verifying integrity.
+            boolean signatureSchemeRollbackProtectionsEnforced =
+                    (parseFlags & PARSE_IS_SYSTEM) == 0;
             jarFile = new StrictJarFile(
                     apkPath,
-                    !verified // whether to verify JAR signature
-                    );
+                    !verified, // whether to verify JAR signature
+                    signatureSchemeRollbackProtectionsEnforced);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
             // Always verify manifest, regardless of source
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 3139151..e8a3438 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -216,6 +216,7 @@
         lastLoggedInTime = orig.lastLoggedInTime;
         partial = orig.partial;
         profileGroupId = orig.profileGroupId;
+        restrictedProfileParentId = orig.restrictedProfileParentId;
         guestToRemove = orig.guestToRemove;
     }
 
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 841e5b0..63ee8d2 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -652,6 +652,14 @@
 
     public static final int TYPE_ALL = -1;
 
+    /**
+     * The lowest sensor type vendor defined sensors can use.
+     *
+     * All vendor sensor types are greater than or equal to this constant.
+     *
+     */
+    public static final int TYPE_DEVICE_PRIVATE_BASE = 0x10000;
+
     // If this flag is set, the sensor defined as a wake up sensor. This field and REPORTING_MODE_*
     // constants are defined as flags in sensors.h. Modify at both places if needed.
     private static final int SENSOR_FLAG_WAKE_UP_SENSOR = 1;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 9e360e1..20c2168 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -200,14 +200,6 @@
      */
     public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
 
-    /**
-     * Sent by ConnectivityService to the NetworkAgent to install an APF program in the network
-     * chipset for use to filter packets.
-     *
-     * obj = byte[] containing the APF program bytecode.
-     */
-    public static final int CMD_PUSH_APF_PROGRAM = BASE + 16;
-
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
         this(looper, context, logTag, ni, nc, lp, score, null);
@@ -327,10 +319,6 @@
                 preventAutomaticReconnect();
                 break;
             }
-            case CMD_PUSH_APF_PROGRAM: {
-                installPacketFilter((byte[]) msg.obj);
-                break;
-            }
         }
     }
 
@@ -506,15 +494,6 @@
     protected void preventAutomaticReconnect() {
     }
 
-    /**
-     * Install a packet filter.
-     * @param filter an APF program to filter incoming packets.
-     * @return {@code true} if filter successfully installed, {@code false} otherwise.
-     */
-    protected boolean installPacketFilter(byte[] filter) {
-        return false;
-    }
-
     protected void log(String s) {
         Log.d(LOG_TAG, "NetworkAgent: " + s);
     }
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 748699e..5511a24 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -56,22 +56,6 @@
      */
     public String subscriberId;
 
-    /**
-     * Version of APF instruction set supported for packet filtering. 0 indicates no support for
-     * packet filtering using APF programs.
-     */
-    public int apfVersionSupported;
-
-    /**
-     * Maximum size of APF program allowed.
-     */
-    public int maximumApfProgramSize;
-
-    /**
-     * Format of packets passed to APF filter. Should be one of ARPHRD_*
-     */
-    public int apfPacketFormat;
-
     public NetworkMisc() {
     }
 
@@ -81,9 +65,6 @@
             explicitlySelected = nm.explicitlySelected;
             acceptUnvalidated = nm.acceptUnvalidated;
             subscriberId = nm.subscriberId;
-            apfVersionSupported = nm.apfVersionSupported;
-            maximumApfProgramSize = nm.maximumApfProgramSize;
-            apfPacketFormat = nm.apfPacketFormat;
         }
     }
 
@@ -98,9 +79,6 @@
         out.writeInt(explicitlySelected ? 1 : 0);
         out.writeInt(acceptUnvalidated ? 1 : 0);
         out.writeString(subscriberId);
-        out.writeInt(apfVersionSupported);
-        out.writeInt(maximumApfProgramSize);
-        out.writeInt(apfPacketFormat);
     }
 
     public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -111,9 +89,6 @@
             networkMisc.explicitlySelected = in.readInt() != 0;
             networkMisc.acceptUnvalidated = in.readInt() != 0;
             networkMisc.subscriberId = in.readString();
-            networkMisc.apfVersionSupported = in.readInt();
-            networkMisc.maximumApfProgramSize = in.readInt();
-            networkMisc.apfPacketFormat = in.readInt();
             return networkMisc;
         }
 
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index d847cd0..b32b2cc 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -18,6 +18,7 @@
 
 import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
@@ -68,6 +69,7 @@
     public static final int MATCH_MOBILE_WILDCARD = 6;
     public static final int MATCH_WIFI_WILDCARD = 7;
     public static final int MATCH_BLUETOOTH = 8;
+    public static final int MATCH_PROXY = 9;
 
     /**
      * Set of {@link NetworkInfo#getType()} that reflect data usage.
@@ -157,6 +159,14 @@
         return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
     }
 
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
+     * networks together.
+     */
+    public static NetworkTemplate buildTemplateProxy() {
+        return new NetworkTemplate(MATCH_PROXY, null, null);
+    }
+
     private final int mMatchRule;
     private final String mSubscriberId;
 
@@ -293,6 +303,8 @@
                 return matchesWifiWildcard(ident);
             case MATCH_BLUETOOTH:
                 return matchesBluetooth(ident);
+            case MATCH_PROXY:
+                return matchesProxy(ident);
             default:
                 throw new IllegalArgumentException("unknown network template");
         }
@@ -401,6 +413,13 @@
         return false;
     }
 
+    /**
+     * Check if matches Proxy network template.
+     */
+    private boolean matchesProxy(NetworkIdentity ident) {
+        return ident.mType == TYPE_PROXY;
+    }
+
     private static String getMatchRuleName(int matchRule) {
         switch (matchRule) {
             case MATCH_MOBILE_3G_LOWER:
@@ -419,6 +438,8 @@
                 return "WIFI_WILDCARD";
             case MATCH_BLUETOOTH:
                 return "BLUETOOTH";
+            case MATCH_PROXY:
+                return "PROXY";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e730ad8..be82d56a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -30,6 +30,8 @@
 import android.telephony.SignalStrength;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
+import android.util.MutableBoolean;
+import android.util.Pair;
 import android.util.Printer;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -5317,26 +5319,28 @@
         }
 
         if (apps != null) {
-            SparseArray<ArrayList<String>> uids = new SparseArray<ArrayList<String>>();
+            SparseArray<Pair<ArrayList<String>, MutableBoolean>> uids = new SparseArray<>();
             for (int i=0; i<apps.size(); i++) {
                 ApplicationInfo ai = apps.get(i);
-                ArrayList<String> pkgs = uids.get(ai.uid);
+                Pair<ArrayList<String>, MutableBoolean> pkgs = uids.get(
+                        UserHandle.getAppId(ai.uid));
                 if (pkgs == null) {
-                    pkgs = new ArrayList<String>();
-                    uids.put(ai.uid, pkgs);
+                    pkgs = new Pair<>(new ArrayList<String>(), new MutableBoolean(false));
+                    uids.put(UserHandle.getAppId(ai.uid), pkgs);
                 }
-                pkgs.add(ai.packageName);
+                pkgs.first.add(ai.packageName);
             }
             SparseArray<? extends Uid> uidStats = getUidStats();
             final int NU = uidStats.size();
             String[] lineArgs = new String[2];
             for (int i=0; i<NU; i++) {
-                int uid = uidStats.keyAt(i);
-                ArrayList<String> pkgs = uids.get(uid);
-                if (pkgs != null) {
-                    for (int j=0; j<pkgs.size(); j++) {
+                int uid = UserHandle.getAppId(uidStats.keyAt(i));
+                Pair<ArrayList<String>, MutableBoolean> pkgs = uids.get(uid);
+                if (pkgs != null && !pkgs.second.value) {
+                    pkgs.second.value = true;
+                    for (int j=0; j<pkgs.first.size(); j++) {
                         lineArgs[0] = Integer.toString(uid);
-                        lineArgs[1] = pkgs.get(j);
+                        lineArgs[1] = pkgs.first.get(j);
                         dumpLine(pw, 0 /* uid */, "i" /* category */, UID_DATA,
                                 (Object[])lineArgs);
                     }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index de8b690..d0029e1 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -91,6 +91,12 @@
     /** The name of the hardware (from the kernel command line or /proc). */
     public static final String HARDWARE = getString("ro.hardware");
 
+    /**
+     * Whether this build was for an emulator device.
+     * @hide
+     */
+    public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1");
+
     /** A hardware serial number, if available.  Alphanumeric only, case-insensitive. */
     public static final String SERIAL = getString("ro.serialno");
 
diff --git a/core/java/android/os/HardwarePropertiesManager.java b/core/java/android/os/HardwarePropertiesManager.java
index 0f2e33d..f13e5b5 100644
--- a/core/java/android/os/HardwarePropertiesManager.java
+++ b/core/java/android/os/HardwarePropertiesManager.java
@@ -101,7 +101,8 @@
      *         {@link #UNDEFINED_TEMPERATURE} if undefined.
      *         Empty if platform doesn't provide the queried temperature.
      *
-     * @throws SecurityException if a non profile or device owner tries to call this method.
+     * @throws SecurityException if something other than the profile or device owner, or the
+     *        current VR service tries to retrieve information provided by this service.
     */
     public @NonNull float[] getDeviceTemperatures(@DeviceTemperatureType int type,
             @TemperatureSource int source) {
@@ -137,7 +138,8 @@
      *         each unplugged core.
      *         Empty if CPU usage is not supported on this system.
      *
-     * @throws SecurityException if a non profile or device owner tries to call this method.
+     * @throws SecurityException if something other than the profile or device owner, or the
+     *        current VR service tries to retrieve information provided by this service.
      */
     public @NonNull CpuUsageInfo[] getCpuUsages() {
         try {
@@ -153,7 +155,8 @@
      * @return an array of float fan speeds in RPM. Empty if there are no fans or fan speed is not
      * supported on this system.
      *
-     * @throws SecurityException if a non profile or device owner tries to call this method.
+     * @throws SecurityException if something other than the profile or device owner, or the
+     *        current VR service tries to retrieve information provided by this service.
      */
     public @NonNull float[] getFanSpeeds() {
         try {
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 54d20d3..34c0b14 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,7 +16,6 @@
 
 package android.os.storage;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
@@ -306,8 +305,8 @@
     }
 
     /**
-     * Builds an intent to give access to a standard storage directory after obtaining the user's
-     * approval.
+     * Builds an intent to give access to a standard storage directory or entire volume after
+     * obtaining the user's approval.
      * <p>
      * When invoked, the system will ask the user to grant access to the requested directory (and
      * its descendants). The result of the request will be returned to the activity through the
@@ -322,16 +321,21 @@
      * {@link Context#getExternalCacheDirs()}, or
      * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
      *
+     * <strong>NOTE: </strong>requesting access to the entire volume is not recommended and it will
+     * result in a stronger message displayed to the user, which may cause the user to reject
+     * the request.
+     *
      * @param directoryName must be one of
      * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
      * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
      * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
      * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
-     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}, or
+     * {code null} to request access to the entire volume.
      *
      * @see DocumentsContract
      */
-    public Intent createAccessIntent(@NonNull String directoryName) {
+    public Intent createAccessIntent(String directoryName) {
         final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
         intent.putExtra(EXTRA_STORAGE_VOLUME, this);
         intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 469a4ea..63bf885 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -61,6 +61,15 @@
     void setStatus(in PrintJobId printJobId, in CharSequence status);
 
     /**
+     * Set the status of this print job
+     *
+     * @param printJobId The print job to update
+     * @param status The new status as a string resource
+     * @param appPackageName App package name the resource belongs to
+     */
+    void setStatusRes(in PrintJobId printJobId, int status, in CharSequence appPackageName);
+
+    /**
      * Handle that a custom icon for a printer was loaded.
      *
      * @param printerId the id of the printer the icon belongs to
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 7e3a72f..f134943 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -21,7 +21,10 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.annotation.TestApi;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -181,7 +184,11 @@
     private float mProgress;
 
     /** A short string describing the status of this job. */
-    private CharSequence mStatus;
+    private @Nullable CharSequence mStatus;
+
+    /** A string resource describing the status of this job. */
+    private @StringRes int mStatusRes;
+    private @Nullable CharSequence mStatusResAppPackageName;
 
     /** Advanced printer specific options. */
     private Bundle mAdvancedOptions;
@@ -210,6 +217,8 @@
         mDocumentInfo = other.mDocumentInfo;
         mProgress = other.mProgress;
         mStatus = other.mStatus;
+        mStatusRes = other.mStatusRes;
+        mStatusResAppPackageName = other.mStatusResAppPackageName;
         mCanceling = other.mCanceling;
         mAdvancedOptions = other.mAdvancedOptions;
     }
@@ -235,8 +244,14 @@
         mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
         mProgress = parcel.readFloat();
         mStatus = parcel.readCharSequence();
+        mStatusRes = parcel.readInt();
+        mStatusResAppPackageName = parcel.readCharSequence();
         mCanceling = (parcel.readInt() == 1);
         mAdvancedOptions = parcel.readBundle();
+
+        if (mAdvancedOptions != null) {
+            Preconditions.checkArgument(!mAdvancedOptions.containsKey(null));
+        }
     }
 
     /**
@@ -370,10 +385,28 @@
      * @hide
      */
     public void setStatus(@Nullable CharSequence status) {
+        mStatusRes = 0;
+        mStatusResAppPackageName = null;
+
         mStatus = status;
     }
 
     /**
+     * Sets the status of the print job.
+     *
+     * @param status The new status as a string resource
+     * @param appPackageName App package name the resource belongs to
+     *
+     * @hide
+     */
+    public void setStatus(@StringRes int status, @NonNull CharSequence appPackageName) {
+        mStatus = null;
+
+        mStatusRes = status;
+        mStatusResAppPackageName = appPackageName;
+    }
+
+    /**
      * Sets the owning application id.
      *
      * @return The owning app id.
@@ -633,6 +666,8 @@
         parcel.writeParcelable(mDocumentInfo, 0);
         parcel.writeFloat(mProgress);
         parcel.writeCharSequence(mStatus);
+        parcel.writeInt(mStatusRes);
+        parcel.writeCharSequence(mStatusResAppPackageName);
         parcel.writeInt(mCanceling ? 1 : 0);
         parcel.writeBundle(mAdvancedOptions);
     }
@@ -659,6 +694,9 @@
         builder.append(", progress: " + mProgress);
         builder.append(", status: " + (mStatus != null
                 ? mStatus.toString() : null));
+        builder.append(", statusRes: " + mStatusRes);
+        builder.append(", statusResAppPackageName: " + (mStatusResAppPackageName != null
+                ? mStatusResAppPackageName.toString() : null));
         builder.append("}");
         return builder.toString();
     }
@@ -707,12 +745,23 @@
     /**
      * Get the status of this job.
      *
+     * @param pm Package manager used to resolve the string
+     *
      * @return the status of this job or null if not set
      * @hide
      */
     @TestApi
-    public @Nullable CharSequence getStatus() {
-        return mStatus;
+    public @Nullable CharSequence getStatus(@NonNull PackageManager pm) {
+        if (mStatusRes == 0) {
+            return mStatus;
+        } else {
+            try {
+                return pm.getResourcesForApplication(mStatusResAppPackageName.toString())
+                        .getString(mStatusRes);
+            } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+                return null;
+            }
+        }
     }
 
     /**
@@ -789,6 +838,8 @@
          * @param value The option value.
          */
         public void putAdvancedOption(@NonNull String key, @Nullable String value) {
+            Preconditions.checkNotNull(key, "key cannot be null");
+
             if (mPrototype.mAdvancedOptions == null) {
                 mPrototype.mAdvancedOptions = new Bundle();
             }
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 0d2d9f4..1ee6389 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -467,10 +467,12 @@
          * {@link android.printservice.PrinterDiscoverySession#onRequestCustomPrinterIcon}.
          * </p>
          *
+         * @param hasCustomPrinterIcon If the printer has a custom icon or not.
+         *
          * @return This builder.
          */
-        public @NonNull Builder setHasCustomPrinterIcon() {
-            mHasCustomPrinterIcon = true;
+        public @NonNull Builder setHasCustomPrinterIcon(boolean hasCustomPrinterIcon) {
+            mHasCustomPrinterIcon = hasCustomPrinterIcon;
             return this;
         }
 
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index 0ae1e18..f0ea6ae 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -52,6 +52,15 @@
      */
     void setStatus(in PrintJobId printJobId, in CharSequence status);
 
+    /**
+     * Set the status of this print job
+     *
+     * @param printJobId The print job to update
+     * @param status The new status as a string resource
+     * @param appPackageName The app package name the string belongs to
+     */
+    void setStatusRes(in PrintJobId printJobId, int status, in CharSequence appPackageName);
+
     void onPrintersAdded(in ParceledListSlice printers);
     void onPrintersRemoved(in ParceledListSlice printerIds);
 
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 6414b6a..7a7ca23 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -20,11 +20,14 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
 import android.os.RemoteException;
 import android.print.PrintJobId;
 import android.print.PrintJobInfo;
 import android.text.TextUtils;
 import android.util.Log;
+import com.android.internal.util.Preconditions;
 
 /**
  * This class represents a print job from the perspective of a print
@@ -45,7 +48,12 @@
 
     private PrintJobInfo mCachedInfo;
 
-    PrintJob(@NonNull PrintJobInfo jobInfo, @NonNull IPrintServiceClient client) {
+    /** Context that created the object */
+    private final Context mContext;
+
+    PrintJob(@NonNull Context context, @NonNull PrintJobInfo jobInfo,
+            @NonNull IPrintServiceClient client) {
+        mContext = context;
         mCachedInfo = jobInfo;
         mPrintServiceClient = client;
         mDocument = new PrintDocument(mCachedInfo.getId(), client,
@@ -216,11 +224,10 @@
     }
 
     /**
-     * Blocks the print job. You should call this method if {@link
-     * #isStarted()} or {@link #isBlocked()} returns true and you need
-     * to block the print job. For example, the user has to add some
-     * paper to continue printing. To resume the print job call {@link
-     * #start()}.
+     * Blocks the print job. You should call this method if {@link #isStarted()} returns true and
+     * you need to block the print job. For example, the user has to add some paper to continue
+     * printing. To resume the print job call {@link #start()}. To change the reason call
+     * {@link #setStatus(CharSequence)}.
      *
      * @param reason The human readable, short, and translated reason why the print job is blocked.
      * @return Whether the job was blocked.
@@ -233,9 +240,7 @@
         PrintService.throwIfNotCalledOnMainThread();
         PrintJobInfo info = getInfo();
         final int state = info.getState();
-        if (state == PrintJobInfo.STATE_STARTED
-                || (state == PrintJobInfo.STATE_BLOCKED
-                        && !TextUtils.equals(info.getStatus(), reason))) {
+        if (state == PrintJobInfo.STATE_STARTED || state == PrintJobInfo.STATE_BLOCKED) {
             return setState(PrintJobInfo.STATE_BLOCKED, reason);
         }
         return false;
@@ -320,6 +325,9 @@
     /**
      * Sets the status of this print job. This should be a human readable, short, and translated
      * description of the current state of the print job.
+     * <p />
+     * This overrides any previously set status set via {@link #setStatus(CharSequence)},
+     * {@link #setStatus(int)}, {@link #block(String)}, or {@link #fail(String)},
      *
      * @param status The new status. If null the status will be empty.
      */
@@ -335,6 +343,29 @@
     }
 
     /**
+     * Sets the status of this print job as a string resource.
+     * <p />
+     * This overrides any previously set status set via {@link #setStatus(CharSequence)},
+     * {@link #setStatus(int)}, {@link #block(String)}, or {@link #fail(String)},
+     * <p />
+     * To clear the status use {@link #setStatus(CharSequence) <code>setStatus(null)</code>}
+     *
+     * @param status  The new status as a String resource.
+     */
+    @MainThread
+    public void setStatus(@StringRes int status) {
+        PrintService.throwIfNotCalledOnMainThread();
+        Preconditions.checkArgument(status != 0, "status has to be != 0");
+
+        try {
+            mPrintServiceClient.setStatusRes(mCachedInfo.getId(), status,
+                    mContext.getPackageName());
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error setting status for job: " + mCachedInfo.getId(), re);
+        }
+    }
+
+    /**
      * Sets a tag that is valid in the context of a {@link PrintService}
      * and is not interpreted by the system. For example, a print service
      * may set as a tag the key of the print job returned by a remote
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 62d214e..8f73518 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -329,7 +329,7 @@
                 final int printJobInfoCount = printJobInfos.size();
                 printJobs = new ArrayList<PrintJob>(printJobInfoCount);
                 for (int i = 0; i < printJobInfoCount; i++) {
-                    printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
+                    printJobs.add(new PrintJob(this, printJobInfos.get(i), mClient));
                 }
             }
             if (printJobs != null) {
@@ -549,7 +549,7 @@
                                 + getPackageName());
                     }
                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
-                    onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
+                    onRequestCancelPrintJob(new PrintJob(PrintService.this, printJobInfo, mClient));
                 } break;
 
                 case MSG_ON_PRINTJOB_QUEUED: {
@@ -561,7 +561,7 @@
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Queued: " + printJobInfo);
                     }
-                    onPrintJobQueued(new PrintJob(printJobInfo, mClient));
+                    onPrintJobQueued(new PrintJob(PrintService.this, printJobInfo, mClient));
                 } break;
 
                 case MSG_SET_CLIENT: {
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index b06d503..e90dc9c 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -15,6 +15,7 @@
  */
 package android.provider;
 
+import android.annotation.WorkerThread;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
@@ -68,7 +69,12 @@
  * Apps can optionally provide the {@link BlockedNumbers#COLUMN_E164_NUMBER} which is the phone
  * number's E164 representation. The provider automatically populates this column if the app does
  * not provide it. Note that this column is not populated if normalization fails or if the address
- * is not a phone number (eg: email). The provider enforces uniqueness constraint on this column.
+ * is not a phone number (eg: email).
+ * <p>
+ * Attempting to insert an existing blocked number (same
+ * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column) will result in replacing the existing
+ * blocked number.
+ * <p>
  * Examples:
  * <pre>
  * ContentValues values = new ContentValues();
@@ -104,6 +110,8 @@
  * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
  * getContentResolver().delete(uri, null, null);
  * </pre>
+ * To check if a particular number is blocked, use the method
+ * {@link #isBlocked(Context, String)}.
  * </p>
  * </dd>
  * <dt><b>Query</b></dt>
@@ -115,8 +123,12 @@
  *          new String[]{BlockedNumbers.COLUMN_ID, BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
  *          BlockedNumbers.COLUMN_E164_NUMBER}, null, null, null);
  * </pre>
- * To check if a particular number is blocked, use the method
- * {@link #isBlocked(Context, String)}.
+ * </p>
+ * </dd>
+ * <dt><b>Unblock</b></dt>
+ * <dd>
+ * <p>
+ * Use the method {@link #unblock(Context, String)} to unblock numbers.
  * </p>
  * </dd>
  *
@@ -201,9 +213,15 @@
     public static final String METHOD_IS_BLOCKED = "is_blocked";
 
     /** @hide */
+    public static final String METHOD_UNBLOCK= "unblock";
+
+    /** @hide */
     public static final String RES_NUMBER_IS_BLOCKED = "blocked";
 
     /** @hide */
+    public static final String RES_NUM_ROWS_DELETED = "num_deleted";
+
+    /** @hide */
     public static final String METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS =
             "can_current_user_block_numbers";
 
@@ -212,9 +230,15 @@
 
     /**
      * Returns whether a given number is in the blocked list.
+     *
+     * <p> This matches the {@code phoneNumber} against the
+     * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column, and the E164 representation of the
+     * {@code phoneNumber} with the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
+     *
      * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
      * context {@code context}, this method will throw an {@link UnsupportedOperationException}.
      */
+    @WorkerThread
     public static boolean isBlocked(Context context, String phoneNumber) {
         final Bundle res = context.getContentResolver().call(
                 AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
@@ -222,6 +246,30 @@
     }
 
     /**
+     * Unblocks the {@code phoneNumber} if it is blocked.
+     *
+     * <p> Returns the number of rows deleted in the blocked number provider as a result of unblock.
+     *
+     * <p> This deletes all rows where the {@code phoneNumber} matches the
+     * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or the E164 representation of the
+     * {@code phoneNumber} matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
+     *
+     * <p>To delete rows based on exact match with specific columns such as
+     * {@link BlockedNumbers#COLUMN_ID} use
+     * {@link android.content.ContentProvider#delete(Uri, String, String[])} with
+     * {@link BlockedNumbers#CONTENT_URI} URI.
+     *
+     * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
+     * context {@code context}, this method will throw an {@link UnsupportedOperationException}.
+     */
+    @WorkerThread
+    public static int unblock(Context context, String phoneNumber) {
+        final Bundle res = context.getContentResolver().call(
+                AUTHORITY_URI, METHOD_UNBLOCK, phoneNumber, null);
+        return res.getInt(RES_NUM_ROWS_DELETED, 0);
+    }
+
+    /**
      * Returns {@code true} if blocking numbers is supported for the current user.
      * <p> Typically, blocking numbers is only supported for one user at a time.
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d8937b4..f7e0e03 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -322,6 +322,20 @@
             "android.settings.PRIVACY_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of VPN.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VPN_SETTINGS =
+            "android.settings.VPN_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of Wi-Fi.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -7699,6 +7713,16 @@
         public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
 
         /**
+         * Device Idle (Doze) specific settings for watches. See {@code #DEVICE_IDLE_CONSTANTS}
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.DeviceIdleController.Constants
+         */
+        public static final String DEVICE_IDLE_CONSTANTS_WATCH = "device_idle_constants_watch";
+
+        /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index d57d0f5..4a5f827 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -111,7 +111,7 @@
         if (parser.next() != XmlPullParser.TEXT) {
             throw new ParserException(parser, "Missing pin digest");
         }
-        String digest = parser.getText();
+        String digest = parser.getText().trim();
         byte[] decodedDigest = null;
         try {
             decodedDigest = Base64.decode(digest, 0);
@@ -168,7 +168,7 @@
         if (parser.next() != XmlPullParser.TEXT) {
             throw new ParserException(parser, "Domain name missing");
         }
-        String domain = parser.getText().toLowerCase(Locale.US);
+        String domain = parser.getText().trim().toLowerCase(Locale.US);
         if (parser.next() != XmlPullParser.END_TAG) {
             throw new ParserException(parser, "domain contains additional elements");
         }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 816ecde..7af0b05 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -367,11 +367,6 @@
     @Override
     public void onActionModeFinished(ActionMode mode) {
     }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
-    }
     // end Window.Callback methods
 
     // begin public api
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java
index 302a08d..5d94b06 100644
--- a/core/java/android/util/jar/StrictJarFile.java
+++ b/core/java/android/util/jar/StrictJarFile.java
@@ -58,11 +58,22 @@
 
     public StrictJarFile(String fileName)
             throws IOException, SecurityException {
-        this(fileName, true);
+        this(fileName, true, true);
     }
 
-    public StrictJarFile(String fileName, boolean verify)
-            throws IOException, SecurityException {
+    /**
+     *
+     * @param verify whether to verify the file's JAR signatures and collect the corresponding
+     *        signer certificates.
+     * @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against
+     *        stripping newer signature schemes (e.g., APK Signature Scheme v2) from the file, or
+     *        {@code false} to ignore any such protections. This parameter is ignored when
+     *        {@code verify} is {@code false}.
+     */
+    public StrictJarFile(String fileName,
+            boolean verify,
+            boolean signatureSchemeRollbackProtectionsEnforced)
+                    throws IOException, SecurityException {
         this.nativeHandle = nativeOpenJarFile(fileName);
         this.raf = new RandomAccessFile(fileName, "r");
 
@@ -73,7 +84,12 @@
             if (verify) {
                 HashMap<String, byte[]> metaEntries = getMetaEntries();
                 this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
-                this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
+                this.verifier =
+                        new StrictJarVerifier(
+                                fileName,
+                                manifest,
+                                metaEntries,
+                                signatureSchemeRollbackProtectionsEnforced);
                 Set<String> files = manifest.getEntries().keySet();
                 for (String file : files) {
                     if (findEntry(file) == null) {
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index 0546a5f..6da50ba 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -72,6 +72,7 @@
     private final StrictJarManifest manifest;
     private final HashMap<String, byte[]> metaEntries;
     private final int mainAttributesEnd;
+    private final boolean signatureSchemeRollbackProtectionsEnforced;
 
     private final Hashtable<String, HashMap<String, Attributes>> signatures =
             new Hashtable<String, HashMap<String, Attributes>>(5);
@@ -164,13 +165,19 @@
      *
      * @param name
      *            the name of the JAR file being verified.
+     *
+     * @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against
+     *        stripping newer signature schemes (e.g., APK Signature Scheme v2) from the file, or
+     *        {@code false} to ignore any such protections.
      */
     StrictJarVerifier(String name, StrictJarManifest manifest,
-        HashMap<String, byte[]> metaEntries) {
+        HashMap<String, byte[]> metaEntries, boolean signatureSchemeRollbackProtectionsEnforced) {
         jarName = name;
         this.manifest = manifest;
         this.metaEntries = metaEntries;
         this.mainAttributesEnd = manifest.getMainAttributesEnd();
+        this.signatureSchemeRollbackProtectionsEnforced =
+                signatureSchemeRollbackProtectionsEnforced;
     }
 
     /**
@@ -357,40 +364,42 @@
             return;
         }
 
-        // Check whether APK Signature Scheme v2 signature was stripped.
-        String apkSignatureSchemeIdList =
-                attributes.getValue(
-                        ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
-        if (apkSignatureSchemeIdList != null) {
-            // This field contains a comma-separated list of APK signature scheme IDs which were
-            // used to sign this APK. If an ID is known to us, it means signatures of that scheme
-            // were stripped from the APK because otherwise we wouldn't have fallen back to
-            // verifying the APK using the JAR signature scheme.
-            boolean v2SignatureGenerated = false;
-            StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
-            while (tokenizer.hasMoreTokens()) {
-                String idText = tokenizer.nextToken().trim();
-                if (idText.isEmpty()) {
-                    continue;
+        // If requested, check whether APK Signature Scheme v2 signature was stripped.
+        if (signatureSchemeRollbackProtectionsEnforced) {
+            String apkSignatureSchemeIdList =
+                    attributes.getValue(
+                            ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+            if (apkSignatureSchemeIdList != null) {
+                // This field contains a comma-separated list of APK signature scheme IDs which
+                // were used to sign this APK. If an ID is known to us, it means signatures of that
+                // scheme were stripped from the APK because otherwise we wouldn't have fallen back
+                // to verifying the APK using the JAR signature scheme.
+                boolean v2SignatureGenerated = false;
+                StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
+                while (tokenizer.hasMoreTokens()) {
+                    String idText = tokenizer.nextToken().trim();
+                    if (idText.isEmpty()) {
+                        continue;
+                    }
+                    int id;
+                    try {
+                        id = Integer.parseInt(idText);
+                    } catch (Exception ignored) {
+                        continue;
+                    }
+                    if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        // This APK was supposed to be signed with APK Signature Scheme v2 but no
+                        // such signature was found.
+                        v2SignatureGenerated = true;
+                        break;
+                    }
                 }
-                int id;
-                try {
-                    id = Integer.parseInt(idText);
-                } catch (Exception ignored) {
-                    continue;
-                }
-                if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
-                    // This APK was supposed to be signed with APK Signature Scheme v2 but no such
-                    // signature was found.
-                    v2SignatureGenerated = true;
-                    break;
-                }
-            }
 
-            if (v2SignatureGenerated) {
-                throw new SecurityException(signatureFile + " indicates " + jarName + " is signed"
-                        + " using APK Signature Scheme v2, but no such signature was found."
-                        + " Signature stripped?");
+                if (v2SignatureGenerated) {
+                    throw new SecurityException(signatureFile + " indicates " + jarName
+                            + " is signed using APK Signature Scheme v2, but no such signature was"
+                            + " found. Signature stripped?");
+                }
             }
         }
 
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index a12434c..41c44f1 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -51,8 +52,8 @@
      * @param paint The paint used when the layer is drawn into the destination canvas.
      * @see View#setLayerPaint(android.graphics.Paint)
      */
-    public void setLayerPaint(Paint paint) {
-        nSetLayerPaint(mFinalizer.get(), paint.getNativeInstance());
+    public void setLayerPaint(@Nullable Paint paint) {
+        nSetLayerPaint(mFinalizer.get(), paint != null ? paint.getNativeInstance() : 0);
         mRenderer.pushLayerUpdate(this);
     }
 
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 70d0513..707300f 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -95,5 +95,5 @@
     /**
      * Called when Keyboard Shortcuts are requested for the window.
      */
-    void requestAppKeyboardShortcuts(IResultReceiver receiver);
+    void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index d8b7421..7af4a1f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -387,7 +387,7 @@
      *
      * @param receiver The receiver to deliver the results to.
      */
-    void requestAppKeyboardShortcuts(IResultReceiver receiver);
+    void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
 
     /**
      * Retrieves the current stable insets from the primary display.
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 2c9006d..c2bd347 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -30,31 +30,46 @@
     private final CharSequence mLabel;
     private final Icon mIcon;
     private final char mBaseCharacter;
+    private final int mKeycode;
     private final int mModifiers;
 
     /**
      * @param label The label that identifies the action performed by this shortcut.
      * @param icon An icon that identifies the action performed by this shortcut.
-     * @param baseCharacter The character that triggers the shortcut.
+     * @param keycode The keycode that triggers the shortcut. This should be a valid constant
+     *     defined in {@link KeyEvent}.
      * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
      *     These should be a combination of {@link KeyEvent#META_CTRL_ON},
-     *     {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON} and
-     *     {@link KeyEvent#META_ALT_ON}.
+     *     {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+     *     {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+     *     {@link KeyEvent#META_SYM_ON}.
      *
      * @hide
      */
     public KeyboardShortcutInfo(
-            @Nullable CharSequence label, @Nullable Icon icon, char baseCharacter, int modifiers) {
+            @Nullable CharSequence label, @Nullable Icon icon, int keycode, int modifiers) {
         mLabel = label;
         mIcon = icon;
-        checkArgument(baseCharacter != MIN_VALUE);
-        mBaseCharacter = baseCharacter;
+        mBaseCharacter = MIN_VALUE;
+        checkArgument(keycode > KeyEvent.KEYCODE_UNKNOWN && keycode <= KeyEvent.getMaxKeyCode());
+        mKeycode = keycode;
         mModifiers = modifiers;
     }
 
     /**
-     * Convenience constructor for shortcuts with a label and no icon.
-     *
+     * @param label The label that identifies the action performed by this shortcut.
+     * @param keycode The keycode that triggers the shortcut. This should be a valid constant
+     *     defined in {@link KeyEvent}.
+     * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+     *     These should be a combination of {@link KeyEvent#META_CTRL_ON},
+     *     {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON} and
+     *     {@link KeyEvent#META_ALT_ON}.
+     */
+    public KeyboardShortcutInfo(CharSequence label, int keycode, int modifiers) {
+        this(label, null, keycode, modifiers);
+    }
+
+    /**
      * @param label The label that identifies the action performed by this shortcut.
      * @param baseCharacter The character that triggers the shortcut.
      * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
@@ -66,14 +81,16 @@
         mLabel = label;
         checkArgument(baseCharacter != MIN_VALUE);
         mBaseCharacter = baseCharacter;
+        mKeycode = KeyEvent.KEYCODE_UNKNOWN;
         mModifiers = modifiers;
         mIcon = null;
     }
 
     private KeyboardShortcutInfo(Parcel source) {
         mLabel = source.readCharSequence();
-        mIcon = (Icon) source.readParcelable(null);
+        mIcon = source.readParcelable(null);
         mBaseCharacter = (char) source.readInt();
+        mKeycode = source.readInt();
         mModifiers = source.readInt();
     }
 
@@ -96,7 +113,16 @@
     }
 
     /**
-     * Returns the base character that, combined with the modifiers, triggers this shortcut.
+     * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the
+     * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}.
+     */
+    public int getKeycode() {
+        return mKeycode;
+    }
+
+    /**
+     * Returns the base character that, combined with the modifiers, triggers this shortcut. If the
+     * keycode was set instead, returns {@link Character#MIN_VALUE}.
      */
     public char getBaseCharacter() {
         return mBaseCharacter;
@@ -119,6 +145,7 @@
         dest.writeCharSequence(mLabel);
         dest.writeParcelable(mIcon, 0);
         dest.writeInt(mBaseCharacter);
+        dest.writeInt(mKeycode);
         dest.writeInt(mModifiers);
     }
 
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index a45c18d..a19254f 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -259,7 +259,7 @@
         return nSetLayerType(mNativeRenderNode, layerType);
     }
 
-    public boolean setLayerPaint(Paint paint) {
+    public boolean setLayerPaint(@Nullable Paint paint) {
         return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0);
     }
 
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 1be4810..1a712c3 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -133,7 +134,6 @@
      */
     public TextureView(Context context) {
         super(context);
-        init();
     }
 
     /**
@@ -144,7 +144,6 @@
      */
     public TextureView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
     }
 
     /**
@@ -158,7 +157,6 @@
      */
     public TextureView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
     }
 
     /**
@@ -176,11 +174,6 @@
      */
     public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init();
-    }
-
-    private void init() {
-        mLayerPaint = new Paint();
     }
 
     /**
@@ -260,7 +253,7 @@
      * method will however be taken into account when rendering the content of
      * this TextureView.
      *
-     * @param layerType The ype of layer to use with this view, must be one of
+     * @param layerType The type of layer to use with this view, must be one of
      *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
      *        {@link #LAYER_TYPE_HARDWARE}
      * @param paint The paint used to compose the layer. This argument is optional
@@ -268,16 +261,16 @@
      *        {@link #LAYER_TYPE_NONE}
      */
     @Override
-    public void setLayerType(int layerType, Paint paint) {
-        if (paint != mLayerPaint) {
-            mLayerPaint = paint == null ? new Paint() : paint;
-            invalidate();
-        }
+    public void setLayerType(int layerType, @Nullable Paint paint) {
+        setLayerPaint(paint);
     }
 
     @Override
-    public void setLayerPaint(Paint paint) {
-        setLayerType(/* ignored */ 0, paint);
+    public void setLayerPaint(@Nullable Paint paint) {
+        if (paint != mLayerPaint) {
+            mLayerPaint = paint;
+            invalidate();
+        }
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6d35a58..83c6e9e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11741,7 +11741,7 @@
         }
     }
 
-   /**
+    /**
      * Utility method to retrieve the inverse of the current mMatrix property.
      * We cache the matrix to avoid recalculating it when transform properties
      * have not changed.
@@ -15626,7 +15626,7 @@
      *
      * @attr ref android.R.styleable#View_layerType
      */
-    public void setLayerType(int layerType, Paint paint) {
+    public void setLayerType(int layerType, @Nullable Paint paint) {
         if (layerType < LAYER_TYPE_NONE || layerType > LAYER_TYPE_HARDWARE) {
             throw new IllegalArgumentException("Layer type can only be one of: LAYER_TYPE_NONE, "
                     + "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
@@ -15645,8 +15645,7 @@
         }
 
         mLayerType = layerType;
-        final boolean layerDisabled = (mLayerType == LAYER_TYPE_NONE);
-        mLayerPaint = layerDisabled ? null : (paint == null ? new Paint() : paint);
+        mLayerPaint = mLayerType == LAYER_TYPE_NONE ? null : paint;
         mRenderNode.setLayerPaint(mLayerPaint);
 
         // draw() behaves differently if we are on a layer, so we need to
@@ -15680,12 +15679,12 @@
      *
      * @see #setLayerType(int, android.graphics.Paint)
      */
-    public void setLayerPaint(Paint paint) {
+    public void setLayerPaint(@Nullable Paint paint) {
         int layerType = getLayerType();
         if (layerType != LAYER_TYPE_NONE) {
-            mLayerPaint = paint == null ? new Paint() : paint;
+            mLayerPaint = paint;
             if (layerType == LAYER_TYPE_HARDWARE) {
-                if (mRenderNode.setLayerPaint(mLayerPaint)) {
+                if (mRenderNode.setLayerPaint(paint)) {
                     invalidateViewProperty(false, false);
                 }
             } else {
@@ -16855,7 +16854,7 @@
                 canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
             } else {
                 // use layer paint to draw the bitmap, merging the two alphas, but also restore
-                int layerPaintAlpha = mLayerPaint.getAlpha();
+                int layerPaintAlpha = mLayerPaint != null ? mLayerPaint.getAlpha() : 255;
                 mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                 canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                 mLayerPaint.setAlpha(layerPaintAlpha);
@@ -22187,7 +22186,7 @@
     /**
      * @hide
      */
-    public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> data) {
+    public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> data, int deviceId) {
         // Do nothing.
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a2295ce..7c06577 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3594,8 +3594,9 @@
                 handleDispatchWindowShown();
             } break;
             case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
-                IResultReceiver receiver = (IResultReceiver) msg.obj;
-                handleRequestKeyboardShortcuts(receiver);
+                final IResultReceiver receiver = (IResultReceiver) msg.obj;
+                final int deviceId = msg.arg1;
+                handleRequestKeyboardShortcuts(receiver, deviceId);
             } break;
             case MSG_UPDATE_POINTER_ICON: {
                 MotionEvent event = (MotionEvent) msg.obj;
@@ -5516,11 +5517,11 @@
         mAttachInfo.mTreeObserver.dispatchOnWindowShown();
     }
 
-    public void handleRequestKeyboardShortcuts(IResultReceiver receiver) {
+    public void handleRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
         Bundle data = new Bundle();
         ArrayList<KeyboardShortcutGroup> list = new ArrayList<>();
         if (mView != null) {
-            mView.requestKeyboardShortcuts(list);
+            mView.requestKeyboardShortcuts(list, deviceId);
         }
         data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);
         try {
@@ -6482,8 +6483,9 @@
         }
     }
 
-    public void dispatchRequestKeyboardShortcuts(IResultReceiver receiver) {
-        mHandler.obtainMessage(MSG_REQUEST_KEYBOARD_SHORTCUTS, receiver).sendToTarget();
+    public void dispatchRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+        mHandler.obtainMessage(
+                MSG_REQUEST_KEYBOARD_SHORTCUTS, deviceId, 0, receiver).sendToTarget();
     }
 
     /**
@@ -7060,11 +7062,11 @@
         }
 
         @Override
-        public void requestAppKeyboardShortcuts(IResultReceiver receiver) {
-          ViewRootImpl viewAncestor = mViewAncestor.get();
-          if (viewAncestor != null) {
-            viewAncestor.dispatchRequestKeyboardShortcuts(receiver);
-          }
+        public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+            ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.dispatchRequestKeyboardShortcuts(receiver, deviceId);
+            }
         }
     }
 
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 63f3744..36ee3e6 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -565,9 +565,10 @@
          *
          * @param data The data list to populate with shortcuts.
          * @param menu The current menu, which may be null.
+         * @param deviceId The id for the connected device the shortcuts should be provided for.
          */
         default public void onProvideKeyboardShortcuts(
-                List<KeyboardShortcutGroup> data, @Nullable Menu menu) { };
+                List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { };
     }
 
     /** @hide */
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index bed74e9..8f2d2e1 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -154,8 +154,9 @@
     }
 
     @Override
-    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
-        mWrapped.onProvideKeyboardShortcuts(data, menu);
+    public void onProvideKeyboardShortcuts(
+            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+        mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
     }
 }
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 17f1991..03dcf99 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -158,7 +158,7 @@
      *
      * @hide
      */
-    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver);
+    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
 
     public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
         /**
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 6e11671..f8c7d68 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -125,7 +125,8 @@
     }
 
     @Override
-    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver) {
+    public void requestAppKeyboardShortcuts(
+            final KeyboardShortcutsReceiver receiver, int deviceId) {
         IResultReceiver resultReceiver = new IResultReceiver.Stub() {
             @Override
             public void send(int resultCode, Bundle resultData) throws RemoteException {
@@ -136,7 +137,7 @@
         };
         try {
             WindowManagerGlobal.getWindowManagerService()
-                .requestAppKeyboardShortcuts(resultReceiver);
+                .requestAppKeyboardShortcuts(resultReceiver, deviceId);
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 655c9b3..7f44bac 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -59,4 +59,8 @@
             boolean touchExplorationEnabled);
 
     IBinder getWindowToken(int windowId, int userId);
+
+    void enableAccessibilityService(in ComponentName service, int userId);
+
+    void disableAccessibilityService(in ComponentName service, int userId);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1d89cd0..cf5cc3e 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -317,6 +317,7 @@
     /**
      * The InputConnection that was last retrieved from the served view.
      */
+    InputConnection mServedInputConnection;
     ControlledInputConnectionWrapper mServedInputConnectionWrapper;
     /**
      * The completions that were last provided by the served view.
@@ -497,7 +498,7 @@
                             // from a thread that created mServedView. That could happen
                             // the current activity is running in the system process.
                             // In that case, we really should not call
-                            // mServedInputConnectionWrapper.finishComposingText().
+                            // mServedInputConnection.finishComposingText.
                             if (checkFocusNoStartInput(mHasBeenInactive, false)) {
                                 final int reason = active ?
                                         InputMethodClient.START_INPUT_REASON_ACTIVATED_BY_IMMS :
@@ -561,9 +562,7 @@
 
         @Override
         public String toString() {
-            return "ControlledInputConnectionWrapper{"
-                    + "connection=" + getInputConnection()
-                    + " mActive=" + mActive
+            return "ControlledInputConnectionWrapper{mActive=" + mActive
                     + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
                     + "}";
         }
@@ -781,8 +780,7 @@
      */
     public boolean isAcceptingText() {
         checkFocus();
-        return mServedInputConnectionWrapper != null &&
-                mServedInputConnectionWrapper.getInputConnection() != null;
+        return mServedInputConnection != null;
     }
 
     /**
@@ -817,6 +815,7 @@
      */
     void clearConnectionLocked() {
         mCurrentTextBoxAttribute = null;
+        mServedInputConnection = null;
         if (mServedInputConnectionWrapper != null) {
             mServedInputConnectionWrapper.deactivate();
             mServedInputConnectionWrapper = null;
@@ -849,21 +848,16 @@
      * Notifies the served view that the current InputConnection will no longer be used.
      */
     private void notifyInputConnectionFinished() {
-        if (mServedView == null || mServedInputConnectionWrapper == null) {
-            return;
-        }
-        final InputConnection inputConnection = mServedInputConnectionWrapper.getInputConnection();
-        if (inputConnection == null) {
-            return;
-        }
-        // We need to tell the previously served view that it is no
-        // longer the input target, so it can reset its state.  Schedule
-        // this call on its window's Handler so it will be on the correct
-        // thread and outside of our lock.
-        ViewRootImpl viewRootImpl = mServedView.getViewRootImpl();
-        if (viewRootImpl != null) {
-            // This will result in a call to reportFinishInputConnection() below.
-            viewRootImpl.dispatchFinishInputConnection(inputConnection);
+        if (mServedView != null && mServedInputConnection != null) {
+            // We need to tell the previously served view that it is no
+            // longer the input target, so it can reset its state.  Schedule
+            // this call on its window's Handler so it will be on the correct
+            // thread and outside of our lock.
+            ViewRootImpl viewRootImpl = mServedView.getViewRootImpl();
+            if (viewRootImpl != null) {
+                // This will result in a call to reportFinishInputConnection() below.
+                viewRootImpl.dispatchFinishInputConnection(mServedInputConnection);
+            }
         }
     }
 
@@ -872,13 +866,7 @@
      * @hide
      */
     public void reportFinishInputConnection(InputConnection ic) {
-        final InputConnection currentConnection;
-        if (mServedInputConnectionWrapper == null) {
-            currentConnection = null;
-        } else {
-            currentConnection = mServedInputConnectionWrapper.getInputConnection();
-        }
-        if (currentConnection != ic) {
+        if (mServedInputConnection != ic) {
             ic.finishComposingText();
             // To avoid modifying the public InputConnection interface
             if (ic instanceof BaseInputConnection) {
@@ -1254,6 +1242,7 @@
             mServedConnecting = false;
             // Notify the served view that its previous input connection is finished
             notifyInputConnectionFinished();
+            mServedInputConnection = ic;
             ControlledInputConnectionWrapper servedContext;
             final int missingMethodFlags;
             if (ic != null) {
@@ -1424,7 +1413,7 @@
             return false;
         }
 
-        final ControlledInputConnectionWrapper ic;
+        InputConnection ic = null;
         synchronized (mH) {
             if (mServedView == mNextServedView && !forceNewFocus) {
                 return false;
@@ -1444,7 +1433,7 @@
                 return false;
             }
 
-            ic = mServedInputConnectionWrapper;
+            ic = mServedInputConnection;
 
             mServedView = mNextServedView;
             mCurrentTextBoxAttribute = null;
@@ -2293,7 +2282,7 @@
         } else {
             p.println("  mCurrentTextBoxAttribute: null");
         }
-        p.println("  mServedInputConnectionWrapper=" + mServedInputConnectionWrapper);
+        p.println("  mServedInputConnection=" + mServedInputConnection);
         p.println("  mCompletions=" + Arrays.toString(mCompletions));
         p.println("  mCursorRect=" + mCursorRect);
         p.println("  mCursorSelStart=" + mCursorSelStart
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 280ff15..fe8916b 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -400,8 +400,14 @@
     }
 
     @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index ef6628a..726586e 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -867,8 +867,14 @@
     }
 
     @Override
-    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
     }
 
     // Draw grid
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 9e8f778..f75b74b 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1839,8 +1839,14 @@
     }
 
     @Override
-    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
     }
 
 
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index d1b5fc8..18687c9 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -129,8 +129,8 @@
      */
     private static final int ANIMATION_STYLE_DEFAULT = -1;
 
-    private final int[] mDrawingLocation = new int[2];
-    private final int[] mScreenLocation = new int[2];
+    private final int[] mTmpDrawingLocation = new int[2];
+    private final int[] mTmpScreenLocation = new int[2];
     private final Rect mTempRect = new Rect();
 
     private Context mContext;
@@ -222,7 +222,7 @@
                         mDecorView.getLayoutParams();
 
                 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
-                        mAnchoredGravity));
+                        p.width, p.height, mAnchoredGravity));
                 update(p.x, p.y, -1, -1, true);
             }
         }
@@ -1123,7 +1123,7 @@
 
         TransitionManager.endTransitions(mDecorView);
 
-        unregisterForViewTreeChanges();
+        detachFromAnchor();
 
         mIsShowing = true;
         mIsDropdown = false;
@@ -1206,7 +1206,7 @@
 
         TransitionManager.endTransitions(mDecorView);
 
-        registerForViewTreeChanges(anchor, xoff, yoff, gravity);
+        attachToAnchor(anchor, xoff, yoff, gravity);
 
         mIsShowing = true;
         mIsDropdown = true;
@@ -1214,7 +1214,8 @@
         final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
         preparePopup(p);
 
-        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
+        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
+                p.width, p.height, gravity);
         updateAboveAnchor(aboveAnchor);
 
         invokePopup(p);
@@ -1494,120 +1495,138 @@
      * to reclaim space. If scrolling is not possible or not enough, the popup
      * window gets moved on top of the anchor.
      * <p>
-     * The height must have been set on the layout parameters prior to calling
-     * this method.
+     * The results of positioning are placed in {@code outParams}.
      *
      * @param anchor the view on which the popup window must be anchored
-     * @param p the layout parameters used to display the drop down
-     * @param xoff horizontal offset used to adjust for background padding
-     * @param yoff vertical offset used to adjust for background padding
+     * @param outParams the layout parameters used to display the drop down
+     * @param xOffset absolute horizontal offset from the top of the anchor
+     * @param yOffset absolute vertical offset from the top of the anchor
      * @param gravity horizontal gravity specifying popup alignment
      * @return true if the popup is translated upwards to fit on screen
      */
-    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff,
-            int yoff, int gravity) {
+    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
+            int xOffset, int yOffset, int width, int height, int gravity) {
         final int anchorHeight = anchor.getHeight();
         final int anchorWidth = anchor.getWidth();
         if (mOverlapAnchor) {
-            yoff -= anchorHeight;
+            yOffset -= anchorHeight;
         }
 
-        anchor.getLocationInWindow(mDrawingLocation);
-        p.x = mDrawingLocation[0] + xoff;
-        p.y = mDrawingLocation[1] + anchorHeight + yoff;
+        // Initially, align to the bottom-left corner of the anchor plus offsets.
+        final int[] drawingLocation = mTmpDrawingLocation;
+        anchor.getLocationInWindow(drawingLocation);
+        outParams.x = drawingLocation[0] + xOffset;
+        outParams.y = drawingLocation[1] + anchorHeight + yOffset;
 
+        // If we need to adjust for gravity RIGHT, align to the bottom-right
+        // corner of the anchor (still accounting for offsets).
         final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
                 & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (hgrav == Gravity.RIGHT) {
-            // Flip the location to align the right sides of the popup and
-            // anchor instead of left.
-            p.x -= mPopupWidth - anchorWidth;
+            outParams.x -= width - anchorWidth;
         }
 
-        boolean onTop = false;
+        // Let the window manager know to align the top to y.
+        outParams.gravity = Gravity.LEFT | Gravity.TOP;
 
-        p.gravity = Gravity.LEFT | Gravity.TOP;
+        final int[] screenLocation = mTmpScreenLocation;
+        anchor.getLocationOnScreen(screenLocation);
 
-        anchor.getLocationOnScreen(mScreenLocation);
         final Rect displayFrame = new Rect();
         anchor.getWindowVisibleDisplayFrame(displayFrame);
 
-        final int screenY = mScreenLocation[1] + anchorHeight + yoff;
+        boolean onTop = false;
+
         final View root = anchor.getRootView();
-        if (screenY + mPopupHeight > displayFrame.bottom
-                || p.x + mPopupWidth - root.getWidth() > 0) {
-            // If the drop down disappears at the bottom of the screen, we try
-            // to scroll a parent scrollview or move the drop down back up on
-            // top of the edit box.
+        final int screenY = screenLocation[1] + anchorHeight + yOffset;
+        final boolean tooFarDown = screenY + height > displayFrame.bottom;
+        final boolean tooFarRight = outParams.x + width > root.getWidth();
+        if (tooFarDown || tooFarRight) {
+            // If the popup extends beyond the visible area, try to scroll the
+            // parent so that it is fully visible.
             if (mAllowScrollingAnchorParent) {
                 final int scrollX = anchor.getScrollX();
                 final int scrollY = anchor.getScrollY();
-                final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff,
-                        scrollY + mPopupHeight + anchorHeight + yoff);
+                final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
+                        scrollY + height + anchorHeight + yOffset);
                 anchor.requestRectangleOnScreen(r, true);
             }
 
-            // Now we re-evaluate the space available, and decide from that
-            // whether the pop-up will go above or below the anchor.
-            anchor.getLocationInWindow(mDrawingLocation);
-            p.x = mDrawingLocation[0] + xoff;
-            p.y = mDrawingLocation[1] + anchorHeight + yoff;
+            // Update for the new anchor position.
+            anchor.getLocationInWindow(drawingLocation);
+            outParams.x = drawingLocation[0] + xOffset;
+            outParams.y = drawingLocation[1] + anchorHeight + yOffset;
 
             // Preserve the gravity adjustment.
             if (hgrav == Gravity.RIGHT) {
-                p.x -= mPopupWidth - anchorWidth;
+                outParams.x -= width - anchorWidth;
             }
 
-            // Determine whether there is more space above or below the anchor.
-            anchor.getLocationOnScreen(mScreenLocation);
-            onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) <
-                    (mScreenLocation[1] - yoff - displayFrame.top);
-            if (!mOverlapAnchor) {
+            final int newScreenY = screenLocation[1] + anchorHeight + yOffset;
+            final boolean stillTooFarDown = newScreenY + height > displayFrame.bottom;
+            if (stillTooFarDown) {
+                // If the popup is still too far down, re-evaluate the space
+                // available and decide whether the pop-up will go above or
+                // below the anchor.
+                anchor.getLocationOnScreen(screenLocation);
+
+                final int below = displayFrame.bottom - screenLocation[1] - anchorHeight - yOffset;
+                final int above = screenLocation[1] - displayFrame.top + yOffset;
+                onTop = above > below;
+
                 if (onTop) {
-                    p.gravity = Gravity.LEFT | Gravity.BOTTOM;
-                    p.y = root.getHeight() - mDrawingLocation[1] + yoff;
-                } else {
-                    p.y = mDrawingLocation[1] + anchorHeight + yoff;
+                    // Move everything up.
+                    if (mOverlapAnchor) {
+                        yOffset += anchorHeight;
+                    }
+                    outParams.y = drawingLocation[1] - height + yOffset;
                 }
             }
         }
 
         if (mClipToScreen) {
-            final int winOffsetX = mScreenLocation[0] - mDrawingLocation[0];
-            final int winOffsetY = mScreenLocation[1] - mDrawingLocation[1];
-            p.x += winOffsetX;
-            p.y += winOffsetY;
-            final int displayFrameWidth = displayFrame.right - displayFrame.left;
-            final int right = p.x + p.width;
+            // Use screen coordinates for comparison against display frame.
+            final int winOffsetX = screenLocation[0] - drawingLocation[0];
+            final int winOffsetY = screenLocation[1] - drawingLocation[1];
+            outParams.x += winOffsetX;
+            outParams.y += winOffsetY;
+
+            final int right = outParams.x + width;
             if (right > displayFrame.right) {
-                p.x -= right - displayFrame.right;
+                // The popup is too far right, move it back in.
+                outParams.x -= right - displayFrame.right;
             }
 
-            if (p.x < displayFrame.left) {
-                p.x = displayFrame.left;
-                p.width = Math.min(p.width, displayFrameWidth);
+            if (outParams.x < displayFrame.left) {
+                // The popup is too far left, move it back in and clip if it's
+                // still too large.
+                outParams.x = displayFrame.left;
+
+                final int displayFrameWidth = displayFrame.width();
+                width = Math.min(width, displayFrameWidth);
             }
 
-            if (mOverlapAnchor) {
-                final int bottom = p.y + p.height;
-                if (bottom > displayFrame.bottom) {
-                    p.y -= bottom - displayFrame.bottom;
-                }
-            } else {
-                if (onTop) {
-                    final int popupTop = mScreenLocation[1] + yoff - mPopupHeight;
-                    if (popupTop < 0) {
-                        p.y += popupTop;
-                    }
-                } else {
-                    p.y = Math.max(p.y, displayFrame.top);
-                }
+            final int bottom = outParams.y + height;
+            if (bottom > displayFrame.bottom) {
+                // The popup is too far down, move it back in.
+                outParams.y -= bottom - displayFrame.bottom;
             }
-            p.x -= winOffsetX;
-            p.y -= winOffsetY;
+
+            if (outParams.y < displayFrame.top) {
+                // The popup is too far up, move it back in and clip if
+                // it's still too large.
+                outParams.y = displayFrame.top;
+
+                final int displayFrameHeight = displayFrame.height();
+                height = Math.min(height, displayFrameHeight);
+            }
+
+            outParams.x -= winOffsetX;
+            outParams.y -= winOffsetY;
         }
 
-        p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
+        outParams.width = width;
+        outParams.height = height;
 
         return onTop;
     }
@@ -1665,7 +1684,7 @@
             anchor.getWindowVisibleDisplayFrame(displayFrame);
         }
 
-        final int[] anchorPos = mDrawingLocation;
+        final int[] anchorPos = mTmpDrawingLocation;
         anchor.getLocationOnScreen(anchorPos);
 
         final int bottomEdge = displayFrame.bottom;
@@ -1754,7 +1773,7 @@
         }
 
         // Clears the anchor view.
-        unregisterForViewTreeChanges();
+        detachFromAnchor();
 
         if (mOnDismissListener != null) {
             mOnDismissListener.onDismiss();
@@ -2018,43 +2037,40 @@
         }
 
         final WeakReference<View> oldAnchor = mAnchor;
+        final int gravity = mAnchoredGravity;
+
         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
-            registerForViewTreeChanges(anchor, xoff, yoff, mAnchoredGravity);
+            attachToAnchor(anchor, xoff, yoff, gravity);
         } else if (needsUpdate) {
             // No need to register again if this is a DropDown, showAsDropDown already did.
             mAnchorXoff = xoff;
             mAnchorYoff = yoff;
         }
 
-        final WindowManager.LayoutParams p =
-                (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+        final LayoutParams p = (LayoutParams) mDecorView.getLayoutParams();
+        final int oldGravity = p.gravity;
+        final int oldWidth = p.width;
+        final int oldHeight = p.height;
+        final int oldX = p.x;
+        final int oldY = p.y;
 
         if (updateDimension) {
             if (width == -1) {
                 width = mPopupWidth;
-            } else {
-                mPopupWidth = width;
-                p.width = width;
             }
             if (height == -1) {
                 height = mPopupHeight;
-            } else {
-                mPopupHeight = height;
-                p.height  = height;
             }
         }
 
-        final int x = p.x;
-        final int y = p.y;
-        if (updateLocation) {
-            updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, mAnchoredGravity));
-        } else {
-            updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
-                    mAnchoredGravity));
-        }
+        final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
+                width, height, gravity);
+        updateAboveAnchor(aboveAnchor);
 
-        update(p.x, p.y, width, height, x != p.x || y != p.y);
+        final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
+                || oldWidth != p.width || oldHeight != p.height;
+        update(p.x, p.y, p.width, p.height, paramsChanged);
     }
 
     /**
@@ -2067,7 +2083,7 @@
         public void onDismiss();
     }
 
-    private void unregisterForViewTreeChanges() {
+    private void detachFromAnchor() {
         final View anchor = mAnchor != null ? mAnchor.get() : null;
         if (anchor != null) {
             final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2084,8 +2100,8 @@
         mIsAnchorRootAttached = false;
     }
 
-    private void registerForViewTreeChanges(View anchor, int xoff, int yoff, int gravity) {
-        unregisterForViewTreeChanges();
+    private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+        detachFromAnchor();
 
         final ViewTreeObserver vto = anchor.getViewTreeObserver();
         if (vto != null) {
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index df01fc1..0136542 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -1103,8 +1103,14 @@
     }
 
     @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
     }
 
     /** @hide */
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index ed4722d..2a9264d 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -21,7 +21,6 @@
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Slog;
-import android.text.TextUtils;
 
 import com.android.internal.util.Preconditions;
 
@@ -140,14 +139,14 @@
     }
 
     public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
-            int dexFlags, String volumeUuid, boolean useProfiles) throws InstallerException {
+            int dexFlags, String compilerFilter, String volumeUuid) throws InstallerException {
         dexopt(apkPath, uid, "*", instructionSet, dexoptNeeded,
-                null /*outputPath*/, dexFlags, volumeUuid, useProfiles);
+                null /*outputPath*/, dexFlags, compilerFilter, volumeUuid);
     }
 
     public void dexopt(String apkPath, int uid, String pkgName, String instructionSet,
-            int dexoptNeeded, String outputPath, int dexFlags, String volumeUuid,
-            boolean useProfiles) throws InstallerException {
+            int dexoptNeeded, String outputPath, int dexFlags, String compilerFilter,
+            String volumeUuid) throws InstallerException {
         execute("dexopt",
                 apkPath,
                 uid,
@@ -156,8 +155,27 @@
                 dexoptNeeded,
                 outputPath,
                 dexFlags,
-                volumeUuid,
-                useProfiles ? '1' : '0');
+                compilerFilter,
+                volumeUuid);
+    }
+
+    public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
+        String rawReply = executeForResult("merge_profiles", uid, pkgName);
+        if (rawReply == null) {
+            throw new IllegalStateException("Unexpected null reply");
+        }
+        final String res[] = rawReply.split(" ");
+
+        if ((res == null) || (res.length != 2)) {
+            throw new InstallerException("Invalid size result: " + rawReply);
+        }
+
+        // Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean.
+        if (!res[1].equals("true") && !res[1].equals("false")) {
+            throw new InstallerException("Invalid boolean result: " + rawReply);
+        }
+
+        return Boolean.parseBoolean(res[1]);
     }
 
     private boolean connect() {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b658f87..5980ab6 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -501,12 +501,14 @@
             for (String classPathElement : classPathElements) {
                 // System server is fully AOTed and never profiled
                 // for profile guided compilation.
+                // TODO: Make this configurable between INTERPRET_ONLY, SPEED, SPACE and EVERYTHING?
                 final int dexoptNeeded = DexFile.getDexOptNeeded(
-                        classPathElement, instructionSet, DexFile.COMPILATION_TYPE_FULL);
+                        classPathElement, instructionSet, "speed",
+                        false /* newProfile */);
                 if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                     installer.dexopt(classPathElement, Process.SYSTEM_UID, instructionSet,
-                            dexoptNeeded, 0 /*dexFlags*/, null /*volumeUuid*/,
-                            false /*useProfiles*/);
+                            dexoptNeeded, 0 /*dexFlags*/, "speed",
+                            null /*volumeUuid*/);
                 }
             }
         } catch (IOException | InstallerException e) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index fbd8fb5..bdf2a21 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2070,10 +2070,10 @@
      * @hide
      */
     @Override
-    public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list) {
+    public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) {
         final PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
         if (!mWindow.isDestroyed() && st != null && mWindow.getCallback() != null) {
-            mWindow.getCallback().onProvideKeyboardShortcuts(list, st.menu);
+            mWindow.getCallback().onProvideKeyboardShortcuts(list, st.menu, deviceId);
         }
     }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 64c5b8d..b243cac 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -65,7 +65,7 @@
     void cancelPreloadRecentApps();
     void showScreenPinningRequest();
 
-    void toggleKeyboardShortcutsMenu();
+    void toggleKeyboardShortcutsMenu(int deviceId);
 
     /**
      * Notifies the status bar that an app transition is pending to delay applying some flags with
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 8acf5d3..ee3f937 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -71,7 +71,7 @@
     void preloadRecentApps();
     void cancelPreloadRecentApps();
 
-    void toggleKeyboardShortcutsMenu();
+    void toggleKeyboardShortcutsMenu(int deviceId);
 
     /**
      * Notifies the status bar that an app transition is pending to delay applying some flags with
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 6076973..48bcc09 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -16,6 +16,11 @@
 
 package com.android.internal.util;
 
+import android.annotation.ColorInt;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.app.Notification;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -43,6 +48,7 @@
 public class NotificationColorUtil {
 
     private static final String TAG = "NotificationColorUtil";
+    private static final boolean DEBUG = false;
 
     private static final Object sLock = new Object();
     private static NotificationColorUtil sInstance;
@@ -222,4 +228,420 @@
                 255 - Color.green(color),
                 255 - Color.blue(color));
     }
+
+    /**
+     * Finds a suitable color such that there's enough contrast.
+     *
+     * @param color the color to start searching from.
+     * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
+     * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+     * @param minRatio the minimum contrast ratio required.
+     * @return a color with the same hue as {@param color}, potentially darkened to meet the
+     *          contrast ratio.
+     */
+    private static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
+        int fg = findFg ? color : other;
+        int bg = findFg ? other : color;
+        if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) {
+            return color;
+        }
+
+        double[] lab = new double[3];
+        ColorUtilsFromCompat.colorToLAB(findFg ? fg : bg, lab);
+
+        double low = 0, high = lab[0];
+        final double a = lab[1], b = lab[2];
+        for (int i = 0; i < 15 && high - low > 0.00001; i++) {
+            final double l = (low + high) / 2;
+            if (findFg) {
+                fg = ColorUtilsFromCompat.LABToColor(l, a, b);
+            } else {
+                bg = ColorUtilsFromCompat.LABToColor(l, a, b);
+            }
+            if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) {
+                low = l;
+            } else {
+                high = l;
+            }
+        }
+        return ColorUtilsFromCompat.LABToColor(low, a, b);
+    }
+
+    /**
+     * Finds a text color with sufficient contrast over bg that has the same hue as the original
+     * color, assuming it is for large text.
+     */
+    private static int ensureLargeTextContrast(int color, int bg) {
+        return findContrastColor(color, bg, true, 3);
+    }
+
+    /**
+     * Finds a text color with sufficient contrast over bg that has the same hue as the original
+     * color.
+     */
+    private static int ensureTextContrast(int color, int bg) {
+        return findContrastColor(color, bg, true, 4.5);
+    }
+
+    /** Finds a background color for a text view with given text color and hint text color, that
+     * has the same hue as the original color.
+     */
+    public static int ensureTextBackgroundColor(int color, int textColor, int hintColor) {
+        color = findContrastColor(color, hintColor, false, 3.0);
+        return findContrastColor(color, textColor, false, 4.5);
+    }
+
+    private static String contrastChange(int colorOld, int colorNew, int bg) {
+        return String.format("from %.2f:1 to %.2f:1",
+                ColorUtilsFromCompat.calculateContrast(colorOld, bg),
+                ColorUtilsFromCompat.calculateContrast(colorNew, bg));
+    }
+
+    /**
+     * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+     */
+    public static int resolveColor(Context context, int color) {
+        if (color == Notification.COLOR_DEFAULT) {
+            return context.getColor(com.android.internal.R.color.notification_icon_default_color);
+        }
+        return color;
+    }
+
+    /**
+     * Resolves a Notification's color such that it has enough contrast to be used as the
+     * color for the Notification's action and header text.
+     *
+     * @param notificationColor the color of the notification or {@link Notification#COLOR_DEFAULT}
+     * @return a color of the same hue with enough contrast against the backgrounds.
+     */
+    public static int resolveContrastColor(Context context, int notificationColor) {
+        final int resolvedColor = resolveColor(context, notificationColor);
+
+        final int actionBg = context.getColor(
+                com.android.internal.R.color.notification_action_list);
+        final int notiBg = context.getColor(
+                com.android.internal.R.color.notification_material_background_color);
+
+        int color = resolvedColor;
+        color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg);
+        color = NotificationColorUtil.ensureTextContrast(color, notiBg);
+
+        if (color != resolvedColor) {
+            if (DEBUG){
+                Log.w(TAG, String.format(
+                        "Enhanced contrast of notification for %s %s (over action)"
+                                + " and %s (over background) by changing #%s to %s",
+                        context.getPackageName(),
+                        NotificationColorUtil.contrastChange(resolvedColor, color, actionBg),
+                        NotificationColorUtil.contrastChange(resolvedColor, color, notiBg),
+                        Integer.toHexString(resolvedColor), Integer.toHexString(color)));
+            }
+        }
+        return color;
+    }
+
+    /**
+     * Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
+     */
+    private static class ColorUtilsFromCompat {
+        private static final double XYZ_WHITE_REFERENCE_X = 95.047;
+        private static final double XYZ_WHITE_REFERENCE_Y = 100;
+        private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
+        private static final double XYZ_EPSILON = 0.008856;
+        private static final double XYZ_KAPPA = 903.3;
+
+        private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
+        private static final int MIN_ALPHA_SEARCH_PRECISION = 1;
+
+        private static final ThreadLocal<double[]> TEMP_ARRAY = new ThreadLocal<>();
+
+        private ColorUtilsFromCompat() {}
+
+        /**
+         * Composite two potentially translucent colors over each other and returns the result.
+         */
+        public static int compositeColors(@ColorInt int foreground, @ColorInt int background) {
+            int bgAlpha = Color.alpha(background);
+            int fgAlpha = Color.alpha(foreground);
+            int a = compositeAlpha(fgAlpha, bgAlpha);
+
+            int r = compositeComponent(Color.red(foreground), fgAlpha,
+                    Color.red(background), bgAlpha, a);
+            int g = compositeComponent(Color.green(foreground), fgAlpha,
+                    Color.green(background), bgAlpha, a);
+            int b = compositeComponent(Color.blue(foreground), fgAlpha,
+                    Color.blue(background), bgAlpha, a);
+
+            return Color.argb(a, r, g, b);
+        }
+
+        private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
+            return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
+        }
+
+        private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) {
+            if (a == 0) return 0;
+            return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF);
+        }
+
+        /**
+         * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
+         * <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
+         */
+        @FloatRange(from = 0.0, to = 1.0)
+        public static double calculateLuminance(@ColorInt int color) {
+            final double[] result = getTempDouble3Array();
+            colorToXYZ(color, result);
+            // Luminance is the Y component
+            return result[1] / 100;
+        }
+
+        /**
+         * Returns the contrast ratio between {@code foreground} and {@code background}.
+         * {@code background} must be opaque.
+         * <p>
+         * Formula defined
+         * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
+         */
+        public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) {
+            if (Color.alpha(background) != 255) {
+                throw new IllegalArgumentException("background can not be translucent: #"
+                        + Integer.toHexString(background));
+            }
+            if (Color.alpha(foreground) < 255) {
+                // If the foreground is translucent, composite the foreground over the background
+                foreground = compositeColors(foreground, background);
+            }
+
+            final double luminance1 = calculateLuminance(foreground) + 0.05;
+            final double luminance2 = calculateLuminance(background) + 0.05;
+
+            // Now return the lighter luminance divided by the darker luminance
+            return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
+        }
+
+        /**
+         * Convert the ARGB color to its CIE Lab representative components.
+         *
+         * @param color  the ARGB color to convert. The alpha component is ignored
+         * @param outLab 3-element array which holds the resulting LAB components
+         */
+        public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) {
+            RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab);
+        }
+
+        /**
+         * Convert RGB components to its CIE Lab representative components.
+         *
+         * <ul>
+         * <li>outLab[0] is L [0 ...1)</li>
+         * <li>outLab[1] is a [-128...127)</li>
+         * <li>outLab[2] is b [-128...127)</li>
+         * </ul>
+         *
+         * @param r      red component value [0..255]
+         * @param g      green component value [0..255]
+         * @param b      blue component value [0..255]
+         * @param outLab 3-element array which holds the resulting LAB components
+         */
+        public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r,
+                @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+                @NonNull double[] outLab) {
+            // First we convert RGB to XYZ
+            RGBToXYZ(r, g, b, outLab);
+            // outLab now contains XYZ
+            XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
+            // outLab now contains LAB representation
+        }
+
+        /**
+         * Convert the ARGB color to it's CIE XYZ representative components.
+         *
+         * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+         * 2° Standard Observer (1931).</p>
+         *
+         * <ul>
+         * <li>outXyz[0] is X [0 ...95.047)</li>
+         * <li>outXyz[1] is Y [0...100)</li>
+         * <li>outXyz[2] is Z [0...108.883)</li>
+         * </ul>
+         *
+         * @param color  the ARGB color to convert. The alpha component is ignored
+         * @param outXyz 3-element array which holds the resulting LAB components
+         */
+        public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
+            RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
+        }
+
+        /**
+         * Convert RGB components to it's CIE XYZ representative components.
+         *
+         * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+         * 2° Standard Observer (1931).</p>
+         *
+         * <ul>
+         * <li>outXyz[0] is X [0 ...95.047)</li>
+         * <li>outXyz[1] is Y [0...100)</li>
+         * <li>outXyz[2] is Z [0...108.883)</li>
+         * </ul>
+         *
+         * @param r      red component value [0..255]
+         * @param g      green component value [0..255]
+         * @param b      blue component value [0..255]
+         * @param outXyz 3-element array which holds the resulting XYZ components
+         */
+        public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r,
+                @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+                @NonNull double[] outXyz) {
+            if (outXyz.length != 3) {
+                throw new IllegalArgumentException("outXyz must have a length of 3.");
+            }
+
+            double sr = r / 255.0;
+            sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
+            double sg = g / 255.0;
+            sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
+            double sb = b / 255.0;
+            sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
+
+            outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
+            outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
+            outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
+        }
+
+        /**
+         * Converts a color from CIE XYZ to CIE Lab representation.
+         *
+         * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+         * 2° Standard Observer (1931).</p>
+         *
+         * <ul>
+         * <li>outLab[0] is L [0 ...1)</li>
+         * <li>outLab[1] is a [-128...127)</li>
+         * <li>outLab[2] is b [-128...127)</li>
+         * </ul>
+         *
+         * @param x      X component value [0...95.047)
+         * @param y      Y component value [0...100)
+         * @param z      Z component value [0...108.883)
+         * @param outLab 3-element array which holds the resulting Lab components
+         */
+        public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
+                @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
+                @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z,
+                @NonNull double[] outLab) {
+            if (outLab.length != 3) {
+                throw new IllegalArgumentException("outLab must have a length of 3.");
+            }
+            x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
+            y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
+            z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
+            outLab[0] = Math.max(0, 116 * y - 16);
+            outLab[1] = 500 * (x - y);
+            outLab[2] = 200 * (y - z);
+        }
+
+        /**
+         * Converts a color from CIE Lab to CIE XYZ representation.
+         *
+         * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+         * 2° Standard Observer (1931).</p>
+         *
+         * <ul>
+         * <li>outXyz[0] is X [0 ...95.047)</li>
+         * <li>outXyz[1] is Y [0...100)</li>
+         * <li>outXyz[2] is Z [0...108.883)</li>
+         * </ul>
+         *
+         * @param l      L component value [0...100)
+         * @param a      A component value [-128...127)
+         * @param b      B component value [-128...127)
+         * @param outXyz 3-element array which holds the resulting XYZ components
+         */
+        public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l,
+                @FloatRange(from = -128, to = 127) final double a,
+                @FloatRange(from = -128, to = 127) final double b,
+                @NonNull double[] outXyz) {
+            final double fy = (l + 16) / 116;
+            final double fx = a / 500 + fy;
+            final double fz = fy - b / 200;
+
+            double tmp = Math.pow(fx, 3);
+            final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA;
+            final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA;
+
+            tmp = Math.pow(fz, 3);
+            final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA;
+
+            outXyz[0] = xr * XYZ_WHITE_REFERENCE_X;
+            outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y;
+            outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z;
+        }
+
+        /**
+         * Converts a color from CIE XYZ to its RGB representation.
+         *
+         * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+         * 2° Standard Observer (1931).</p>
+         *
+         * @param x X component value [0...95.047)
+         * @param y Y component value [0...100)
+         * @param z Z component value [0...108.883)
+         * @return int containing the RGB representation
+         */
+        @ColorInt
+        public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
+                @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
+                @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) {
+            double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;
+            double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;
+            double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100;
+
+            r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
+            g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
+            b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
+
+            return Color.rgb(
+                    constrain((int) Math.round(r * 255), 0, 255),
+                    constrain((int) Math.round(g * 255), 0, 255),
+                    constrain((int) Math.round(b * 255), 0, 255));
+        }
+
+        /**
+         * Converts a color from CIE Lab to its RGB representation.
+         *
+         * @param l L component value [0...100]
+         * @param a A component value [-128...127]
+         * @param b B component value [-128...127]
+         * @return int containing the RGB representation
+         */
+        @ColorInt
+        public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l,
+                @FloatRange(from = -128, to = 127) final double a,
+                @FloatRange(from = -128, to = 127) final double b) {
+            final double[] result = getTempDouble3Array();
+            LABToXYZ(l, a, b, result);
+            return XYZToColor(result[0], result[1], result[2]);
+        }
+
+        private static int constrain(int amount, int low, int high) {
+            return amount < low ? low : (amount > high ? high : amount);
+        }
+
+        private static double pivotXyzComponent(double component) {
+            return component > XYZ_EPSILON
+                    ? Math.pow(component, 1 / 3.0)
+                    : (XYZ_KAPPA * component + 16) / 116;
+        }
+
+        private static double[] getTempDouble3Array() {
+            double[] result = TEMP_ARRAY.get();
+            if (result == null) {
+                result = new double[3];
+                TEMP_ARRAY.set(result);
+            }
+            return result;
+        }
+
+    }
 }
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 53cb56e..c46851e 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -35,6 +35,20 @@
     }
 
     /**
+     * Ensures that an expression checking an argument is true.
+     *
+     * @param expression the expression to check
+     * @param errorMessage the exception message to use if the check fails; will
+     *     be converted to a string using {@link String#valueOf(Object)}
+     * @throws IllegalArgumentException if {@code expression} is false
+     */
+    public static void checkArgument(boolean expression, final Object errorMessage) {
+        if (!expression) {
+            throw new IllegalArgumentException(String.valueOf(errorMessage));
+        }
+    }
+
+    /**
      * Ensures that an string reference passed as a parameter to the calling
      * method is not empty.
      *
diff --git a/core/java/com/android/internal/util/ScreenShapeHelper.java b/core/java/com/android/internal/util/ScreenShapeHelper.java
index 4a196f8..5f390be 100644
--- a/core/java/com/android/internal/util/ScreenShapeHelper.java
+++ b/core/java/com/android/internal/util/ScreenShapeHelper.java
@@ -1,27 +1,20 @@
 package com.android.internal.util;
 
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.SystemProperties;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.view.ViewRootImpl;
 
-import com.android.internal.R;
-
 /**
  * @hide
  */
 public class ScreenShapeHelper {
-    private static final boolean IS_EMULATOR = Build.HARDWARE.contains("goldfish");
-
     /**
      * Return the bottom pixel window outset of a window given its style attributes.
      * @return An outset dimension in pixels or 0 if no outset should be applied.
      */
     public static int getWindowOutsetBottomPx(Resources resources) {
-        if (IS_EMULATOR) {
+        if (Build.IS_EMULATOR) {
             return SystemProperties.getInt(ViewRootImpl.PROPERTY_EMULATOR_WIN_OUTSET_BOTTOM_PX, 0);
         } else {
             return resources.getInteger(com.android.internal.R.integer.config_windowOutsetBottom);
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index ab918c8..530e00c 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -111,6 +111,6 @@
     }
 
     @Override
-    public void requestAppKeyboardShortcuts(IResultReceiver receiver) {
+    public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
     }
 }
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index afa1554..3be15f3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.view;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -59,8 +57,7 @@
     private static final int DO_CLEAR_META_KEY_STATES = 130;
     private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
 
-    @NonNull
-    private final WeakReference<InputConnection> mInputConnection;
+    private WeakReference<InputConnection> mInputConnection;
 
     private Looper mMainLooper;
     private Handler mH;
@@ -89,11 +86,6 @@
         mH = new MyHandler(mMainLooper);
     }
 
-    @Nullable
-    public InputConnection getInputConnection() {
-        return mInputConnection.get();
-    }
-
     abstract protected boolean isActive();
 
     /**
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 8682030..da223a6 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -41,6 +41,7 @@
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.View.OnLayoutChangeListener;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.Window;
@@ -878,6 +879,10 @@
                     .start();
         }
 
+        /**
+         * Defines the position of the floating toolbar popup panels when transition animation has
+         * stopped.
+         */
         private void setPanelsStatesAtRestingPosition() {
             mOverflowButton.setEnabled(true);
             mOverflowPanel.awakenScrollBars();
@@ -1236,7 +1241,15 @@
                     Math.min(
                             Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
                             mOverflowPanel.getCount()));
-            return actualSize * getLineHeight(mContext) + mOverflowButtonSize.getHeight();
+            int extension = 0;
+            if (actualSize < mOverflowPanel.getCount()) {
+                // The overflow will require scrolling to get to all the items.
+                // Extend the height so that part of the hidden items is displayed.
+                extension = (int) (getLineHeight(mContext) * 0.5f);
+            }
+            return actualSize * getLineHeight(mContext)
+                    + mOverflowButtonSize.getHeight()
+                    + extension;
         }
 
         private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
@@ -1446,6 +1459,7 @@
             OverflowPanel(FloatingToolbarPopup popup) {
                 super(Preconditions.checkNotNull(popup).mContext);
                 this.mPopup = popup;
+                setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
             }
 
             @Override
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 1ead618..528541d 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -723,6 +723,7 @@
         // Make sure that the recycled bitmap has the correct alpha type.
         mRecycledBitmap->setAlphaType(bitmap->alphaType());
 
+        bitmap->notifyPixelsChanged();
         bitmap->lockPixels();
         mNeedsCopy = false;
 
diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index 2998c99..ab393f2 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -232,12 +232,6 @@
         obj->addPath(*src, *matrix);
     }
 
-    static void offset__FFPath(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy, jlong dstHandle) {
-        SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
-        SkPath* dst = reinterpret_cast<SkPath*>(dstHandle);
-        obj->offset(dx, dy, dst);
-    }
-
     static void offset__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->offset(dx, dy);
@@ -508,7 +502,6 @@
     {"native_addPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF},
     {"native_addPath","(JJ)V", (void*) SkPathGlue::addPath__Path},
     {"native_addPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix},
-    {"native_offset","(JFFJ)V", (void*) SkPathGlue::offset__FFPath},
     {"native_offset","(JFF)V", (void*) SkPathGlue::offset__FF},
     {"native_setLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint},
     {"native_transform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath},
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 2a4aa967..30b5a79 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -28,7 +28,7 @@
             android:orientation="horizontal"
             android:gravity="center_vertical"
             android:visibility="gone"
-            android:background="#ffeeeeee"
+            android:background="@color/notification_action_list"
             >
         <!-- actions will be added here -->
     </LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 809e525..ba6d30a 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -51,7 +51,7 @@
         <LinearLayout
             android:id="@+id/media_actions"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             android:layout_gravity="bottom|end"
             android:layout_marginStart="10dp"
             android:layout_marginBottom="12dp"
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 58d3d91..c0716e9 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -26,12 +26,4 @@
 
     <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
     <string translatable="false" name="config_defaultPictureInPictureBounds">"1328 54 1808 324"</string>
-
-    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
-         is located in center. -->
-    <string translatable="false" name="config_centeredPictureInPictureBounds">"596 280 1324 690"</string>
-
-    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
-         when the PIP is shown with Recents. -->
-    <string translatable="false" name="config_pictureInPictureBoundsInRecents">"1484 96 1804 276"</string>
 </resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 7711825..48aa440 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -130,11 +130,15 @@
     <drawable name="notification_template_divider">#29000000</drawable>
     <drawable name="notification_template_divider_media">#29ffffff</drawable>
 
+    <color name="notification_material_background_color">#ffffffff</color>
+
     <color name="notification_default_color">#757575</color> <!-- Gray 600 -->
     <color name="notification_icon_default_color">@color/notification_default_color</color>
 
     <color name="notification_progress_background_color">@color/secondary_text_material_light</color>
 
+    <color name="notification_action_list">#ffeeeeee</color>
+
     <!-- Keyguard colors -->
     <color name="keyguard_avatar_frame_color">#ffffffff</color>
     <color name="keyguard_avatar_frame_shadow_color">#80000000</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 01b2c47..6ecaa1f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2462,14 +2462,6 @@
     <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
     <string translatable="false" name="config_defaultPictureInPictureBounds">"0 0 100 100"</string>
 
-    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
-         is located in center. -->
-    <string translatable="false" name="config_centeredPictureInPictureBounds">"0 0 300 300"</string>
-
-    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
-         when the PIP is shown with Recents. -->
-    <string translatable="false" name="config_pictureInPictureBoundsInRecents">"0 0 100 100"</string>
-
     <!-- Controls the snap mode for the docked stack divider
              0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
              1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 081d613..9b7b1d4 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -22,6 +22,8 @@
     <dimen name="thumbnail_width">192dp</dimen>
     <!-- The height that is used when creating thumbnails of applications. -->
     <dimen name="thumbnail_height">192dp</dimen>
+    <!-- The amount to scale a fullscreen screenshot thumbnail. -->
+    <item name="thumbnail_fullscreen_scale" type="fraction">60%</item>
     <!-- The standard size (both width and height) of an application icon that
          will be displayed in the app launcher and elsewhere. -->
     <dimen name="app_icon_size">48dip</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8b78183..7426ddf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -308,8 +308,6 @@
   <java-symbol type="bool" name="config_guestUserEphemeral" />
   <java-symbol type="bool" name="config_localDisplaysMirrorContent" />
   <java-symbol type="string" name="config_defaultPictureInPictureBounds" />
-  <java-symbol type="string" name="config_centeredPictureInPictureBounds" />
-  <java-symbol type="string" name="config_pictureInPictureBoundsInRecents" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />
@@ -1513,6 +1511,7 @@
   <java-symbol type="dimen" name="docked_stack_minimize_thickness" />
   <java-symbol type="integer" name="config_dockedStackDividerSnapMode" />
   <java-symbol type="fraction" name="docked_stack_divider_fixed_ratio" />
+  <java-symbol type="fraction" name="thumbnail_fullscreen_scale" />
   <java-symbol type="dimen" name="navigation_bar_height" />
   <java-symbol type="dimen" name="navigation_bar_height_landscape" />
   <java-symbol type="dimen" name="navigation_bar_width" />
@@ -2537,6 +2536,9 @@
   <java-symbol type="string" name="usb_mtp_launch_notification_title" />
   <java-symbol type="string" name="usb_mtp_launch_notification_description" />
 
+  <java-symbol type="color" name="notification_action_list" />
+  <java-symbol type="color" name="notification_material_background_color" />
+
   <!-- Resolver target actions -->
   <java-symbol type="array" name="resolver_target_actions_pin" />
   <java-symbol type="array" name="resolver_target_actions_unpin" />
diff --git a/core/tests/coretests/src/android/graphics/PathOffsetTest.java b/core/tests/coretests/src/android/graphics/PathOffsetTest.java
new file mode 100644
index 0000000..950f873
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/PathOffsetTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+
+import android.graphics.Bitmap.Config;
+import android.graphics.Path.Direction;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PathOffsetTest {
+
+    private static final int SQUARE = 10;
+    private static final int WIDTH = 100;
+    private static final int HEIGHT = 100;
+    private static final int START_X = 10;
+    private static final int START_Y = 20;
+    private static final int OFFSET_X = 30;
+    private static final int OFFSET_Y = 40;
+
+    @Test
+    @SmallTest
+    public void testPathOffset() {
+        Path actualPath = new Path();
+        actualPath.addRect(START_X, START_Y, START_X + SQUARE, START_Y + SQUARE, Direction.CW);
+        assertTrue(actualPath.isSimplePath);
+        actualPath.offset(OFFSET_X, OFFSET_Y);
+        assertTrue(actualPath.isSimplePath);
+
+        Path expectedPath = new Path();
+        expectedPath.addRect(START_X + OFFSET_X, START_Y + OFFSET_Y, START_X + OFFSET_X + SQUARE,
+                START_Y + OFFSET_Y + SQUARE, Direction.CW);
+
+        assertPaths(actualPath, expectedPath);
+    }
+
+    @Test
+    @SmallTest
+    public void testPathOffsetWithDestination() {
+        Path initialPath = new Path();
+        initialPath.addRect(START_X, START_Y, START_X + SQUARE, START_Y + SQUARE, Direction.CW);
+        Path actualPath = new Path();
+        assertTrue(initialPath.isSimplePath);
+        assertTrue(actualPath.isSimplePath);
+        initialPath.offset(OFFSET_X, OFFSET_Y, actualPath);
+        assertTrue(actualPath.isSimplePath);
+
+        Path expectedPath = new Path();
+        expectedPath.addRect(START_X + OFFSET_X, START_Y + OFFSET_Y, START_X + OFFSET_X + SQUARE,
+                START_Y + OFFSET_Y + SQUARE, Direction.CW);
+
+        assertPaths(actualPath, expectedPath);
+    }
+
+    private static void assertPaths(Path actual, Path expected) {
+        Bitmap actualBitmap = drawAndGetBitmap(actual);
+        Bitmap expectedBitmap = drawAndGetBitmap(expected);
+        assertTrue(actualBitmap.sameAs(expectedBitmap));
+    }
+
+    private static Bitmap drawAndGetBitmap(Path path) {
+        Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Config.ARGB_8888);
+        bitmap.eraseColor(Color.BLACK);
+        Paint paint = new Paint();
+        paint.setColor(Color.RED);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawPath(path, paint);
+        return bitmap;
+    }
+
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index d554a50..cbed96c 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -20,6 +20,8 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.Layout.Alignment;
 import static android.text.Layout.Alignment.*;
+import android.text.TextPaint;
+import android.text.method.EditorState;
 import android.util.Log;
 
 import junit.framework.TestCase;
@@ -33,6 +35,10 @@
  * @Suppress
  */
 public class StaticLayoutTest extends TestCase {
+    private static final int DEFAULT_OUTER_WIDTH = 150;
+    private static final Alignment DEFAULT_ALIGN = Alignment.ALIGN_CENTER;
+    private static final float SPACE_MULTI = 1.0f;
+    private static final float SPACE_ADD = 0.0f;
 
     /**
      * Basic test showing expected behavior and relationship between font
@@ -321,4 +327,91 @@
         assertEquals(topPad, l.getTopPadding());
         assertEquals(botPad, l.getBottomPadding());
     }
+
+    private void moveCursorToRightCursorableOffset(EditorState state, TextPaint paint) {
+        assertEquals("The editor has selection", state.mSelectionStart, state.mSelectionEnd);
+        final Layout layout = builder().setText(state.mText.toString()).setPaint(paint).build();
+        final int newOffset = layout.getOffsetToRightOf(state.mSelectionStart);
+        state.mSelectionStart = state.mSelectionEnd = newOffset;
+    }
+
+    private void moveCursorToLeftCursorableOffset(EditorState state, TextPaint paint) {
+        assertEquals("The editor has selection", state.mSelectionStart, state.mSelectionEnd);
+        final Layout layout = builder().setText(state.mText.toString()).setPaint(paint).build();
+        final int newOffset = layout.getOffsetToLeftOf(state.mSelectionStart);
+        state.mSelectionStart = state.mSelectionEnd = newOffset;
+    }
+
+    /**
+     * Tests for keycap, variation selectors, flags are in CTS.
+     * See {@link android.text.cts.StaticLayoutTest}.
+     */
+    public void testEmojiOffset() {
+        EditorState state = new EditorState();
+        TextPaint paint = new TextPaint();
+
+        // Odd numbered regional indicator symbols.
+        // U+1F1E6 is REGIONAL INDICATOR SYMBOL LETTER A, U+1F1E8 is REGIONAL INDICATOR SYMBOL
+        // LETTER C.
+        state.setByString("| U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+1F1E6 U+1F1E8 | U+1F1E6 U+1F1E8 U+1F1E6");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 | U+1F1E6");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 |");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 |");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 | U+1F1E6");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("U+1F1E6 U+1F1E8 | U+1F1E6 U+1F1E8 U+1F1E6");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("| U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("| U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6");
+        moveCursorToLeftCursorableOffset(state, paint);
+
+        // Zero width sequence
+        final String zwjSequence = "U+1F468 U+200D U+2764 U+FE0F U+200D U+1F468";
+        state.setByString("| " + zwjSequence + " " + zwjSequence + " " + zwjSequence);
+        moveCursorToRightCursorableOffset(state, paint);
+        state.assertEquals(zwjSequence + " | " + zwjSequence + " " + zwjSequence);
+        moveCursorToRightCursorableOffset(state, paint);
+        state.assertEquals(zwjSequence + " " + zwjSequence + " | " + zwjSequence);
+        moveCursorToRightCursorableOffset(state, paint);
+        state.assertEquals(zwjSequence + " " + zwjSequence + " " + zwjSequence + " |");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.assertEquals(zwjSequence + " " + zwjSequence + " " + zwjSequence + " |");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.assertEquals(zwjSequence + " " + zwjSequence + " | " + zwjSequence);
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.assertEquals(zwjSequence + " | " + zwjSequence + " " + zwjSequence);
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.assertEquals("| " + zwjSequence + " " + zwjSequence + " " + zwjSequence);
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.assertEquals("| " + zwjSequence + " " + zwjSequence + " " + zwjSequence);
+        moveCursorToLeftCursorableOffset(state, paint);
+
+        // Emoji modifiers
+        // U+261D is WHITE UP POINTING INDEX, U+1F3FB is EMOJI MODIFIER FITZPATRICK TYPE-1-2.
+        state.setByString("| U+261D U+1F3FB U+261D U+1F3FB U+261D U+1F3FB");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+261D U+1F3FB | U+261D U+1F3FB U+261D U+1F3FB");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+261D U+1F3FB U+261D U+1F3FB | U+261D U+1F3FB");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+261D U+1F3FB U+261D U+1F3FB U+261D U+1F3FB |");
+        moveCursorToRightCursorableOffset(state, paint);
+        state.setByString("U+261D U+1F3FB U+261D U+1F3FB U+261D U+1F3FB |");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("U+261D U+1F3FB U+261D U+1F3FB | U+261D U+1F3FB");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("U+261D U+1F3FB | U+261D U+1F3FB U+261D U+1F3FB");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("| U+261D U+1F3FB U+261D U+1F3FB U+261D U+1F3FB");
+        moveCursorToLeftCursorableOffset(state, paint);
+        state.setByString("| U+261D U+1F3FB U+261D U+1F3FB U+261D U+1F3FB");
+        moveCursorToLeftCursorableOffset(state, paint);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java
index 1966313..7519627 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java
@@ -291,9 +291,6 @@
 
         @Override
         public void onActionModeFinished(ActionMode mode) {}
-
-        @Override
-        public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {}
     }
 
     private static final class MockActionModeCallback implements ActionMode.Callback {
diff --git a/docs/html/guide/topics/data/data-storage.jd b/docs/html/guide/topics/data/data-storage.jd
index 46db371..a745d00 100644
--- a/docs/html/guide/topics/data/data-storage.jd
+++ b/docs/html/guide/topics/data/data-storage.jd
@@ -178,6 +178,20 @@
 android.content.Context#MODE_WORLD_READABLE}, and {@link
 android.content.Context#MODE_WORLD_WRITEABLE}.</p>
 
+<p class="note"><strong>Note:</strong> The constants {@link
+android.content.Context#MODE_WORLD_READABLE} and {@link
+android.content.Context#MODE_WORLD_WRITEABLE} have been deprecated since API level 17.
+Starting from Android N their use will result in a {@link java.lang.SecurityException}
+to be thrown.
+This means that apps targeting Android N and higher
+cannot share private files by name, and attempts to share a "file://" URI will result in a
+{@link android.os.FileUriExposedException} to be thrown. If your app needs to share private
+files with other apps, it may use a {@link android.support.v4.content.FileProvider} with
+the {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+See also <a
+href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>.
+</p>
+
 <p>To read a file from internal storage:</p>
 
 <ol>
diff --git a/docs/html/training/basics/data-storage/files.jd b/docs/html/training/basics/data-storage/files.jd
index 49a9169..58a1d5f 100644
--- a/docs/html/training/basics/data-storage/files.jd
+++ b/docs/html/training/basics/data-storage/files.jd
@@ -59,7 +59,7 @@
 <p><b>Internal storage:</b></p>
 <ul>
 <li>It's always available.</li>
-<li>Files saved here are accessible by only your app by default.</li>
+<li>Files saved here are accessible by only your app.</li>
 <li>When the user uninstalls your app, the system removes all your app's files from
 internal storage.</li>
 </ul>
@@ -83,6 +83,12 @@
 with other apps or allow the user to access with a computer.</p>
 </div>
 
+<p class="note">
+<strong>Note:</strong> Before Android N, internal files could be made accessible to other
+apps by means of relaxing file system permissions. This is no longer the case. If you wish
+to make the content of a private file accessible to other apps, your app may use the
+{@link android.support.v4.content.FileProvider}. See <a
+href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>.</p>
 
 <p class="note" style="clear:both">
 <strong>Tip:</strong> Although apps are installed onto the internal storage by
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index 5efc00c..b6a209f 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -45,15 +45,11 @@
                 int outlineLeft, int outlineTop, int outlineRight, int outlineBottom,
                 float outlineRadius, int outlineAlpha, float decodeScale) {
             opticalRect = new Rect(opticalLeft, opticalTop, opticalRight, opticalBottom);
-            outlineRect = new Rect(outlineLeft, outlineTop, outlineRight, outlineBottom);
+            opticalRect.scale(decodeScale);
 
-            if (decodeScale != 1.0f) {
-                // if bitmap was scaled when decoded, scale the insets from the metadata values
-                opticalRect.scale(decodeScale);
+            outlineRect = scaleInsets(outlineLeft, outlineTop,
+                    outlineRight, outlineBottom, decodeScale);
 
-                // round inward while scaling outline, as the outline should always be conservative
-                outlineRect.scaleRoundIn(decodeScale);
-            }
             this.outlineRadius = outlineRadius * decodeScale;
             this.outlineAlpha = outlineAlpha / 255.0f;
         }
@@ -62,6 +58,23 @@
         public final Rect outlineRect;
         public final float outlineRadius;
         public final float outlineAlpha;
+
+        /**
+         * Scales up the rect by the given scale, ceiling values, so actual outline Rect
+         * grows toward the inside.
+         */
+        public static Rect scaleInsets(int left, int top, int right, int bottom, float scale) {
+            if (scale == 1.0f) {
+                return new Rect(left, top, right, bottom);
+            }
+
+            Rect result = new Rect();
+            result.left = (int) Math.ceil(left * scale);
+            result.top = (int) Math.ceil(top * scale);
+            result.right = (int) Math.ceil(right * scale);
+            result.bottom = (int) Math.ceil(bottom * scale);
+            return  result;
+        }
     }
 
     private final Bitmap mBitmap;
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index da3deff..de391af 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,6 +16,9 @@
 
 package android.graphics;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * The Path class encapsulates compound (multiple contour) geometric paths
  * consisting of straight line segments, quadratic curves, and cubic curves.
@@ -91,10 +94,22 @@
 
     /** Replace the contents of this with the contents of src.
     */
-    public void set(Path src) {
-        if (this != src) {
-            isSimplePath = src.isSimplePath;
-            native_set(mNativePath, src.mNativePath);
+    public void set(@NonNull Path src) {
+        if (this == src) {
+            return;
+        }
+        isSimplePath = src.isSimplePath;
+        native_set(mNativePath, src.mNativePath);
+        if (!isSimplePath) {
+            return;
+        }
+
+        if (rects != null && src.rects != null) {
+            rects.set(src.rects);
+        } else if (rects != null && src.rects == null) {
+            rects.setEmpty();
+        } else if (src.rects != null) {
+            rects = new Region(src.rects);
         }
     }
 
@@ -685,13 +700,13 @@
      * @param dst The translated path is written here. If this is null, then
      *            the original path is modified.
      */
-    public void offset(float dx, float dy, Path dst) {
-        long dstNative = 0;
+    public void offset(float dx, float dy, @Nullable Path dst) {
         if (dst != null) {
-            dstNative = dst.mNativePath;
-            dst.isSimplePath = false;
+            dst.set(this);
+        } else {
+            dst = this;
         }
-        native_offset(mNativePath, dx, dy, dstNative);
+        dst.offset(dx, dy);
     }
 
     /**
@@ -701,7 +716,15 @@
      * @param dy The amount in the Y direction to offset the entire path
      */
     public void offset(float dx, float dy) {
-        isSimplePath = false;
+        if (isSimplePath && rects == null) {
+            // nothing to offset
+            return;
+        }
+        if (isSimplePath && dx == Math.rint(dx) && dy == Math.rint(dy)) {
+            rects.translate((int) dx, (int) dy);
+        } else {
+            isSimplePath = false;
+        }
         native_offset(mNativePath, dx, dy);
     }
 
@@ -823,7 +846,6 @@
     private static native void native_addPath(long nPath, long src, float dx, float dy);
     private static native void native_addPath(long nPath, long src);
     private static native void native_addPath(long nPath, long src, long matrix);
-    private static native void native_offset(long nPath, float dx, float dy, long dst_path);
     private static native void native_offset(long nPath, float dx, float dy);
     private static native void native_setLastPoint(long nPath, float dx, float dy);
     private static native void native_transform(long nPath, long matrix, long dst_path);
diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java
index 0416159..2848949 100644
--- a/graphics/java/android/graphics/PathMeasure.java
+++ b/graphics/java/android/graphics/PathMeasure.java
@@ -112,7 +112,7 @@
      * Given a start and stop distance, return in dst the intervening
      * segment(s). If the segment is zero-length, return false, else return
      * true. startD and stopD are pinned to legal values (0..getLength()).
-     * If startD <= stopD then return false (and leave dst untouched).
+     * If startD >= stopD then return false (and leave dst untouched).
      * Begin the segment with a moveTo if startWithMoveTo is true.
      *
      * <p>On {@link android.os.Build.VERSION_CODES#KITKAT} and earlier
@@ -121,6 +121,19 @@
      * such as <code>dst.rLineTo(0, 0)</code>.</p>
      */
     public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
+        // Skia used to enforce this as part of it's API, but has since relaxed that restriction
+        // so to maintain consistency in our API we enforce the preconditions here.
+        float length = getLength();
+        if (startD < 0) {
+            startD = 0;
+        }
+        if (stopD > length) {
+            stopD = length;
+        }
+        if (startD >= stopD) {
+            return false;
+        }
+
         dst.isSimplePath = false;
         return native_getSegment(native_instance, startD, stopD, dst.ni(), startWithMoveTo);
     }
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 93ef3f0..7f579a2 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -647,16 +647,4 @@
         }
     }
 
-    /**
-     * Scales up the rect by the given scale, rounding values toward the inside.
-     * @hide
-     */
-    public void scaleRoundIn(float scale) {
-        if (scale != 1.0f) {
-            left = (int) Math.ceil(left * scale);
-            top = (int) Math.ceil(top * scale);
-            right = (int) Math.floor(right * scale);
-            bottom = (int) Math.floor(bottom * scale);
-        }
-    }
 }
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 51221b4..2b950d3 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -243,13 +243,15 @@
     }
 
     /**
-     * Invokes {@link #loadDrawable(Context)} on a background thread
-     * and then runs <code>andThen</code> on the UI thread when finished.
+     * Invokes {@link #loadDrawable(Context)} on a background thread and notifies the <code>
+     * {@link OnDrawableLoadedListener#onDrawableLoaded listener} </code> on the {@code handler}
+     * when finished.
      *
      * @param context {@link Context Context} in which to load the drawable; see
      *                {@link #loadDrawable(Context)}
-     * @param listener a callback to run on the provided
-     * @param handler {@link Handler} on which to run <code>andThen</code>.
+     * @param listener to be {@link OnDrawableLoadedListener#onDrawableLoaded notified} when
+     *                 {@link #loadDrawable(Context)} finished
+     * @param handler {@link Handler} on which to notify the {@code listener}
      */
     public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener,
             Handler handler) {
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 5b1cc80..d962385 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -706,18 +706,9 @@
 
         final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
         if (insets != null) {
-            if (mOutlineInsets == null) {
-                mOutlineInsets = new Rect();
-            }
-            final Rect outlineInsets = insets.outlineRect;
-            mOutlineInsets.left = Drawable.scaleFromDensity(
-                    outlineInsets.left, sourceDensity, targetDensity, false);
-            mOutlineInsets.top = Drawable.scaleFromDensity(
-                    outlineInsets.top, sourceDensity, targetDensity, false);
-            mOutlineInsets.right = Drawable.scaleFromDensity(
-                    outlineInsets.right, sourceDensity, targetDensity, false);
-            mOutlineInsets.bottom = Drawable.scaleFromDensity(
-                    outlineInsets.bottom, sourceDensity, targetDensity, false);
+            Rect outlineRect = insets.outlineRect;
+            mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top,
+                    outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity);
             mOutlineRadius = Drawable.scaleFromDensity(
                     insets.outlineRadius, sourceDensity, targetDensity);
         } else {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index 156f45f6..be390ff 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -193,12 +193,12 @@
         putSignatureImpl("NONEwithECDSA",
                 PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE");
 
-        putSignatureImpl("ECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1");
-        put("Alg.Alias.Signature.SHA1withECDSA", "ECDSA");
-        put("Alg.Alias.Signature.ECDSAwithSHA1", "ECDSA");
+        putSignatureImpl("SHA1withECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1");
+        put("Alg.Alias.Signature.ECDSA", "SHA1withECDSA");
+        put("Alg.Alias.Signature.ECDSAwithSHA1", "SHA1withECDSA");
         // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1)
-        put("Alg.Alias.Signature.1.2.840.10045.4.1", "ECDSA");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "ECDSA");
+        put("Alg.Alias.Signature.1.2.840.10045.4.1", "SHA1withECDSA");
+        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "SHA1withECDSA");
 
         // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
         putSignatureImpl("SHA224withECDSA",
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 6a3c890..44a24c8 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -47,6 +47,9 @@
         return false;
     }
 
+    int getWidth() { return mWidth; }
+    int getHeight() { return mHeight; }
+
     ANDROID_API bool setBlend(bool blend) {
         if (blend != mBlend) {
             mBlend = blend;
@@ -75,6 +78,10 @@
         mTransform = matrix ? new SkMatrix(*matrix) : nullptr;
     }
 
+    SkMatrix* getTransform() {
+        return mTransform;
+    }
+
     ANDROID_API void setPaint(const SkPaint* paint);
 
     void apply();
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index dc967e0..fae8e48 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -34,7 +34,7 @@
 FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
         uint32_t viewportWidth, uint32_t viewportHeight,
         const std::vector< sp<RenderNode> >& nodes,
-        const LightGeometry& lightGeometry, const Rect &contentDrawBounds, Caches* caches)
+        const LightGeometry& lightGeometry, const Rect &contentDrawBounds, Caches& caches)
         : mCanvasState(*this)
         , mCaches(caches)
         , mLightRadius(lightGeometry.radius) {
@@ -364,15 +364,13 @@
         casterPath = frameAllocatedPath;
     }
 
-
     if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) {
         Matrix4 shadowMatrixXY(casterNodeOp.localMatrix);
         Matrix4 shadowMatrixZ(casterNodeOp.localMatrix);
         node.applyViewPropertyTransforms(shadowMatrixXY, false);
         node.applyViewPropertyTransforms(shadowMatrixZ, true);
 
-        LOG_ALWAYS_FATAL_IF(!mCaches, "Caches needed for shadows");
-        sp<TessellationCache::ShadowTask> task = mCaches->tessellationCache.getShadowTask(
+        sp<TessellationCache::ShadowTask> task = mCaches.tessellationCache.getShadowTask(
                 mCanvasState.currentTransform(),
                 mCanvasState.getLocalClipBounds(),
                 casterAlpha >= 1.0f,
@@ -483,13 +481,14 @@
  * Defers an unmergeable, strokeable op, accounting correctly
  * for paint's style on the bounds being computed.
  */
-void FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+const BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
         BakedOpState::StrokeBehavior strokeBehavior) {
     // Note: here we account for stroke when baking the op
     BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
             mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
-    if (!bakedState) return; // quick rejected
+    if (!bakedState) return nullptr; // quick rejected
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
+    return bakedState;
 }
 
 /**
@@ -607,7 +606,10 @@
 }
 
 void FrameBuilder::deferPathOp(const PathOp& op) {
-    deferStrokeableOp(op, OpBatchType::Bitmap);
+    auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture);
+    if (CC_LIKELY(state)) {
+        mCaches.pathCache.precache(op.path, op.paint);
+    }
 }
 
 void FrameBuilder::deferPointsOp(const PointsOp& op) {
@@ -620,7 +622,12 @@
 }
 
 void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) {
-    deferStrokeableOp(op, tessBatchId(op));
+    auto state = deferStrokeableOp(op, tessBatchId(op));
+    if (CC_LIKELY(state && !op.paint->getPathEffect())) {
+        // TODO: consider storing tessellation task in BakedOpState
+        mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint),
+                op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry);
+    }
 }
 
 void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
@@ -660,15 +667,32 @@
     } else {
         currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
     }
+
+    FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
+    auto& totalTransform = bakedState->computedState.transform;
+    if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) {
+        fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
+    } else {
+        // Partial transform case, see BakedOpDispatcher::renderTextOp
+        float sx, sy;
+        totalTransform.decomposeScale(sx, sy);
+        fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::MakeScale(
+                roundf(std::max(1.0f, sx)),
+                roundf(std::max(1.0f, sy))));
+    }
 }
 
 void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) {
     BakedOpState* bakedState = tryBakeUnboundedOpState(op);
     if (!bakedState) return; // quick rejected
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
+
+    mCaches.fontRenderer.getFontRenderer().precache(
+            op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
 }
 
 void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) {
+    if (CC_UNLIKELY(!op.layer->isRenderable())) return;
     BakedOpState* bakedState = tryBakeOpState(op);
     if (!bakedState) return; // quick rejected
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer);
@@ -826,5 +850,9 @@
     }
 }
 
+void FrameBuilder::finishDefer() {
+    mCaches.fontRenderer.endPrecaching();
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 8a00d33..0b7a606 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -65,14 +65,16 @@
             uint32_t viewportWidth, uint32_t viewportHeight,
             const std::vector< sp<RenderNode> >& nodes,
             const LightGeometry& lightGeometry,
-            Caches* caches)
-            : FrameBuilder(layers, clip, viewportWidth, viewportHeight, nodes, lightGeometry, Rect(), caches) {}
+            Caches& caches)
+            : FrameBuilder(layers, clip, viewportWidth, viewportHeight,
+                    nodes, lightGeometry, Rect(), caches) {}
 
     FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
             uint32_t viewportWidth, uint32_t viewportHeight,
             const std::vector< sp<RenderNode> >& nodes,
             const LightGeometry& lightGeometry,
-            const Rect &contentDrawBounds, Caches* caches);
+            const Rect &contentDrawBounds,
+            Caches& caches);
 
     virtual ~FrameBuilder() {}
 
@@ -81,10 +83,10 @@
      *
      * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
      * state->op->opId to lookup a receiver that will be called when the op is replayed.
-     *
      */
     template <typename StaticDispatcher, typename Renderer>
     void replayBakedOps(Renderer& renderer) {
+        finishDefer();
         /**
          * Defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to
          * dispatch the op via a method on a static dispatcher when the op is replayed.
@@ -157,6 +159,7 @@
     virtual GLuint getTargetFbo() const override { return 0; }
 
 private:
+    void finishDefer();
     enum class ChildrenSelectMode {
         Negative,
         Positive
@@ -198,7 +201,7 @@
         return mAllocator.create<SkPath>();
     }
 
-    void deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+    const BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
             BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
 
     /**
@@ -230,7 +233,7 @@
 
     CanvasState mCanvasState;
 
-    Caches* mCaches = nullptr;
+    Caches& mCaches;
 
     float mLightRadius;
 
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index c305f65..2246cf9c 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -21,6 +21,8 @@
 #include <cstdio>
 #include <errno.h>
 #include <inttypes.h>
+#include <limits>
+#include <cmath>
 #include <sys/mman.h>
 
 namespace android {
@@ -202,6 +204,40 @@
 
 }
 
+static bool shouldReplace(SlowFrame& existing, SlowFrame& candidate) {
+    if (candidate.whenHours - existing.whenHours >= 24) {
+        // If the old slowframe is over 24 hours older than the candidate,
+        // replace it. It's too stale
+        return true;
+    }
+    if (candidate.frametimeMs > existing.frametimeMs) {
+        return true;
+    }
+    return false;
+}
+
+void JankTracker::updateSlowest(const FrameInfo& frame) {
+    uint16_t durationMs = static_cast<uint16_t>(std::min(
+            ns2ms(frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync]),
+            static_cast<nsecs_t>(std::numeric_limits<uint16_t>::max())));
+    uint16_t startHours = static_cast<uint16_t>(std::lround(
+            ns2s(frame[FrameInfoIndex::IntendedVsync]) / 3600.0f));
+    SlowFrame* toReplace = nullptr;
+    SlowFrame thisFrame{startHours, durationMs};
+    // First find the best candidate for replacement
+    for (SlowFrame& existing : mData->slowestFrames) {
+        // If we should replace the current data with the replacement candidate,
+        // it means the current data is worse than the replacement candidate
+        if (!toReplace || shouldReplace(existing, *toReplace)) {
+            toReplace = &existing;
+        }
+    }
+    // Now see if we should replace it
+    if (shouldReplace(*toReplace, thisFrame)) {
+        *toReplace = thisFrame;
+    }
+}
+
 void JankTracker::addFrame(const FrameInfo& frame) {
     mData->totalFrameCount++;
     // Fast-path for jank-free frames
@@ -215,6 +251,11 @@
         return;
     }
 
+    // For slowest frames we are still interested in frames that are otherwise
+    // exempt (such as first-draw). Although those frames don't directly impact
+    // smoothness, they do impact responsiveness.
+    updateSlowest(frame);
+
     if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) {
         return;
     }
@@ -247,6 +288,11 @@
     dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
     dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
     dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
+    dprintf(fd, "\nSlowest frames over last 24h: ");
+    for (auto& slowFrame : data->slowestFrames) {
+        if (!slowFrame.frametimeMs) continue;
+        dprintf(fd, "%ums ", slowFrame.frametimeMs);
+    }
     for (int i = 0; i < NUM_BUCKETS; i++) {
         dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
     }
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 3887e5e..1a4a489 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -39,6 +39,11 @@
     NUM_BUCKETS,
 };
 
+struct SlowFrame {
+    uint16_t whenHours; // When this occurred in CLOCK_MONOTONIC in hours
+    uint16_t frametimeMs; // How long the frame took in ms
+};
+
 // Try to keep as small as possible, should match ASHMEM_SIZE in
 // GraphicsStatsService.java
 struct ProfileData {
@@ -49,6 +54,8 @@
     uint32_t totalFrameCount;
     uint32_t jankFrameCount;
     nsecs_t statStartTime;
+
+    std::array<SlowFrame, 10> slowestFrames;
 };
 
 // TODO: Replace DrawProfiler with this
@@ -71,6 +78,7 @@
 private:
     void freeData();
     void setFrameInterval(nsecs_t frameIntervalNanos);
+    void updateSlowest(const FrameInfo& frame);
 
     static uint32_t findPercentile(const ProfileData* data, int p);
     static void dumpData(const ProfileData* data, int fd);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index e00ae66..1e5498b 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -216,6 +216,10 @@
         this->renderTarget = renderTarget;
     }
 
+    inline bool isRenderable() const {
+        return renderTarget != GL_NONE;
+    }
+
     void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) {
         texture.setWrap(wrap, bindTexture, force, renderTarget);
     }
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 5bce8ac..137316f 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -353,7 +353,7 @@
 
 bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap) {
     Caches& caches = Caches::getInstance();
-    if (layer && layer->getRenderTarget() != GL_NONE
+    if (layer && layer->isRenderable()
             && bitmap->width() <= caches.maxTextureSize
             && bitmap->height() <= caches.maxTextureSize) {
 
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index cf78781..4eeadb7 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -574,15 +574,19 @@
     // We ref the DeferredLayerUpdater due to its thread-safe ref-counting semantics.
     mDisplayList->ref(layerHandle);
 
-    Layer* layer = layerHandle->backingLayer();
+    // Note that the backing layer has *not* yet been updated, so don't trust
+    // its width, height, transform, etc...!
     Matrix4 totalTransform(*(mState.currentSnapshot()->transform));
-    totalTransform.multiply(layer->getTransform());
+    if (layerHandle->getTransform()) {
+        Matrix4 layerTransform(*layerHandle->getTransform());
+        totalTransform.multiply(layerTransform);
+    }
 
     addOp(alloc().create_trivial<TextureLayerOp>(
-            Rect(layer->getWidth(), layer->getHeight()),
+            Rect(layerHandle->getWidth(), layerHandle->getHeight()),
             totalTransform,
             getRecordedClip(),
-            layer));
+            layerHandle->backingLayer()));
 }
 
 void RecordingCanvas::callDrawGLFunction(Functor* functor) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1eb4fa0..acb88e2 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -191,13 +191,16 @@
             const SkPaint* paint) override;
 
     // Text
-    virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
-            const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
-            float boundsRight, float boundsBottom, float totalAdvance) override;
-    virtual void drawGlyphsOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
-            float hOffset, float vOffset, const SkPaint& paint) override;
     virtual bool drawTextAbsolutePos() const override { return false; }
 
+protected:
+    virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
+            const SkPaint& paint, float x, float y,
+            float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+            float totalAdvance) override;
+    virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint) override;
+
 private:
     const ClipBase* getRecordedClip() {
         return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc());
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index b1ecb71..5d24fa0 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -147,13 +147,6 @@
             float dstLeft, float dstTop, float dstRight, float dstBottom,
             const SkPaint* paint) override;
 
-    virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
-            const SkPaint& paint, float x, float y,
-            float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
-            float totalAdvance) override;
-    virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
-            float hOffset, float vOffset, const SkPaint& paint) override;
-
     virtual bool drawTextAbsolutePos() const  override { return true; }
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
@@ -169,6 +162,14 @@
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
     virtual void callDrawGLFunction(Functor* functor) override;
 
+protected:
+    virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
+            const SkPaint& paint, float x, float y,
+            float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+            float totalAdvance) override;
+    virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint) override;
+
 private:
     struct SaveRec {
         int              saveCount;
@@ -761,14 +762,8 @@
         const SkPaint& paint, float x, float y,
         float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
         float totalAdvance) {
-    // Set align to left for drawing, as we don't want individual
-    // glyphs centered or right-aligned; the offset above takes
-    // care of all alignment.
-    SkPaint paintCopy(paint);
-    paintCopy.setTextAlign(SkPaint::kLeft_Align);
-
     static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
-    mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy);
+    mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paint);
     drawTextDecorations(x, y, totalAdvance, paint);
 }
 
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 8c3eea3..7bfa15a 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -166,6 +166,11 @@
         bounds.offset(x, y);
     }
 
+    // Set align to left for drawing, as we don't want individual
+    // glyphs centered or right-aligned; the offset above takes
+    // care of all alignment.
+    paint.setTextAlign(Paint::kLeft_Align);
+
     DrawTextFunctor f(layout, this, glyphs.get(), pos.get(),
             paint, x, y, bounds, layout.getAdvance());
     MinikinUtils::forFontRun(layout, &paint, f);
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index dc669f0..5dbda43 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -53,6 +53,7 @@
 } // namespace SaveFlags
 
 namespace uirenderer {
+class SkiaCanvasProxy;
 namespace VectorDrawable {
 class Tree;
 };
@@ -205,19 +206,6 @@
             float dstLeft, float dstTop, float dstRight, float dstBottom,
             const SkPaint* paint) = 0;
 
-    // Text
-    /**
-     * drawText: count is of glyphs
-     * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
-     */
-    virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
-            const SkPaint& paint, float x, float y,
-            float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
-            float totalAdvance) = 0;
-    /** drawTextOnPath: count is of glyphs */
-    virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
-            float hOffset, float vOffset, const SkPaint& paint) = 0;
-
     /**
      * Specifies if the positions passed to ::drawText are absolute or relative
      * to the (x,y) value provided.
@@ -244,6 +232,22 @@
 
 protected:
     void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
+
+    /**
+     * drawText: count is of glyphs
+     * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
+     */
+    virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
+            const SkPaint& paint, float x, float y,
+            float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+            float totalAdvance) = 0;
+    /** drawTextOnPath: count is of glyphs */
+    virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint) = 0;
+
+    friend class DrawTextFunctor;
+    friend class DrawTextOnPathFunctor;
+    friend class uirenderer::SkiaCanvasProxy;
 };
 
 }; // namespace android
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 69c321c..9599c30 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -30,6 +30,7 @@
 public:
     Paint();
     Paint(const Paint& paint);
+    Paint(const SkPaint& paint);
     ~Paint();
 
     Paint& operator=(const Paint& other);
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 1172a0e..b27672c 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -29,6 +29,11 @@
         mHyphenEdit(paint.mHyphenEdit) {
 }
 
+Paint::Paint(const SkPaint& paint) : SkPaint(paint),
+        mLetterSpacing(0), mFontFeatureSettings(), mMinikinLangListId(0),
+        mFontVariant(VARIANT_DEFAULT) {
+}
+
 Paint::~Paint() {
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 31eec8c..eee5278 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -345,10 +345,10 @@
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
+    auto& caches = Caches::getInstance();
     FrameBuilder frameBuilder(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
-            mRenderNodes, mLightGeometry, mContentDrawBounds, &Caches::getInstance());
+            mRenderNodes, mLightGeometry, mContentDrawBounds, caches);
     mLayerUpdateQueue.clear();
-    auto&& caches = Caches::getInstance();
     BakedOpRenderer renderer(caches, mRenderThread.renderState(),
             mOpaque, mLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4aee61..e1c9319 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -16,6 +16,7 @@
 
 #include "TestUtils.h"
 
+#include "hwui/Paint.h"
 #include "DeferredLayerUpdater.h"
 #include "LayerRenderer.h"
 
@@ -87,50 +88,17 @@
     *outTotalAdvance = totalAdvance;
 }
 
-void TestUtils::drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
+
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
         const SkPaint& paint, float x, float y) {
-    // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
-    LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
-            "must use glyph encoding");
-    SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
-    SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
-
-    std::vector<glyph_t> glyphs;
-    std::vector<float> positions;
-    float totalAdvance;
-    Rect bounds;
-    layoutTextUnscaled(paint, text, &glyphs, &positions, &totalAdvance, &bounds);
-
-    // apply alignment via x parameter (which JNI layer would have done)
-    if (paint.getTextAlign() == SkPaint::kCenter_Align) {
-        x -= totalAdvance / 2;
-    } else if (paint.getTextAlign() == SkPaint::kRight_Align) {
-        x -= totalAdvance;
-    }
-
-    bounds.translate(x, y);
-
-    // Force left alignment, since alignment offset is already baked in
-    SkPaint alignPaintCopy(paint);
-    alignPaintCopy.setTextAlign(SkPaint::kLeft_Align);
-    canvas->drawGlyphs(glyphs.data(), positions.data(), glyphs.size(), alignPaintCopy, x, y,
-                bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+    auto utf16 = asciiToUtf16(text);
+    canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, 0, paint, nullptr);
 }
 
-void TestUtils::drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
         const SkPaint& paint, const SkPath& path) {
-    // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
-    LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
-            "must use glyph encoding");
-    SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
-    SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
-
-    std::vector<glyph_t> glyphs;
-    while (*text != '\0') {
-        SkUnichar unichar = SkUTF8_NextUnichar(&text);
-        glyphs.push_back(autoCache.getCache()->unicharToGlyph(unichar));
-    }
-    canvas->drawGlyphsOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
+    auto utf16 = asciiToUtf16(text);
+    canvas->drawTextOnPath(utf16.get(), strlen(text), 0, path, 0, 0, paint, nullptr);
 }
 
 void TestUtils::TestTask::run() {
@@ -143,12 +111,13 @@
     renderState.onGLContextDestroyed();
 }
 
-std::unique_ptr<uint16_t[]> TestUtils::utf8ToUtf16(const char* str) {
-    const size_t strLen = strlen(str);
-    const ssize_t utf16Len = utf8_to_utf16_length((uint8_t*) str, strLen);
-    std::unique_ptr<uint16_t[]> dst(new uint16_t[utf16Len + 1]);
-    utf8_to_utf16((uint8_t*) str, strLen, (char16_t*) dst.get());
-    return dst;
+std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) {
+    const int length = strlen(str);
+    std::unique_ptr<uint16_t[]> utf16(new uint16_t[length]);
+    for (int i = 0; i < length; i++) {
+        utf16.get()[i] = str[i];
+    }
+    return utf16;
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index a5e7a5f..73027e2 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -207,13 +207,13 @@
             std::vector<glyph_t>* outGlyphs, std::vector<float>* outPositions,
             float* outTotalAdvance, Rect* outBounds);
 
-    static void drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
+    static void drawUtf8ToCanvas(Canvas* canvas, const char* text,
             const SkPaint& paint, float x, float y);
 
-    static void drawUtf8ToCanvas(TestCanvas* canvas, const char* text,
+    static void drawUtf8ToCanvas(Canvas* canvas, const char* text,
             const SkPaint& paint, const SkPath& path);
 
-    static std::unique_ptr<uint16_t[]> utf8ToUtf16(const char* str);
+    static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str);
 
 private:
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
new file mode 100644
index 0000000..63c067b
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+#include "utils/Color.h"
+
+#include <minikin/Layout.h>
+#include <hwui/Paint.h>
+
+#include <cstdio>
+
+class GlyphStressAnimation;
+
+static TestScene::Registrar _GlyphStress(TestScene::Info{
+    "glyphstress",
+    "A stress test for both the glyph cache, and glyph rendering.",
+    TestScene::simpleCreateScene<GlyphStressAnimation>
+});
+
+class GlyphStressAnimation : public TestScene {
+public:
+    sp<RenderNode> container;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        container = TestUtils::createNode(0, 0, width, height, nullptr);
+        doFrame(0); // update container
+
+        canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+        canvas.drawRenderNode(container.get());
+    }
+
+    void doFrame(int frameNr) override {
+        std::unique_ptr<uint16_t[]> text = TestUtils::asciiToUtf16(
+                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        ssize_t textLength = 26 * 2;
+
+        TestCanvas canvas(
+                container->stagingProperties().getWidth(),
+                container->stagingProperties().getHeight());
+        Paint paint;
+        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        paint.setAntiAlias(true);
+        paint.setColor(Color::Black);
+        for (int i = 0; i < 5; i++) {
+            paint.setTextSize(10 + (frameNr % 20) + i * 20);
+            canvas.drawText(text.get(), 0, textLength, textLength,
+                    0, 100 * (i + 2), kBidi_Force_LTR, paint, nullptr);
+        }
+
+        container->setStagingDisplayList(canvas.finishRecording());
+    }
+};
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 7816f0f..9daf633 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -65,7 +65,7 @@
     auto nodes = createTestNodeList();
     while (state.KeepRunning()) {
         FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-                nodes, sLightGeometry, nullptr);
+                nodes, sLightGeometry, Caches::getInstance());
         benchmark::DoNotOptimize(&frameBuilder);
     }
 }
@@ -80,7 +80,7 @@
 
         while (state.KeepRunning()) {
             FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-                    nodes, sLightGeometry, nullptr);
+                    nodes, sLightGeometry, Caches::getInstance());
 
             BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -119,7 +119,7 @@
     while (state.KeepRunning()) {
         FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
                 SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
-                nodes, sLightGeometry, nullptr);
+                nodes, sLightGeometry, Caches::getInstance());
         benchmark::DoNotOptimize(&frameBuilder);
     }
 }
@@ -137,7 +137,7 @@
         while (state.KeepRunning()) {
             FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
                     SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
-                    nodes, sLightGeometry, nullptr);
+                    nodes, sLightGeometry, Caches::getInstance());
 
             BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index a467b5c..99400c6 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -107,7 +107,7 @@
 
 class FailRenderer : public TestRendererBase {};
 
-TEST(FrameBuilder, simple) {
+RENDERTHREAD_TEST(FrameBuilder, simple) {
     class SimpleTestRenderer : public TestRendererBase {
     public:
         void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
@@ -133,13 +133,13 @@
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
 }
 
-TEST(FrameBuilder, simpleStroke) {
+RENDERTHREAD_TEST(FrameBuilder, simpleStroke) {
     class SimpleStrokeTestRenderer : public TestRendererBase {
     public:
         void onPointsOp(const PointsOp& op, const BakedOpState& state) override {
@@ -159,13 +159,13 @@
         canvas.drawPoint(50, 50, strokedPaint);
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SimpleStrokeTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
 }
 
-TEST(FrameBuilder, simpleRejection) {
+RENDERTHREAD_TEST(FrameBuilder, simpleRejection) {
     auto node = TestUtils::createNode(0, 0, 200, 200,
             [](RenderProperties& props, RecordingCanvas& canvas) {
         canvas.save(SaveFlags::MatrixClip);
@@ -174,13 +174,13 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
 
     FailRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
 
-TEST(FrameBuilder, simpleBatching) {
+RENDERTHREAD_TEST(FrameBuilder, simpleBatching) {
     const int LOOPS = 5;
     class SimpleBatchingTestRenderer : public TestRendererBase {
     public:
@@ -209,14 +209,14 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SimpleBatchingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
             << "Expect number of ops = 2 * loop count";
 }
 
-TEST(FrameBuilder, clippedMerging) {
+RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
     class ClippedMergingTestRenderer : public TestRendererBase {
     public:
         void onMergedBitmapOps(const MergedBakedOpList& opList) override {
@@ -250,13 +250,13 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     ClippedMergingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-TEST(FrameBuilder, textMerging) {
+RENDERTHREAD_TEST(FrameBuilder, textMerging) {
     class TextMergingTestRenderer : public TestRendererBase {
     public:
         void onMergedTextOps(const MergedBakedOpList& opList) override {
@@ -278,13 +278,13 @@
         TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     TextMergingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
 }
 
-TEST(FrameBuilder, textStrikethrough) {
+RENDERTHREAD_TEST(FrameBuilder, textStrikethrough) {
     const int LOOPS = 5;
     class TextStrikethroughTestRenderer : public TestRendererBase {
     public:
@@ -309,7 +309,7 @@
         }
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     TextStrikethroughTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -319,7 +319,7 @@
 static auto styles = {
         SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style };
 
-TEST(FrameBuilder, textStyle) {
+RENDERTHREAD_TEST(FrameBuilder, textStyle) {
     class TextStyleTestRenderer : public TestRendererBase {
     public:
         void onMergedTextOps(const MergedBakedOpList& opList) override {
@@ -344,8 +344,9 @@
             EXPECT_TRUE(stroke.contains(fill));
             EXPECT_FALSE(fill.contains(stroke));
 
+            // outset by half the stroke width
             Rect outsetFill(fill);
-            outsetFill.outset(10);
+            outsetFill.outset(5);
             EXPECT_EQ(stroke, outsetFill);
         }
     };
@@ -365,7 +366,7 @@
         }
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     TextStyleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops";
@@ -398,13 +399,13 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     TextureLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
 }
 
-TEST(FrameBuilder, functor_reject) {
+RENDERTHREAD_TEST(FrameBuilder, functor_reject) {
     class FunctorTestRenderer : public TestRendererBase {
     public:
         void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override {
@@ -421,13 +422,14 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(scrolledFunctorView), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(scrolledFunctorView),
+            sLightGeometry, Caches::getInstance());
     FunctorTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected";
 }
 
-TEST(FrameBuilder, renderNode) {
+RENDERTHREAD_TEST(FrameBuilder, renderNode) {
     class RenderNodeTestRenderer : public TestRendererBase {
     public:
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -466,13 +468,13 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
     RenderNodeTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
 }
 
-TEST(FrameBuilder, clipped) {
+RENDERTHREAD_TEST(FrameBuilder, clipped) {
     class ClippedTestRenderer : public TestRendererBase {
     public:
         void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
@@ -491,12 +493,12 @@
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
             SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            200, 200, TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     ClippedTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
 
-TEST(FrameBuilder, saveLayer_simple) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) {
     class SaveLayerSimpleTestRenderer : public TestRendererBase {
     public:
         OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -533,13 +535,13 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SaveLayerSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-TEST(FrameBuilder, saveLayer_nested) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) {
     /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
      * - startTemporaryLayer2, rect2 endLayer2
      * - startTemporaryLayer1, rect1, drawLayer2, endLayer1
@@ -605,13 +607,13 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SaveLayerNestedTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
 }
 
-TEST(FrameBuilder, saveLayer_contentRejection) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) {
         auto node = TestUtils::createNode(0, 0, 200, 200,
                 [](RenderProperties& props, RecordingCanvas& canvas) {
         canvas.save(SaveFlags::MatrixClip);
@@ -625,14 +627,14 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
 
-TEST(FrameBuilder, saveLayerUnclipped_simple) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_simple) {
     class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
     public:
         void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -668,13 +670,13 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SaveLayerUnclippedSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-TEST(FrameBuilder, saveLayerUnclipped_mergedClears) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) {
     class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
     public:
         void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -722,14 +724,14 @@
         canvas.restoreToCount(restoreTo);
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SaveLayerUnclippedMergedClearsTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex())
             << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect.";
 }
 
-TEST(FrameBuilder, saveLayerUnclipped_clearClip) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_clearClip) {
     class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase {
     public:
         void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -763,13 +765,13 @@
 
     // draw with partial screen dirty, and assert we see that rect later
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(50, 50, 150, 150), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SaveLayerUnclippedClearClipTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-TEST(FrameBuilder, saveLayerUnclipped_reject) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_reject) {
     auto node = TestUtils::createNode(0, 0, 200, 200,
             [](RenderProperties& props, RecordingCanvas& canvas) {
         // unclipped savelayer + rect both in area that won't intersect with dirty
@@ -780,7 +782,7 @@
 
     // draw with partial screen dirty that doesn't intersect with savelayer
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     FailRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -789,7 +791,7 @@
  * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
  * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
  */
-TEST(FrameBuilder, saveLayerUnclipped_complex) {
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) {
     class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
     public:
         OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
@@ -840,7 +842,7 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     SaveLayerUnclippedComplexTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(12, renderer.getIndex());
@@ -898,7 +900,7 @@
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
     FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            syncedNodeList, sLightGeometry, nullptr);
+            syncedNodeList, sLightGeometry, Caches::getInstance());
     HwLayerSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
@@ -999,7 +1001,7 @@
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
 
     FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            syncedList, sLightGeometry, nullptr);
+            syncedList, sLightGeometry, Caches::getInstance());
     HwLayerComplexTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(13, renderer.getIndex());
@@ -1023,7 +1025,7 @@
     node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
-TEST(FrameBuilder, zReorder) {
+RENDERTHREAD_TEST(FrameBuilder, zReorder) {
     class ZReorderTestRenderer : public TestRendererBase {
     public:
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -1048,13 +1050,13 @@
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
     ZReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
 };
 
-TEST(FrameBuilder, projectionReorder) {
+RENDERTHREAD_TEST(FrameBuilder, projectionReorder) {
     static const int scrollX = 5;
     static const int scrollY = 10;
     class ProjectionReorderTestRenderer : public TestRendererBase {
@@ -1139,7 +1141,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
     ProjectionReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(3, renderer.getIndex());
@@ -1222,7 +1224,7 @@
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
     FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            syncedList, sLightGeometry, nullptr);
+            syncedList, sLightGeometry, Caches::getInstance());
     ProjectionHwLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
@@ -1278,7 +1280,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
     ProjectionChildScrollTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -1321,7 +1323,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(parent), sLightGeometry, &Caches::getInstance());
+            TestUtils::createSyncedNodeList(parent), sLightGeometry, Caches::getInstance());
     ShadowTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -1363,8 +1365,7 @@
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
             TestUtils::createSyncedNodeList(parent),
-            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50},
-            &Caches::getInstance());
+            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
     ShadowSaveLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(5, renderer.getIndex());
@@ -1416,8 +1417,7 @@
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
     FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
             syncedList,
-            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 30},
-            &Caches::getInstance());
+            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 30}, Caches::getInstance());
     ShadowHwLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(5, renderer.getIndex());
@@ -1426,7 +1426,7 @@
     *layerHandle = nullptr;
 }
 
-TEST(FrameBuilder, shadowLayering) {
+RENDERTHREAD_TEST(FrameBuilder, shadowLayering) {
     class ShadowLayeringTestRenderer : public TestRendererBase {
     public:
         void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
@@ -1447,8 +1447,7 @@
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
             TestUtils::createSyncedNodeList(parent),
-            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50},
-            &Caches::getInstance());
+            (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
     ShadowLayeringTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -1476,13 +1475,13 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometry, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
     PropertyTestRenderer renderer(opValidateCallback);
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
 }
 
-TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) {
     testProperty([](RenderProperties& properties) {
         properties.setAlpha(0.5f);
         properties.setHasOverlappingRendering(false);
@@ -1491,7 +1490,7 @@
     });
 }
 
-TEST(FrameBuilder, renderPropClipping) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropClipping) {
     testProperty([](RenderProperties& properties) {
         properties.setClipToBounds(true);
         properties.setClipBounds(Rect(10, 20, 300, 400));
@@ -1501,7 +1500,7 @@
     });
 }
 
-TEST(FrameBuilder, renderPropRevealClip) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropRevealClip) {
     testProperty([](RenderProperties& properties) {
         properties.mutableRevealClip().set(true, 50, 50, 25);
     }, [](const RectOp& op, const BakedOpState& state) {
@@ -1512,7 +1511,7 @@
     });
 }
 
-TEST(FrameBuilder, renderPropOutlineClip) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropOutlineClip) {
     testProperty([](RenderProperties& properties) {
         properties.mutableOutline().setShouldClip(true);
         properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
@@ -1524,7 +1523,7 @@
     });
 }
 
-TEST(FrameBuilder, renderPropTransform) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropTransform) {
     testProperty([](RenderProperties& properties) {
         properties.setLeftTopRightBottom(10, 10, 110, 110);
 
@@ -1618,7 +1617,7 @@
     auto nodes = TestUtils::createSyncedNodeList(node); // sync before querying height
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            nodes, sLightGeometry, nullptr);
+            nodes, sLightGeometry, Caches::getInstance());
     SaveLayerAlphaClipTestRenderer renderer(outObservedData);
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 
@@ -1626,7 +1625,7 @@
     ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
 }
 
-TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {
     SaveLayerAlphaData observedData;
     testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
         properties.setTranslationX(10); // offset rendering content
@@ -1642,7 +1641,7 @@
             << "expect content to be translated as part of being clipped";
 }
 
-TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) {
     SaveLayerAlphaData observedData;
     testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
         // Translate and rotate the view so that the only visible part is the top left corner of
@@ -1661,7 +1660,7 @@
     EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
 }
 
-TEST(FrameBuilder, renderPropSaveLayerAlphaScale) {
+RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) {
     SaveLayerAlphaData observedData;
     testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
         properties.setPivotX(0);
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
index da786c7..9161f90 100644
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -41,7 +41,7 @@
     Caches& caches = Caches::getInstance();
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            TestUtils::createSyncedNodeList(node), sLightGeometery, nullptr);
+            TestUtils::createSyncedNodeList(node), sLightGeometery, Caches::getInstance());
     BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 6ab5110..42dabd3 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -23,6 +23,9 @@
 #include <tests/common/TestUtils.h>
 #include <utils/Color.h>
 
+#include <SkGradientShader.h>
+#include <SkShader.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -567,36 +570,69 @@
 
 TEST(RecordingCanvas, refPaint) {
     SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(20);
-    paint.setTextAlign(SkPaint::kLeft_Align);
-    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
         paint.setColor(SK_ColorBLUE);
-        // first three should use same paint
+        // first two should use same paint
         canvas.drawRect(0, 0, 200, 10, paint);
         SkPaint paintCopy(paint);
         canvas.drawRect(0, 10, 200, 20, paintCopy);
-        TestUtils::drawUtf8ToCanvas(&canvas, "helloworld", paint, 50, 25);
 
         // only here do we use different paint ptr
         paint.setColor(SK_ColorRED);
         canvas.drawRect(0, 20, 200, 30, paint);
     });
     auto ops = dl->getOps();
-    ASSERT_EQ(4u, ops.size());
+    ASSERT_EQ(3u, ops.size());
 
-    // first three are the same
+    // first two are the same
     EXPECT_NE(nullptr, ops[0]->paint);
     EXPECT_NE(&paint, ops[0]->paint);
     EXPECT_EQ(ops[0]->paint, ops[1]->paint);
-    EXPECT_EQ(ops[0]->paint, ops[2]->paint);
 
     // last is different, but still copied / non-null
-    EXPECT_NE(nullptr, ops[3]->paint);
-    EXPECT_NE(ops[0]->paint, ops[3]->paint);
-    EXPECT_NE(&paint, ops[3]->paint);
+    EXPECT_NE(nullptr, ops[2]->paint);
+    EXPECT_NE(ops[0]->paint, ops[2]->paint);
+    EXPECT_NE(&paint, ops[2]->paint);
+}
+
+TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
+    SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
+        SkPaint paint;
+        SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(bitmap,
+                SkShader::TileMode::kClamp_TileMode,
+                SkShader::TileMode::kClamp_TileMode));
+        paint.setShader(shader);
+        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
+    });
+    auto& bitmaps = dl->getBitmapResources();
+    EXPECT_EQ(1u, bitmaps.size());
+}
+
+TEST(RecordingCanvas, refBitmapInShader_composeShader) {
+    SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
+        SkPaint paint;
+        SkAutoTUnref<SkShader> shader1(SkShader::CreateBitmapShader(bitmap,
+                SkShader::TileMode::kClamp_TileMode,
+                SkShader::TileMode::kClamp_TileMode));
+
+        SkPoint center;
+        center.set(50, 50);
+        SkColor colors[2];
+        colors[0] = Color::Black;
+        colors[1] = Color::White;
+        SkAutoTUnref<SkShader> shader2(SkGradientShader::CreateRadial(center, 50, colors, nullptr, 2,
+                SkShader::TileMode::kRepeat_TileMode));
+
+        SkAutoTUnref<SkShader> composeShader(SkShader::CreateComposeShader(shader1, shader2,
+                SkXfermode::Mode::kMultiply_Mode));
+        paint.setShader(composeShader);
+        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
+    });
+    auto& bitmaps = dl->getBitmapResources();
+    EXPECT_EQ(1u, bitmaps.size());
 }
 
 TEST(RecordingCanvas, drawText) {
@@ -605,7 +641,7 @@
         paint.setAntiAlias(true);
         paint.setTextSize(20);
         paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-        std::unique_ptr<uint16_t[]> dst = TestUtils::utf8ToUtf16("HELLO");
+        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
         canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
     });
 
@@ -629,7 +665,7 @@
         paint.setAntiAlias(true);
         paint.setTextSize(20);
         paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-        std::unique_ptr<uint16_t[]> dst = TestUtils::utf8ToUtf16("HELLO");
+        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
         canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
     });
 
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index a5b3179..4bf0852 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1888,6 +1888,19 @@
         for (ExifTag tag : IFD_POINTER_TAGS) {
             setAttribute(tag.name, null);
         }
+        // Remove old thumbnail data
+        setAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name, null);
+        setAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, null);
+
+        // Remove null value tags.
+        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
+            for (Object obj : mAttributes[hint].entrySet().toArray()) {
+                Map.Entry entry = (Map.Entry) obj;
+                if (entry.getValue() == null) {
+                    mAttributes[hint].remove(entry.getKey());
+                }
+            }
+        }
 
         // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
         // offset when there is one or more tags in the thumbnail IFD.
@@ -1900,25 +1913,12 @@
         if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
             mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name, "0");
         }
-        // Remove old thumbnail data
-        setAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name, null);
-        setAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, null);
         if (mHasThumbnail) {
             mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name, "0");
             mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
                     String.valueOf(mThumbnailLength));
         }
 
-        // Remove null value tags.
-        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
-            for (Object obj : mAttributes[hint].entrySet().toArray()) {
-                Map.Entry entry = (Map.Entry) obj;
-                if (entry.getValue() == null) {
-                    mAttributes[hint].remove(entry.getKey());
-                }
-            }
-        }
-
         // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
         // value which has a bigger size than 4 bytes.
         for (int i = 0; i < 5; ++i) {
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index fe2796c..c1805cb 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -57,8 +57,9 @@
  * <h3>Standard Extra Data</h3>
  *
  * <p>These are the current standard fields that can be used as extra data via
- * {@link #subscribe(String, Bundle, SubscriptionCallback)}, {@link #unsubscribe(String, Bundle)},
- * and {@link SubscriptionCallback#onChildrenLoaded(String, List, Bundle)}.
+ * {@link #subscribe(String, Bundle, SubscriptionCallback)},
+ * {@link #unsubscribe(String, SubscriptionCallback)}, and
+ * {@link SubscriptionCallback#onChildrenLoaded(String, List, Bundle)}.
  *
  * <ul>
  *     <li> {@link #EXTRA_PAGE}
@@ -383,7 +384,7 @@
     }
 
     /**
-     * Unsubscribes for changes to the children of the specified media id.
+     * Unsubscribes for changes to the children of the specified media id through a callback.
      * <p>
      * The query callback will no longer be invoked for results associated with
      * this id once this method returns.
@@ -391,13 +392,13 @@
      *
      * @param parentId The id of the parent media item whose list of children
      *            will be unsubscribed.
-     * @param options A bundle sent to the media browse service to subscribe.
+     * @param callback A callback sent to the media browse service to subscribe.
      */
-    public void unsubscribe(@NonNull String parentId, @NonNull Bundle options) {
-        if (options == null) {
-            throw new IllegalArgumentException("options are null");
+    public void unsubscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback is null");
         }
-        unsubscribeInternal(parentId, options);
+        unsubscribeInternal(parentId, callback);
     }
 
     /**
@@ -490,7 +491,7 @@
         }
     }
 
-    private void unsubscribeInternal(String parentId, Bundle options) {
+    private void unsubscribeInternal(String parentId, SubscriptionCallback callback) {
         // Check arguments.
         if (TextUtils.isEmpty(parentId)) {
             throw new IllegalArgumentException("parentId is empty.");
@@ -500,16 +501,21 @@
         Subscription sub = mSubscriptions.get(parentId);
 
         // Tell the service if necessary.
-        if (sub != null && sub.removeCallback(options) && mState == CONNECT_STATE_CONNECTED) {
+        if (mState == CONNECT_STATE_CONNECTED && sub != null) {
             try {
-                // NOTE: Do not call removeSubscriptionWithOptions when options are null. Otherwise,
-                // it will break the action of support library which expects removeSubscription will
-                // be called when options are null.
-                if (options == null) {
+                if (callback == null) {
                     mServiceBinder.removeSubscription(parentId, mServiceCallbacks);
                 } else {
-                    mServiceBinder.removeSubscriptionWithOptions(
-                            parentId, options, mServiceCallbacks);
+                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+                    final List<Bundle> optionsList = sub.getOptionsList();
+                    for (int i = callbacks.size() - 1; i >= 0; --i) {
+                        if (callbacks.get(i) == callback) {
+                            mServiceBinder.removeSubscriptionWithOptions(
+                                    parentId, optionsList.get(i), mServiceCallbacks);
+                            callbacks.remove(i);
+                            optionsList.remove(i);
+                        }
+                    }
                 }
             } catch (RemoteException ex) {
                 // Process is crashing. We will disconnect, and upon reconnect we will
@@ -517,7 +523,8 @@
                 Log.d(TAG, "removeSubscription failed with RemoteException parentId=" + parentId);
             }
         }
-        if (sub != null && sub.isEmpty()) {
+
+        if (sub != null && (sub.isEmpty() || callback == null)) {
             mSubscriptions.remove(parentId);
         }
     }
@@ -1118,16 +1125,5 @@
             mCallbacks.add(callback);
             mOptionsList.add(options);
         }
-
-        public boolean removeCallback(Bundle options) {
-            for (int i = 0; i < mOptionsList.size(); ++i) {
-                if (MediaBrowserUtils.areSameOptions(mOptionsList.get(i), options)) {
-                    mCallbacks.remove(i);
-                    mOptionsList.remove(i);
-                    return true;
-                }
-            }
-            return false;
-        }
     }
 }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 612a147..97ef6d8 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1689,19 +1689,20 @@
         public abstract void onTune(Uri channelUri);
 
         /**
-         * Called when the application requests to tune to a given channel for TV program recording.
+         * Calls {@link #onTune(Uri)}. Override this method in order to handle domain-specific
+         * features that are only known between certain TV inputs and their clients.
          *
          * <p>The application may call this method before starting or after stopping recording, but
          * not during recording.
          *
-         * <p>The session must call {@link #notifyTuned()} if the tune request was fulfilled, or
+         * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or
          * {@link #notifyError(int)} otherwise.
          *
          * @param channelUri The URI of a channel.
-         * @param params Extra parameters.
-         * @hide
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
          */
-        @SystemApi
         public void onTune(Uri channelUri, Bundle params) {
             onTune(channelUri);
         }
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index da1002d..a5ff29f 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -91,22 +91,23 @@
      * Tunes to a given channel for TV program recording. The first tune request will create a new
      * recording session for the corresponding TV input and establish a connection between the
      * application and the session. If recording has already started in the current recording
-     * session, this method throws an exception.
+     * session, this method throws an exception. This can be used to provide domain-specific
+     * features that are only known between certain client and their TV inputs.
      *
      * <p>The application may call this method before starting or after stopping recording, but not
      * during recording.
      *
      * <p>The recording session will respond by calling
-     * {@link RecordingCallback#onTuned()} if the tune request was fulfilled, or
+     * {@link RecordingCallback#onTuned(Uri)} if the tune request was fulfilled, or
      * {@link RecordingCallback#onError(int)} otherwise.
      *
      * @param inputId The ID of the TV input for the given channel.
      * @param channelUri The URI of a channel.
-     * @param params Extra parameters.
+     * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+     *            name, i.e. prefixed with a package name you own, so that different developers will
+     *            not create conflicting keys.
      * @throws IllegalStateException If recording is already started.
-     * @hide
      */
-    @SystemApi
     public void tune(String inputId, Uri channelUri, Bundle params) {
         if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
         if (TextUtils.isEmpty(inputId)) {
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index 0e7013c..aa99248 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -355,6 +355,18 @@
         }
     }
 
+    /**
+     * Returns object size in 64-bit integer.
+     *
+     * The object size stored in MtpObjectInfo is unsigned 32-bit integer.
+     * The methods reads 64-bit object size from the object property so that it can fetch 4GB+
+     * object size correctly.
+     * @hide
+     */
+    public long getObjectSizeLong(int handle, int format) throws IOException {
+        return native_get_object_size_long(handle, format);
+    }
+
     // used by the JNI code
     private long mNativeContext;
 
@@ -381,4 +393,5 @@
     private native int native_submit_event_request();
     private native MtpEvent native_reap_event_request(int handle);
     private native void native_discard_event_request(int handle);
+    private native long native_get_object_size_long(int handle, int format) throws IOException;
 }
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index f593685..ae86632 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -574,6 +574,9 @@
      * Remove the subscription.
      */
     private boolean removeSubscription(String id, ConnectionRecord connection, Bundle options) {
+        if (options == null) {
+            return connection.subscriptions.remove(id) != null;
+        }
         boolean removed = false;
         List<Bundle> optionsList = connection.subscriptions.get(id);
         if (optionsList != null) {
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index 0ecb750..f7c6770 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -42,6 +42,7 @@
 #include "MtpDeviceInfo.h"
 #include "MtpStorageInfo.h"
 #include "MtpObjectInfo.h"
+#include "MtpProperty.h"
 
 using namespace android;
 
@@ -700,6 +701,40 @@
     device->discardEventRequest(seq);
 }
 
+static jlong android_mtp_MtpDevice_get_object_size_long(
+        JNIEnv *env, jobject thiz, jint handle, jint format) {
+    MtpDevice* const device = get_device_from_object(env, thiz);
+    if (!device) {
+        env->ThrowNew(clazz_io_exception, "Failed to obtain MtpDevice.");
+        return 0;
+    }
+
+    std::unique_ptr<MtpProperty> property(
+            device->getObjectPropDesc(MTP_PROPERTY_OBJECT_SIZE, format));
+    if (!property) {
+        env->ThrowNew(clazz_io_exception, "Failed to obtain property desc.");
+        return 0;
+    }
+
+    if (property->getDataType() != MTP_TYPE_UINT64) {
+        env->ThrowNew(clazz_io_exception, "Unexpected property data type.");
+        return 0;
+    }
+
+    if (!device->getObjectPropValue(handle, property.get())) {
+        env->ThrowNew(clazz_io_exception, "Failed to obtain property value.");
+        return 0;
+    }
+
+    const jlong object_size = static_cast<jlong>(property->getCurrentValue().u.u64);
+    if (object_size < 0) {
+        env->ThrowNew(clazz_io_exception, "Object size is too large to express as jlong.");
+        return 0;
+    }
+
+    return object_size;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -733,6 +768,8 @@
     {"native_reap_event_request",   "(I)Landroid/mtp/MtpEvent;",
                                             (void *)android_mtp_MtpDevice_reap_event_request},
     {"native_discard_event_request", "(I)V", (void *)android_mtp_MtpDevice_discard_event_request},
+
+    {"native_get_object_size_long", "(II)J", (void *)android_mtp_MtpDevice_get_object_size_long},
 };
 
 int register_android_mtp_MtpDevice(JNIEnv *env)
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index aea8585..f21fd88 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -27,7 +27,8 @@
         <activity
             android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
             android:label="@string/action_bar_label"
-            android:theme="@style/AppTheme" >
+            android:theme="@style/AppTheme"
+            android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.net.conn.CAPTIVE_PORTAL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index be08385..14609b2 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -44,7 +44,7 @@
         <activity
             android:name=".LauncherActivity"
             android:theme="@android:style/Theme.NoDisplay"
-            android:icon="@drawable/ic_files_app"
+            android:icon="@mipmap/ic_launcher_download"
             android:label="@string/downloads_label">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -55,7 +55,7 @@
         <activity
             android:name=".FilesActivity"
             android:theme="@style/DocumentsTheme"
-            android:icon="@drawable/ic_files_app"
+            android:icon="@mipmap/ic_launcher_download"
             android:label="@string/downloads_label"
             android:documentLaunchMode="intoExisting">
             <intent-filter>
diff --git a/packages/DocumentsUI/res/color/item_root_primary_text.xml b/packages/DocumentsUI/res/color/item_root_primary_text.xml
index 551245f..a5a65b2 100644
--- a/packages/DocumentsUI/res/color/item_root_primary_text.xml
+++ b/packages/DocumentsUI/res/color/item_root_primary_text.xml
@@ -15,8 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" android:state_activated="true" android:color="?android:colorAccent" />
-    <item android:state_focused="false" android:state_activated="true" android:color="?android:colorAccent" />
+  <item android:state_focused="true" android:state_activated="true" android:color="@color/root_activated_color" />
+  <item android:state_focused="false" android:state_activated="true" android:color="@color/root_activated_color" />
     <item android:state_enabled="false" android:alpha="@*android:dimen/disabled_alpha_material_light" android:color="@*android:color/primary_text_default_material_light" />
     <item android:color="@*android:color/primary_text_default_material_light" />
 </selector>
diff --git a/packages/DocumentsUI/res/drawable/ic_files_app.xml b/packages/DocumentsUI/res/drawable/ic_files_app.xml
deleted file mode 100644
index 76e3ba6..0000000
--- a/packages/DocumentsUI/res/drawable/ic_files_app.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/ic_launcher_download"
-    android:tint="?android:attr/colorControlNormal"
-    android:autoMirrored="true" />
diff --git a/packages/DocumentsUI/res/layout/dialog_file_name.xml b/packages/DocumentsUI/res/layout/dialog_file_name.xml
index 5ed476f..3a95a13 100644
--- a/packages/DocumentsUI/res/layout/dialog_file_name.xml
+++ b/packages/DocumentsUI/res/layout/dialog_file_name.xml
@@ -17,6 +17,7 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:fitsSystemWindows="true"
     android:padding="?android:attr/listPreferredItemPaddingEnd">
 
     <EditText
diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml
index 7aac620..a889b9f 100644
--- a/packages/DocumentsUI/res/layout/fragment_save.xml
+++ b/packages/DocumentsUI/res/layout/fragment_save.xml
@@ -21,6 +21,7 @@
     android:orientation="horizontal"
     android:baselineAligned="false"
     android:gravity="center_vertical"
+    android:fitsSystemWindows="true"
     android:minHeight="?android:attr/listPreferredItemHeightSmall">
 
     <FrameLayout
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_launcher_download.png b/packages/DocumentsUI/res/mipmap-hdpi/ic_launcher_download.png
similarity index 100%
rename from packages/DocumentsUI/res/drawable-hdpi/ic_launcher_download.png
rename to packages/DocumentsUI/res/mipmap-hdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_launcher_download.png b/packages/DocumentsUI/res/mipmap-mdpi/ic_launcher_download.png
similarity index 100%
rename from packages/DocumentsUI/res/drawable-mdpi/ic_launcher_download.png
rename to packages/DocumentsUI/res/mipmap-mdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_launcher_download.png b/packages/DocumentsUI/res/mipmap-xhdpi/ic_launcher_download.png
similarity index 100%
rename from packages/DocumentsUI/res/drawable-xhdpi/ic_launcher_download.png
rename to packages/DocumentsUI/res/mipmap-xhdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_launcher_download.png b/packages/DocumentsUI/res/mipmap-xxhdpi/ic_launcher_download.png
similarity index 100%
rename from packages/DocumentsUI/res/drawable-xxhdpi/ic_launcher_download.png
rename to packages/DocumentsUI/res/mipmap-xxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_launcher_download.png b/packages/DocumentsUI/res/mipmap-xxxhdpi/ic_launcher_download.png
similarity index 100%
rename from packages/DocumentsUI/res/drawable-xxxhdpi/ic_launcher_download.png
rename to packages/DocumentsUI/res/mipmap-xxxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index 04b7fee..51e04b6 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -16,6 +16,7 @@
 
 <resources>
     <color name="material_grey_400">#ffbdbdbd</color>
+    <color name="material_teal_700">#ff00796b</color>
 
     <!-- This is the window background, but also the background for anything
          else that needs to manually declare a background matching the "default"
@@ -36,6 +37,8 @@
 
     <color name="item_doc_background_disabled">#fff4f4f4</color>
 
+    <color name="root_activated_color">@color/material_teal_700</color>
+
     <!-- TODO: Would be nice to move this to a color-set, but not sure how to support animation -->
     <color name="item_doc_background">#fffafafa</color>
     <color name="item_doc_background_selected">#ffe0f2f1</color>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 0d098e6..b26ee97 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -204,6 +204,9 @@
     <string name="open_external_dialog_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
         access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory on
         <xliff:g id="storage" example="SD Card"><i>^3</i></xliff:g>?</string>
+    <!-- Text in an alert dialog asking user to grant app access to all data in an external storage volume -->
+    <string name="open_external_dialog_root_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
+        access to your data, including photos and videos, on <xliff:g id="storage" example="SD Card"><i>^2</i></xliff:g>?</string>
     <!-- Checkbox that allows user to not be questioned about the directory access request again -->
     <string name="never_ask_again">Don\'t ask again</string>
     <!-- Text in the button asking user to allow access to a given directory. -->
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index a548d89..b16554c 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -36,6 +36,8 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowActionModeOverlay">true</item>
         <item name="android:windowNoTitle">true</item>
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:fitsSystemWindows">false</item>
 
         <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
     </style>
@@ -43,7 +45,7 @@
     <style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
         <item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
         <item name="android:minHeight">3dp</item>
-        <item name="android:maxHeight">3dp</item>    
+        <item name="android:maxHeight">3dp</item>
     </style>
 
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 2911027..54202d4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -58,6 +58,7 @@
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -79,6 +80,7 @@
     private int mLayoutId;
 
     private boolean mNavDrawerHasFocus;
+    private long mStartTime;
 
     public abstract void onDocumentPicked(DocumentInfo doc, Model model);
     public abstract void onDocumentsPicked(List<DocumentInfo> docs);
@@ -96,16 +98,14 @@
     @CallSuper
     @Override
     public void onCreate(Bundle icicle) {
+        // Record the time when onCreate is invoked for metric.
+        mStartTime = new Date().getTime();
+
         super.onCreate(icicle);
 
         final Intent intent = getIntent();
 
-        // If startup benchmark is requested by a whitelisted testing package, then close the
-        // activity once idle, and notify the testing activity.
-        if (intent.getBooleanExtra(EXTRA_BENCHMARK, false) &&
-                BENCHMARK_TESTING_PACKAGE.equals(getCallingPackage())) {
-            closeOnIdleForTesting();
-        }
+        addListenerForLaunchCompletion();
 
         setContentView(mLayoutId);
 
@@ -127,6 +127,7 @@
         mSearchManager = new SearchViewManager(this, icicle);
 
         DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
+        Display.adjustToolbar(toolbar, this);
         setActionBar(toolbar);
         mNavigator = new NavigationView(
                 mDrawer,
@@ -604,12 +605,10 @@
         return super.onKeyDown(keyCode, event);
     }
 
-    @VisibleForTesting
     public void addEventListener(EventListener listener) {
         mEventListeners.add(listener);
     }
 
-    @VisibleForTesting
     public void removeEventListener(EventListener listener) {
         mEventListeners.remove(listener);
     }
@@ -673,6 +672,44 @@
         return false;
     }
 
+    /**
+     * Closes the activity when it's idle.
+     */
+    private void addListenerForLaunchCompletion() {
+        addEventListener(new EventListener() {
+            @Override
+            public void onDirectoryNavigated(Uri uri) {
+            }
+
+            @Override
+            public void onDirectoryLoaded(Uri uri) {
+                removeEventListener(this);
+                getMainLooper().getQueue().addIdleHandler(new IdleHandler() {
+                    @Override
+                    public boolean queueIdle() {
+                        // If startup benchmark is requested by a whitelisted testing package, then
+                        // close the activity once idle, and notify the testing activity.
+                        if (getIntent().getBooleanExtra(EXTRA_BENCHMARK, false) &&
+                                BENCHMARK_TESTING_PACKAGE.equals(getCallingPackage())) {
+                            setResult(RESULT_OK);
+                            finish();
+                        }
+
+                        Metrics.logStartupMs(
+                                BaseActivity.this, (int) (new Date().getTime() - mStartTime));
+
+                        // Remove the idle handler.
+                        return false;
+                    }
+                });
+                new Handler().post(new Runnable() {
+                    @Override public void run() {
+                    }
+                });
+            }
+        });
+    }
+
     private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
         private RootInfo mRoot;
 
@@ -694,33 +731,6 @@
         }
     }
 
-    /**
-     * Closes the activity when it's idle. Used only for tests.
-     */
-    private void closeOnIdleForTesting() {
-        addEventListener(new EventListener() {
-            @Override
-            public void onDirectoryNavigated(Uri uri) {
-            }
-
-            @Override
-            public void onDirectoryLoaded(Uri uri) {
-                getMainLooper().getQueue().addIdleHandler(new IdleHandler() {
-                    @Override
-                    public boolean queueIdle() {
-                        setResult(RESULT_OK);
-                        finish();
-                        return false;
-                    }
-                });
-                new Handler().post(new Runnable() {
-                    @Override public void run() {
-                    }
-                });
-            }
-        });
-    }
-
     private static final class HandleRootsChangedTask
             extends PairedTask<BaseActivity, RootInfo, RootInfo> {
         DocumentInfo mDownloadsDocument;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Display.java b/packages/DocumentsUI/src/com/android/documentsui/Display.java
index bae2d58..d46a3ea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Display.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Display.java
@@ -20,13 +20,15 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.TypedValue;
+import android.view.WindowManager;
+import android.widget.Toolbar;
 
 /*
  * Convenience class for getting display related attributes
  */
 public final class Display {
     /*
-     * Returns the screen width in pixels.
+     * Returns the screen width in raw pixels.
      */
     public static float screenWidth(Activity activity) {
         Point size = new Point();
@@ -42,15 +44,44 @@
     }
 
     /*
-     * Returns action bar height in pixels.
+     * Returns action bar height in raw pixels.
      */
     public static float actionBarHeight(Context context) {
-        int actionBarHeight = 0;
+        int height = 0;
         TypedValue tv = new TypedValue();
         if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
-            actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
+            height = TypedValue.complexToDimensionPixelSize(tv.data,
                     context.getResources().getDisplayMetrics());
         }
-        return actionBarHeight;
+        return height;
+    }
+
+    /*
+     * Returns status bar height in raw pixels.
+     */
+    private static int statusBarHeight(Context context) {
+        int height = 0;
+        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen",
+                "android");
+        if (resourceId > 0) {
+            height = context.getResources().getDimensionPixelSize(resourceId);
+        }
+        return height;
+    }
+
+    /*
+     * Adjusts toolbar for the layout with translucent status bar. Increases the
+     * height of the toolbar and adds padding at the top to accommodate status bar visible above
+     * toolbar.
+     */
+    public static void adjustToolbar(Toolbar toolbar, Activity activity) {
+        if ((activity.getWindow().getAttributes().flags
+                & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
+            int statusBarHeight = Display.statusBarHeight(activity);
+            toolbar.getLayoutParams().height = (int) (Display.actionBarHeight(activity)
+                    + statusBarHeight);
+            toolbar.setPadding(toolbar.getPaddingLeft(), statusBarHeight, toolbar.getPaddingRight(),
+                    toolbar.getPaddingBottom());
+        }
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 020f2c0..2dbb730 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -55,7 +55,7 @@
 
         View drawer = activity.findViewById(R.id.drawer_roots);
         Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
-
+        Display.adjustToolbar(toolbar, activity);
         drawer.getLayoutParams().width = calculateDrawerWidth(activity);
 
         ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 2af6c46..bed8b29 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -381,7 +381,7 @@
             }
 
             // Open the Close drawer if it is closed and we're at the top of a root.
-            if (size == 1) {
+            if (size <= 1) {
                 mDrawer.setOpen(true);
                 // Remember so we don't just close it again if back is pressed again.
                 mDrawerLastFiddled = System.currentTimeMillis();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java
index 8c4859f..2315664 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java
@@ -16,8 +16,6 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.Shared.DEBUG;
-import static com.android.documentsui.Shared.TAG;
 import static com.android.documentsui.State.MODE_UNKNOWN;
 
 import java.lang.annotation.Retention;
@@ -29,7 +27,6 @@
 import android.content.SharedPreferences;
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
-import android.util.Log;
 
 import com.android.documentsui.State.ViewMode;
 import com.android.documentsui.model.RootInfo;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index deef1c2..e6b22e6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -64,6 +64,7 @@
     private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
     private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
     private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
+    private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
 
     // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
     // root that is not explicitly recognized by the Metrics code (see {@link
@@ -347,7 +348,7 @@
     }
 
     /**
-     * Log the cancellation of a file operation.  Call this when a Job is canceled.
+     * Logs the cancellation of a file operation.  Call this when a Job is canceled.
      * @param context
      * @param operationType
      */
@@ -355,6 +356,15 @@
         logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType));
     }
 
+    /**
+     * Logs startup time in milliseconds.
+     * @param context
+     * @param startupMs Startup time in milliseconds.
+     */
+    public static void logStartupMs(Context context, int startupMs) {
+        logHistogram(context, COUNT_STARTUP_MS, startupMs);
+    }
+
     private static void logInterProviderFileOps(
             Context context,
             String histogram,
@@ -427,10 +437,14 @@
     public static void logValidScopedAccessRequest(Activity activity, String directory,
             @ScopedAccessGrant int type) {
         int index = -1;
-        for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
-            if (STANDARD_DIRECTORIES[i].equals(directory)) {
-                index = i;
-                break;
+        if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) {
+            index = -2;
+        } else {
+            for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
+                if (STANDARD_DIRECTORIES[i].equals(directory)) {
+                    index = i;
+                    break;
+                }
             }
         }
         final String packageName = activity.getCallingPackage();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index ab45af1..2fe2756 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -85,6 +85,9 @@
     private static final String EXTRA_APP_LABEL = "com.android.documentsui.APP_LABEL";
     private static final String EXTRA_VOLUME_LABEL = "com.android.documentsui.VOLUME_LABEL";
     private static final String EXTRA_VOLUME_UUID = "com.android.documentsui.VOLUME_UUID";
+    private static final String EXTRA_IS_ROOT = "com.android.documentsui.IS_ROOT";
+    // Special directory name representing the full volume
+    static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";
 
     private ContentProviderClient mExternalStorageClient;
 
@@ -114,13 +117,9 @@
             finish();
             return;
         }
-        final String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME);
+        String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME );
         if (directoryName == null) {
-            logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
-            if (DEBUG) Log.d(TAG, "missing extra " + EXTRA_DIRECTORY_NAME + " on " + intent);
-            setResult(RESULT_CANCELED);
-            finish();
-            return;
+            directoryName = DIRECTORY_ROOT;
         }
         final StorageVolume volume = (StorageVolume) storageVolume;
         if (getScopedAccessPermissionStatus(getApplicationContext(), getCallingPackage(),
@@ -157,9 +156,11 @@
         if (DEBUG)
             Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
                     + directoryName + ", and user " + userId);
+        final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
+        final File volumeRoot = storageVolume.getPathFile();
         File file;
         try {
-            file = new File(storageVolume.getPathFile(), directoryName).getCanonicalFile();
+            file = isRoot ? volumeRoot : new File(volumeRoot, directoryName).getCanonicalFile();
         } catch (IOException e) {
             Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
                     + " and directory " + directoryName);
@@ -169,16 +170,21 @@
         final StorageManager sm =
                 (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE);
 
-        final String root = file.getParent();
-        final String directory = file.getName();
-
-        // Verify directory is valid.
-        if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
-            if (DEBUG)
-                Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
-                        + file.getAbsolutePath() + "')");
-            logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
-            return false;
+        final String root, directory;
+        if (isRoot) {
+            root = volumeRoot.getAbsolutePath();
+            directory = ".";
+        } else {
+            root = file.getParent();
+            directory = file.getName();
+            // Verify directory is valid.
+            if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
+                if (DEBUG)
+                    Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
+                            + file.getAbsolutePath() + "')");
+                logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
+                return false;
+            }
         }
 
         // Gets volume label and converted path.
@@ -186,12 +192,13 @@
         String volumeUuid = null;
         final List<VolumeInfo> volumes = sm.getVolumes();
         if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
+        File internalRoot = null;
         for (VolumeInfo volume : volumes) {
             if (isRightVolume(volume, root, userId)) {
-                final File internalRoot = volume.getInternalPathForUser(userId);
+                internalRoot = volume.getInternalPathForUser(userId);
                 // Must convert path before calling getDocIdForFileCreateNewDir()
                 if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
-                file = new File(internalRoot, directory);
+                file = isRoot ? internalRoot : new File(internalRoot, directory);
                 volumeLabel = sm.getBestVolumeDescription(volume);
                 volumeUuid = volume.getFsUuid();
                 break;
@@ -199,7 +206,7 @@
         }
 
         // Checks if the user has granted the permission already.
-        final Intent intent = getIntentForExistingPermission(activity, file);
+        final Intent intent = getIntentForExistingPermission(activity, isRoot, internalRoot, file);
         if (intent != null) {
             logValidScopedAccessRequest(activity, directory,
                     SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
@@ -227,6 +234,7 @@
         args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
         args.putString(EXTRA_VOLUME_UUID, volumeUuid);
         args.putString(EXTRA_APP_LABEL, appLabel);
+        args.putBoolean(EXTRA_IS_ROOT, isRoot);
 
         final FragmentManager fm = activity.getFragmentManager();
         final FragmentTransaction ft = fm.beginTransaction();
@@ -310,19 +318,27 @@
     }
 
     private static Intent getIntentForExistingPermission(OpenExternalDirectoryActivity activity,
-            File file) {
+            boolean isRoot, File root, File file) {
         final String packageName = activity.getCallingPackage();
-        final Uri grantedUri =
-                getGrantedUriPermission(activity, activity.getExternalStorageClient(), file);
+        final ContentProviderClient storageClient = activity.getExternalStorageClient();
+        final Uri grantedUri = getGrantedUriPermission(activity, storageClient, file);
+        final Uri rootUri = root.equals(file) ? grantedUri
+                : getGrantedUriPermission(activity, storageClient, root);
+
         if (DEBUG)
-            Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri);
+            Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri
+                    + " or its root (" + rootUri + ")");
         final ActivityManager am =
                 (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
         for (UriPermission uriPermission : am.getGrantedUriPermissions(packageName).getList()) {
             final Uri uri = uriPermission.getUri();
-            if (uri.equals(grantedUri)) {
+            if (uri == null) {
+                Log.w(TAG, "null URI for " + uriPermission);
+                continue;
+            }
+            if (uri.equals(grantedUri) || uri.equals(rootUri)) {
                 if (DEBUG) Log.d(TAG, packageName + " already has permission: " + uriPermission);
-                return createGrantedUriPermissionsIntent(uri);
+                return createGrantedUriPermissionsIntent(grantedUri);
             }
         }
         if (DEBUG) Log.d(TAG, packageName + " does not have permission for " + grantedUri);
@@ -335,6 +351,7 @@
         private String mVolumeUuid;
         private String mVolumeLabel;
         private String mAppLabel;
+        private boolean mIsRoot;
         private CheckBox mDontAskAgain;
         private OpenExternalDirectoryActivity mActivity;
         private AlertDialog mDialog;
@@ -349,6 +366,7 @@
                 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
                 mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
                 mAppLabel = args.getString(EXTRA_APP_LABEL);
+                mIsRoot = args.getBoolean(EXTRA_IS_ROOT);
             }
             mActivity = (OpenExternalDirectoryActivity) getActivity();
         }
@@ -375,6 +393,7 @@
                 mActivity = (OpenExternalDirectoryActivity) getActivity();
             }
             final String directory = mFile.getName();
+            final String directoryName = mIsRoot ? DIRECTORY_ROOT : directory;
             final Context context = mActivity.getApplicationContext();
             final OnClickListener listener = new OnClickListener() {
 
@@ -386,17 +405,17 @@
                                 mActivity.getExternalStorageClient(), mFile);
                     }
                     if (which == DialogInterface.BUTTON_NEGATIVE || intent == null) {
-                        logValidScopedAccessRequest(mActivity, directory,
+                        logValidScopedAccessRequest(mActivity, directoryName,
                                 SCOPED_DIRECTORY_ACCESS_DENIED);
                         final boolean checked = mDontAskAgain.isChecked();
                         if (checked) {
                             logValidScopedAccessRequest(mActivity, directory,
                                     SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST);
                             setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
-                                    mVolumeUuid, directory, PERMISSION_NEVER_ASK);
+                                    mVolumeUuid, directoryName, PERMISSION_NEVER_ASK);
                         } else {
                             setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
-                                    mVolumeUuid, directory, PERMISSION_ASK_AGAIN);
+                                    mVolumeUuid, directoryName, PERMISSION_ASK_AGAIN);
                         }
                         mActivity.setResult(RESULT_CANCELED);
                     } else {
@@ -408,13 +427,17 @@
                 }
             };
 
-            final CharSequence message = TextUtils
-                    .expandTemplate(
-                            getText(R.string.open_external_dialog_request), mAppLabel, directory,
-                            mVolumeLabel);
             @SuppressLint("InflateParams")
             // It's ok pass null ViewRoot on AlertDialogs.
             final View view = View.inflate(mActivity, R.layout.dialog_open_scoped_directory, null);
+            final CharSequence message;
+            if (mIsRoot) {
+                message = TextUtils.expandTemplate(getText(
+                        R.string.open_external_dialog_root_request), mAppLabel, mVolumeLabel);
+            } else {
+                message = TextUtils.expandTemplate(getText(R.string.open_external_dialog_request),
+                        mAppLabel, directory, mVolumeLabel);
+            }
             final TextView messageField = (TextView) view.findViewById(R.id.message);
             messageField.setText(message);
             mDialog = new AlertDialog.Builder(mActivity, R.style.Theme_AppCompat_Light_Dialog_Alert)
@@ -425,7 +448,7 @@
 
             mDontAskAgain = (CheckBox) view.findViewById(R.id.do_not_ask_checkbox);
             if (getScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
-                    mVolumeUuid, directory) == PERMISSION_ASK_AGAIN) {
+                    mVolumeUuid, directoryName) == PERMISSION_ASK_AGAIN) {
                 mDontAskAgain.setVisibility(View.VISIBLE);
                 mDontAskAgain.setOnCheckedChangeListener(new OnCheckedChangeListener() {
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index e55dd27..4acf85e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -527,9 +527,13 @@
 
             // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
             final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
-            final Toolbar rootsToolbar = (Toolbar) getActivity().findViewById(R.id.roots_toolbar);
             toolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-            rootsToolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+
+            // This toolbar is not present in the fixed_layout
+            final Toolbar rootsToolbar = (Toolbar) getActivity().findViewById(R.id.roots_toolbar);
+            if (rootsToolbar != null) {
+                rootsToolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+            }
         }
 
         @Override
@@ -544,13 +548,16 @@
                 // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
                 // these controls when using linear navigation.
                 final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
-                final Toolbar rootsToolbar = (Toolbar) getActivity().findViewById(
-                        R.id.roots_toolbar);
                 toolbar.setImportantForAccessibility(
                         View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-                rootsToolbar.setImportantForAccessibility(
-                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
 
+                // This toolbar is not present in the fixed_layout
+                final Toolbar rootsToolbar = (Toolbar) getActivity().findViewById(
+                        R.id.roots_toolbar);
+                if (rootsToolbar != null) {
+                    rootsToolbar.setImportantForAccessibility(
+                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+                }
                 return true;
             }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
index 3536593..b816287 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
@@ -21,6 +21,7 @@
 import android.content.ContextWrapper;
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.database.MergeCursor;
 import android.provider.DocumentsContract.Document;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContentResolver;
@@ -117,21 +118,25 @@
 
     // Tests multiple authorities with clashing document IDs.
     public void testModelIdIsUnique() {
-        MatrixCursor cIn = new MatrixCursor(COLUMNS);
+        MatrixCursor cIn1 = new MatrixCursor(COLUMNS);
+        MatrixCursor cIn2 = new MatrixCursor(COLUMNS);
 
         // Make two sets of items with the same IDs, under different authorities.
         final String AUTHORITY0 = "auth0";
         final String AUTHORITY1 = "auth1";
+
         for (int i = 0; i < ITEM_COUNT; ++i) {
-            MatrixCursor.RowBuilder row0 = cIn.newRow();
+            MatrixCursor.RowBuilder row0 = cIn1.newRow();
             row0.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY0);
             row0.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
 
-            MatrixCursor.RowBuilder row1 = cIn.newRow();
+            MatrixCursor.RowBuilder row1 = cIn2.newRow();
             row1.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY1);
             row1.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
         }
 
+        Cursor cIn = new MergeCursor(new Cursor[] { cIn1, cIn2 });
+
         // Update the model, then make sure it contains all the expected items.
         DirectoryResult r = new DirectoryResult();
         r.cursor = cIn;
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
index 2b44d51..1d4ed1d 100644
--- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -51,6 +51,7 @@
 static jclass app_fuse_class;
 static jmethodID app_fuse_get_file_size;
 static jmethodID app_fuse_read_object_bytes;
+static jmethodID app_fuse_write_object_bytes;
 static jfieldID app_fuse_buffer;
 
 // NOTE:
@@ -140,6 +141,9 @@
             case FUSE_READ:
                 invoke_handler(fd, req, &AppFuse::handle_fuse_read);
                 return true;
+            case FUSE_WRITE:
+                invoke_handler(fd, req, &AppFuse::handle_fuse_write);
+                return true;
             case FUSE_RELEASE:
                 invoke_handler(fd, req, &AppFuse::handle_fuse_release);
                 return true;
@@ -289,6 +293,29 @@
         return 0;
     }
 
+    int handle_fuse_write(const fuse_in_header& /* header */,
+                          const fuse_write_in* in,
+                          FuseResponse<fuse_write_out>* out) {
+        if (in->size > MAX_WRITE) {
+            return -EINVAL;
+        }
+        const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
+        if (it == handles_.end()) {
+            return -EBADF;
+        }
+        const uint64_t offset = in->offset;
+        const uint32_t size = in->size;
+        const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in);
+        uint32_t written_size;
+        const int result = write_object_bytes(it->second, offset, size, buffer, &written_size);
+        if (result < 0) {
+            return result;
+        }
+        out->prepare_buffer();
+        out->data()->size = written_size;
+        return 0;
+    }
+
     int handle_fuse_release(const fuse_in_header& /* header */,
                             const fuse_release_in* in,
                             FuseResponse<void>* /* out */) {
@@ -355,6 +382,27 @@
         return read_size;
     }
 
+    int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer,
+                           uint32_t* written_size) {
+        ScopedLocalRef<jbyteArray> array(
+                env_,
+                static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
+        {
+            ScopedByteArrayRW bytes(env_, array.get());
+            if (bytes.get() == nullptr) {
+                return -EIO;
+            }
+            memcpy(bytes.get(), buffer, size);
+        }
+        *written_size = env_->CallIntMethod(
+                self_, app_fuse_write_object_bytes, inode, offset, size, array.get());
+        if (env_->ExceptionCheck()) {
+            env_->ExceptionClear();
+            return -EIO;
+        }
+        return 0;
+    }
+
     static void fuse_reply(int fd, int unique, int reply_code, void* reply_data,
                            size_t reply_size) {
         // Don't send any data for error case.
@@ -469,6 +517,28 @@
         return -1;
     }
 
+    app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I");
+    if (app_fuse_write_object_bytes == nullptr) {
+        ALOGE("Can't find getWriteObjectBytes");
+        return -1;
+    }
+
+    app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B");
+    if (app_fuse_buffer == nullptr) {
+        ALOGE("Can't find mBuffer");
+        return -1;
+    }
+
+    const jfieldID read_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_READ", "I");
+    if (static_cast<int>(env->GetStaticIntField(app_fuse_class, read_max_fied)) != MAX_READ) {
+        return -1;
+    }
+
+    const jfieldID write_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_WRITE", "I");
+    if (static_cast<int>(env->GetStaticIntField(app_fuse_class, write_max_fied)) != MAX_WRITE) {
+        return -1;
+    }
+
     const int result = android::AndroidRuntime::registerNativeMethods(
             env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods));
     if (result < 0) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
index 38435f4..1fd471c 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
@@ -38,8 +38,12 @@
      * Max read amount specified at the FUSE kernel implementation.
      * The value is copied from sdcard.c.
      */
+    @UsedByNative("com_android_mtp_AppFuse.cpp")
     static final int MAX_READ = 128 * 1024;
 
+    @UsedByNative("com_android_mtp_AppFuse.cpp")
+    static final int MAX_WRITE = 256 * 1024;
+
     private final String mName;
     private final Callback mCallback;
 
@@ -47,7 +51,7 @@
      * Buffer for read bytes request.
      * Don't use the buffer from the out of AppFuseMessageThread.
      */
-    private byte[] mBuffer = new byte[MAX_READ];
+    private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)];
 
     private Thread mMessageThread;
     private ParcelFileDescriptor mDeviceFd;
@@ -79,11 +83,22 @@
         }
     }
 
-    public ParcelFileDescriptor openFile(int i) throws FileNotFoundException {
+    /**
+     * Opens a file on app fuse and returns ParcelFileDescriptor.
+     *
+     * @param i ID for opened file.
+     * @param mode Mode for opening file.
+     * @see ParcelFileDescriptor#MODE_READ_ONLY
+     * @see ParcelFileDescriptor#MODE_WRITE_ONLY
+     */
+    public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException {
+        Preconditions.checkArgument(
+                mode == ParcelFileDescriptor.MODE_READ_ONLY ||
+                mode == ParcelFileDescriptor.MODE_WRITE_ONLY);
         return ParcelFileDescriptor.open(new File(
                 getMountPoint(),
                 Integer.toString(i)),
-                ParcelFileDescriptor.MODE_READ_ONLY);
+                mode);
     }
 
     File getMountPoint() {
@@ -100,7 +115,7 @@
         long getFileSize(int inode) throws FileNotFoundException;
 
         /**
-         * Returns flie bytes for the give inode.
+         * Returns file bytes for the give inode.
          * @param inode
          * @param offset Offset for file bytes.
          * @param size Size for file bytes.
@@ -109,6 +124,17 @@
          * @throws IOException
          */
         long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException;
+
+        /**
+         * Handles writing bytes for the give inode.
+         * @param inode
+         * @param offset Offset for file bytes.
+         * @param size Size for file bytes.
+         * @param bytes Buffer to store file bytes.
+         * @return Number of read bytes. Must not be negative.
+         * @throws IOException
+         */
+        int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException;
     }
 
     @UsedByNative("com_android_mtp_AppFuse.cpp")
@@ -138,6 +164,15 @@
         }
     }
 
+    @UsedByNative("com_android_mtp_AppFuse.cpp")
+    @WorkerThread
+    private /* unsgined */ int writeObjectBytes(int inode,
+                                                /* unsigned */ long offset,
+                                                /* unsigned */ int size,
+                                                byte[] bytes) throws IOException {
+        return mCallback.writeObjectBytes(inode, offset, size, bytes);
+    }
+
     private native boolean native_start_app_fuse_loop(int fd);
 
     private class AppFuseMessageThread extends Thread {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index cf5bee5..4152d1e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -242,7 +242,8 @@
                     // extension.
                     if (MtpDeviceRecord.isPartialReadSupported(
                             device.operationsSupported, fileSize)) {
-                        return mAppFuse.openFile(Integer.parseInt(documentId));
+                        return mAppFuse.openFile(
+                                Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY);
                     } else {
                         return getPipeManager(identifier).readDocument(mMtpManager, identifier);
                     }
@@ -606,5 +607,12 @@
         public long getFileSize(int inode) throws FileNotFoundException {
             return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
         }
+
+        @Override
+        public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
+                throws IOException {
+            // TODO: Implement it.
+            throw new IOException();
+        }
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java b/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java
index a7f295f..2ded925 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java
@@ -22,7 +22,7 @@
 /**
  * Annotation that shows the method is used by JNI.
  */
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.FIELD})
 public @interface UsedByNative {
     /**
      * JNI file name that uses the method.
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
index c0973bd..3b92506 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
@@ -56,7 +56,8 @@
                     }
                 });
         appFuse.mount(storageManager);
-        final ParcelFileDescriptor fd = appFuse.openFile(INODE);
+        final ParcelFileDescriptor fd = appFuse.openFile(
+                INODE, ParcelFileDescriptor.MODE_READ_ONLY);
         fd.close();
         appFuse.close();
     }
@@ -67,11 +68,21 @@
         final AppFuse appFuse = new AppFuse("test", new TestCallback());
         appFuse.mount(storageManager);
         try {
-            appFuse.openFile(INODE);
+            appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_ONLY);
             fail();
-        } catch (Throwable t) {
-            assertTrue(t instanceof FileNotFoundException);
-        }
+        } catch (FileNotFoundException exp) {}
+        appFuse.close();
+    }
+
+    public void testOpenFile_illegalMode() throws IOException {
+        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+        final int INODE = 10;
+        final AppFuse appFuse = new AppFuse("test", new TestCallback());
+        appFuse.mount(storageManager);
+        try {
+            appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_WRITE);
+            fail();
+        } catch (IllegalArgumentException exp) {}
         appFuse.close();
     }
 
@@ -105,7 +116,8 @@
                     }
                 });
         appFuse.mount(storageManager);
-        final ParcelFileDescriptor fd = appFuse.openFile(fileInode);
+        final ParcelFileDescriptor fd = appFuse.openFile(
+                fileInode, ParcelFileDescriptor.MODE_READ_ONLY);
         try (final ParcelFileDescriptor.AutoCloseInputStream stream =
                 new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
             final byte[] buffer = new byte[1024];
@@ -115,6 +127,71 @@
         appFuse.close();
     }
 
+    public void testWriteFile() throws IOException {
+        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+        final int INODE = 10;
+        final byte[] resultBytes = new byte[5];
+        final AppFuse appFuse = new AppFuse(
+                "test",
+                new TestCallback() {
+                    @Override
+                    public long getFileSize(int inode) throws FileNotFoundException {
+                        if (inode != INODE) {
+                            throw new FileNotFoundException();
+                        }
+                        return resultBytes.length;
+                    }
+
+                    @Override
+                    public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) {
+                        for (int i = 0; i < size; i++) {
+                            resultBytes[(int)(offset + i)] = bytes[i];
+                        }
+                        return size;
+                    }
+                });
+        appFuse.mount(storageManager);
+        final ParcelFileDescriptor fd = appFuse.openFile(
+                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
+        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
+                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+            stream.write('a');
+            stream.write('b');
+            stream.write('c');
+            stream.write('d');
+            stream.write('e');
+        }
+        final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' };
+        assertTrue(Arrays.equals(BYTES, resultBytes));
+        appFuse.close();
+    }
+
+    public void testWriteFile_writeError() throws IOException {
+        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+        final int INODE = 10;
+        final AppFuse appFuse = new AppFuse(
+                "test",
+                new TestCallback() {
+                    @Override
+                    public long getFileSize(int inode) throws FileNotFoundException {
+                        if (inode != INODE) {
+                            throw new FileNotFoundException();
+                        }
+                        return 5;
+                    }
+                });
+        appFuse.mount(storageManager);
+        final ParcelFileDescriptor fd = appFuse.openFile(
+                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
+        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
+                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+            stream.write('a');
+            fail();
+        } catch (IOException e) {
+        }
+        appFuse.close();
+    }
+
     private static class TestCallback implements AppFuse.Callback {
         @Override
         public long getFileSize(int inode) throws FileNotFoundException {
@@ -126,5 +203,11 @@
                 throws IOException {
             throw new IOException();
         }
+
+        @Override
+        public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
+                throws IOException {
+            throw new IOException();
+        }
     }
 }
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 1bdb6d8..2bcd851 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -23,16 +23,12 @@
          on the device. Usually an app can access only the print jobs it created. -->
     <permission
         android:name="com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS"
-        android:label="@string/permlab_accessAllPrintJobs"
-        android:description="@string/permdesc_accessAllPrintJobs"
         android:protectionLevel="signature" />
 
     <!-- May be required by the settings and add printer activities of a
          print service if the developer wants only trusted system code to
          be able to launch these activities. -->
     <permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
-        android:label="@string/permlab_startPrintServiceConfigActivity"
-        android:description="@string/permdesc_startPrintServiceConfigActivity"
         android:protectionLevel="signature" />
 
     <uses-permission android:name="com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS"/>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 454c99d..2f24d2c 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -211,9 +211,6 @@
     <!-- Label for an unknown reason for failed or blocked print job. [CHAR LIMIT=25] -->
     <string name="reason_unknown">unknown</string>
 
-    <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
-    <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> &#8211; unavailable</string>
-
     <!-- Title for a warning message about security implications of using a print service,
          displayed as a dialog message when the user prints using a print service that has not been
          used before. [CHAR LIMIT=NONE] -->
@@ -254,26 +251,6 @@
         <item>Landscape</item>
     </string-array>
 
-    <!-- Permissions -->
-
-    <!-- Title of an application permission, listed so the user can choose whether they want
-         to allow the application to do this. -->
-    <string name="permlab_accessAllPrintJobs" translatable="false">access all print jobs</string>
-    <!-- Description of an application permission, listed so the user can choose whether
-         they want to allow the application to do this. -->
-    <string name="permdesc_accessAllPrintJobs" translatable="false">Allows the holder to access
-        print jobs created by another app. Should never be needed for normal apps.</string>
-
-    <!-- Title of an application permission, listed so the user can choose whether they want
-         to allow the application to do this. -->
-    <string name="permlab_startPrintServiceConfigActivity" translatable="false">start print
-        service configuration activities</string>
-    <!-- Description of an application permission, listed so the user can choose whether they
-         want to allow the application to do this. -->
-    <string name="permdesc_startPrintServiceConfigActivity" translatable="false">Allows the
-        holder to start the configuration activities of a print service. Should never be needed
-        for normal apps.</string>
-
     <!-- Error messages -->
 
     <!-- Message for an error when trying to print to a PDF file. [CHAR LIMIT=50] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 0210693..cd1d540 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -208,7 +208,7 @@
             }
         }
 
-        CharSequence status = printJob.getStatus();
+        CharSequence status = printJob.getStatus(mContext.getPackageManager());
         if (status != null) {
             builder.setContentText(status);
         } else {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 18160ff..9b1ab0e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -19,6 +19,7 @@
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
@@ -575,19 +576,35 @@
         }
     }
 
-   /**
-    * Set the status for a print job.
-    *
-    * @param printJobId ID of the print job to update
-    * @param status the new status
-    */
-   public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
-       synchronized (mLock) {
-           getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
+    /**
+     * Set the status for a print job.
+     *
+     * @param printJobId ID of the print job to update
+     * @param status the new status
+     */
+    public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+        synchronized (mLock) {
+            getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
 
-           mNotificationController.onUpdateNotifications(mPrintJobs);
-       }
-   }
+            mNotificationController.onUpdateNotifications(mPrintJobs);
+        }
+    }
+
+    /**
+     * Set the status for a print job.
+     *
+     * @param printJobId ID of the print job to update
+     * @param status the new status as a string resource
+     * @param appPackageName app package the resource belongs to
+     */
+    public void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
+            @Nullable CharSequence appPackageName) {
+        synchronized (mLock) {
+            getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status, appPackageName);
+
+            mNotificationController.onUpdateNotifications(mPrintJobs);
+        }
+    }
 
     public boolean hasActivePrintJobsLocked() {
         final int printJobCount = mPrintJobs.size();
@@ -884,7 +901,7 @@
                         serializer.attribute(null, ATTR_PROGRESS, String.valueOf(progress));
                     }
 
-                    CharSequence status = printJob.getStatus();
+                    CharSequence status = printJob.getStatus(getPackageManager());
                     if (!TextUtils.isEmpty(status)) {
                         serializer.attribute(null, ATTR_STATUS, status.toString());
                     }
@@ -1041,7 +1058,9 @@
             try {
                 in = mStatePersistFile.openRead();
             } catch (FileNotFoundException e) {
-                Log.i(LOG_TAG, "No existing print spooler state.");
+                if (DEBUG_PERSISTENCE) {
+                    Log.d(LOG_TAG, "No existing print spooler state.");
+                }
                 return;
             }
             try {
@@ -1433,6 +1452,13 @@
             PrintSpoolerService.this.setStatus(printJobId, status);
         }
 
+        @Override
+        public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
+                @NonNull CharSequence appPackageName) throws RemoteException {
+            PrintSpoolerService.this.setStatus(printJobId, status, appPackageName);
+        }
+
+
         public PrintSpoolerService getService() {
             return PrintSpoolerService.this;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
index 3b818c8..0e3e0d5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
@@ -60,7 +60,7 @@
 public class StorageMeasurement {
     private static final String TAG = "StorageMeasurement";
 
-    private static final boolean LOCAL_LOGV = true;
+    private static final boolean LOCAL_LOGV = false;
     static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index c9582ea..c131fa5 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -409,10 +409,11 @@
 
         final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
         if (mProcesses.indexOfKey(id) >= 0) {
+            // BUGREPORT_STARTED intent was already received; ignore it.
             Log.w(TAG, "ID " + id + " already watched");
-        } else {
-            mProcesses.put(info.id, info);
+            return true;
         }
+        mProcesses.put(info.id, info);
         // Take initial screenshot.
         takeScreenshot(id, false);
         updateProgress(info);
diff --git a/packages/SystemUI/res/anim/recents_from_search_launcher_enter.xml b/packages/SystemUI/res/anim/recents_from_search_launcher_enter.xml
deleted file mode 100644
index 7de4460..0000000
--- a/packages/SystemUI/res/anim/recents_from_search_launcher_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<!-- Recents Activity -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="normal">
-  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/linear"
-         android:duration="1"/>
-</set>
diff --git a/packages/SystemUI/res/anim/recents_from_search_launcher_exit.xml b/packages/SystemUI/res/anim/recents_from_search_launcher_exit.xml
deleted file mode 100644
index 23cedf8..0000000
--- a/packages/SystemUI/res/anim/recents_from_search_launcher_exit.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<!-- Launcher Activity -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="top">
-  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@interpolator/recents_from_launcher_exit_interpolator"
-         android:duration="133"/>
-</set>
diff --git a/packages/SystemUI/res/anim/recents_to_search_launcher_enter.xml b/packages/SystemUI/res/anim/recents_to_search_launcher_enter.xml
deleted file mode 100644
index 657b216..0000000
--- a/packages/SystemUI/res/anim/recents_to_search_launcher_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<!-- Launcher Activity -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="normal">
-  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@interpolator/recents_to_launcher_enter_interpolator"
-         android:duration="133"/>
-</set>
diff --git a/packages/SystemUI/res/anim/recents_to_search_launcher_exit.xml b/packages/SystemUI/res/anim/recents_to_search_launcher_exit.xml
deleted file mode 100644
index 5182cab2..0000000
--- a/packages/SystemUI/res/anim/recents_to_search_launcher_exit.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<!-- Recents Activity -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="top">
-  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/linear"
-         android:duration="1"/>
-</set>
diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml b/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml
new file mode 100644
index 0000000..89e4aac
--- /dev/null
+++ b/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <objectAnimator
+        android:propertyName="translationY"
+        android:valueTo="10dp"
+        android:interpolator="@android:interpolator/linear_out_slow_in"
+        android:duration="@integer/recents_tv_pip_focus_anim_duration" />
+    <objectAnimator
+        android:propertyName="alpha"
+        android:valueTo="1.0"
+        android:interpolator="@android:interpolator/linear_out_slow_in"
+        android:duration="@integer/recents_tv_pip_focus_anim_duration" />
+</set>
diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml b/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml
new file mode 100644
index 0000000..c73fed6
--- /dev/null
+++ b/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <objectAnimator
+        android:propertyName="translationY"
+        android:valueTo="0dp"
+        android:interpolator="@android:interpolator/fast_out_linear_in"
+        android:duration="@integer/recents_tv_pip_focus_anim_duration" />
+    <objectAnimator
+        android:propertyName="alpha"
+        android:valueTo="0.0"
+        android:interpolator="@android:interpolator/fast_out_linear_in"
+        android:duration="@integer/recents_tv_pip_focus_anim_duration" />
+</set>
diff --git a/packages/SystemUI/res/layout/recents_history.xml b/packages/SystemUI/res/layout/recents_history.xml
deleted file mode 100644
index dc2da72..0000000
--- a/packages/SystemUI/res/layout/recents_history.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.systemui.recents.history.RecentsHistoryView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-    <android.support.v7.widget.RecyclerView
-        android:id="@+id/list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-</com.android.systemui.recents.history.RecentsHistoryView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_history_clear_all_button.xml b/packages/SystemUI/res/layout/recents_history_clear_all_button.xml
deleted file mode 100644
index 05f0979..0000000
--- a/packages/SystemUI/res/layout/recents_history_clear_all_button.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="start|center_vertical"
-    android:paddingStart="16dp"
-    android:paddingEnd="16dp"
-    android:paddingTop="14dp"
-    android:paddingBottom="14dp"
-    android:drawableStart="@drawable/recents_dismiss_all_history"
-    android:contentDescription="@string/recents_history_clear_all_button_label"
-    android:textSize="14sp"
-    android:textColor="#FFFFFF"
-    android:textAllCaps="true"
-    android:shadowColor="#99000000"
-    android:shadowDx="0"
-    android:shadowDy="2"
-    android:shadowRadius="5"
-    android:fontFamily="sans-serif-medium"
-    android:background="?android:selectableItemBackground"
-    android:visibility="invisible" />
diff --git a/packages/SystemUI/res/layout/recents_history_date.xml b/packages/SystemUI/res/layout/recents_history_date.xml
deleted file mode 100644
index 13c7dbe..0000000
--- a/packages/SystemUI/res/layout/recents_history_date.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="4dp"
-    android:paddingEnd="4dp"
-    android:paddingTop="12dp"
-    android:paddingBottom="12dp"
-    android:gravity="start"
-    android:textSize="14sp"
-    android:textColor="#009688"
-    android:textAllCaps="true"
-    android:fontFamily="sans-serif-medium"
-    android:background="?android:selectableItemBackground"
-    android:alpha="1" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_history_task.xml b/packages/SystemUI/res/layout/recents_history_task.xml
deleted file mode 100644
index e92c24a..0000000
--- a/packages/SystemUI/res/layout/recents_history_task.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="48dp"
-    android:orientation="horizontal"
-    android:clickable="true"
-    android:focusable="true"
-    android:background="?android:selectableItemBackground">
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:layout_gravity="center"
-        android:layout_marginStart="4dp"
-        android:layout_marginEnd="12dp" />
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:layout_gravity="end"
-        android:paddingStart="16dp"
-        android:gravity="start|center_vertical"
-        android:textSize="14sp"
-        android:textColor="#FFFFFF"
-        android:fontFamily="sans-serif-medium" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_on_tv.xml b/packages/SystemUI/res/layout/recents_on_tv.xml
index f0bfebe..0f8c77c 100644
--- a/packages/SystemUI/res/layout/recents_on_tv.xml
+++ b/packages/SystemUI/res/layout/recents_on_tv.xml
@@ -19,8 +19,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:clipChildren="false"
-    android:clipToPadding="false"
-    android:layoutDirection="rtl">
+    android:clipToPadding="false">
     <com.android.systemui.recents.tv.views.TaskStackHorizontalGridView
         android:id="@+id/task_list"
         android:layout_width="wrap_content"
@@ -29,20 +28,21 @@
         android:clipToPadding="false"
         android:descendantFocusability="beforeDescendants"
         android:layout_marginTop="@dimen/recents_tv_gird_row_top_margin"
-        android:focusable="true" />
+        android:focusable="true"
+        android:layoutDirection="rtl" />
+
     <View
         android:id="@+id/pip_shade"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="gone"
-        android:background="#76000000"/>
+        android:background="#76000000" />
 
-    <!-- Placeholder view to handle key events for PIP when it's focused.
-         Size and positions will be adjusted to comply with the PIP bounds -->
-    <View
-        android:id="@+id/pip"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:visibility="gone"
-        android:focusable="true" />
+    <include
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|center_horizontal"
+        android:layout_marginTop="132dp"
+        layout="@layout/tv_pip_controls" />
+
 </com.android.systemui.recents.tv.views.RecentsTvView>
diff --git a/packages/SystemUI/res/layout/recents_history_button.xml b/packages/SystemUI/res/layout/recents_stack_action_button.xml
similarity index 95%
rename from packages/SystemUI/res/layout/recents_history_button.xml
rename to packages/SystemUI/res/layout/recents_stack_action_button.xml
index 538bad1..8783261 100644
--- a/packages/SystemUI/res/layout/recents_history_button.xml
+++ b/packages/SystemUI/res/layout/recents_stack_action_button.xml
@@ -23,7 +23,7 @@
     android:paddingEnd="14dp"
     android:paddingTop="12dp"
     android:paddingBottom="12dp"
-    android:text="@string/recents_history_button_label"
+    android:text="@string/recents_stack_action_button_label"
     android:textSize="14sp"
     android:textColor="#FFFFFF"
     android:textAllCaps="true"
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index fa65758..2b3c5df 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -14,28 +14,28 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!-- The layouts params are calculated in TaskViewHeader.java -->
 <com.android.systemui.recents.views.TaskViewHeader
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/task_view_bar"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/recents_task_bar_height"
+    android:layout_height="wrap_content"
     android:layout_gravity="top|center_horizontal">
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/icon"
         android:contentDescription="@string/recents_app_info_button_label"
-        android:layout_width="@dimen/recents_task_view_header_icon_width"
-        android:layout_height="@dimen/recents_task_view_header_icon_height"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|start"
         android:paddingTop="8dp"
         android:paddingBottom="8dp"
-        android:paddingStart="12dp"
-        android:paddingEnd="16dp" />
+        android:paddingStart="16dp"
+        android:paddingEnd="12dp" />
     <LinearLayout
+        android:id="@+id/title_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|start"
-        android:layout_marginStart="56dp"
-        android:layout_marginEnd="56dp"
         android:orientation="vertical">
         <TextView
             android:id="@+id/title"
@@ -67,21 +67,20 @@
     </LinearLayout>
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/move_task"
-        android:layout_width="@dimen/recents_task_view_header_button_width"
-        android:layout_height="@dimen/recents_task_view_header_button_height"
-        android:layout_marginEnd="@dimen/recents_task_view_header_button_width"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|end"
-        android:padding="13dp"
+        android:padding="@dimen/recents_task_view_header_button_padding"
         android:src="@drawable/star"
         android:background="?android:selectableItemBackground"
         android:alpha="0"
         android:visibility="gone" />
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/dismiss_task"
-        android:layout_width="@dimen/recents_task_view_header_button_width"
-        android:layout_height="@dimen/recents_task_view_header_button_height"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|end"
-        android:padding="13dp"
+        android:padding="@dimen/recents_task_view_header_button_padding"
         android:src="@drawable/recents_dismiss_light"
         android:background="?android:selectableItemBackground"
         android:alpha="0"
diff --git a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
index 1becdab..cf09b1d 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
@@ -13,6 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!-- The layouts params are calculated in TaskViewHeader.java -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
@@ -20,20 +21,18 @@
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/app_icon"
         android:contentDescription="@string/recents_app_info_button_label"
-        android:layout_width="@dimen/recents_task_view_header_icon_width"
-        android:layout_height="@dimen/recents_task_view_header_icon_height"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|start"
         android:paddingTop="8dp"
         android:paddingBottom="8dp"
-        android:paddingStart="12dp"
-        android:paddingEnd="16dp" />
+        android:paddingStart="16dp"
+        android:paddingEnd="12dp" />
     <TextView
         android:id="@+id/app_title"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|start"
-        android:layout_marginStart="56dp"
-        android:layout_marginEnd="112dp"
         android:textSize="16sp"
         android:textColor="#ffffffff"
         android:text="@string/recents_empty_message"
@@ -44,10 +43,10 @@
         android:fadingEdge="horizontal" />
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/app_info"
-        android:layout_width="@dimen/recents_task_view_header_button_width"
-        android:layout_height="@dimen/recents_task_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|end"
-        android:padding="13dp"
+        android:padding="@dimen/recents_task_view_header_button_padding"
         android:background="?android:selectableItemBackground"
         android:src="@drawable/recents_info_light" />
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml
new file mode 100644
index 0000000..2e0c9e7
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_controls.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2016, 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.
+*/
+-->
+
+<com.android.systemui.tv.pip.PipControlsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/pip_controls"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:layout_width="100dp"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="center">
+
+        <ImageView android:id="@+id/full_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:focusable="true"
+            android:src="@drawable/tv_pip_full_button" />
+
+        <TextView android:id="@+id/full_desc"
+            android:layout_width="100dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="3dp"
+            android:gravity="center"
+            android:visibility="invisible"
+            android:text="@string/pip_fullscreen"
+            android:fontFamily="sans-serif"
+            android:textSize="12sp"
+            android:textColor="#EEEEEE" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="100dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-50dp"
+        android:orientation="vertical"
+        android:gravity="center">
+
+        <ImageView android:id="@+id/close_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:focusable="true"
+            android:src="@drawable/tv_pip_close_button" />
+
+        <TextView android:id="@+id/close_desc"
+            android:layout_width="100dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="3dp"
+            android:gravity="center"
+            android:visibility="invisible"
+            android:text="@string/pip_close"
+            android:fontFamily="sans-serif"
+            android:textSize="12sp"
+            android:textColor="#EEEEEE" />
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/play_pause"
+        android:layout_width="100dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-50dp"
+        android:orientation="vertical"
+        android:gravity="center" >
+
+        <ImageView android:id="@+id/play_pause_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:focusable="true"
+            android:src="@drawable/tv_pip_pause_button" />
+
+        <TextView android:id="@+id/play_pause_desc"
+            android:layout_width="100dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="3dp"
+            android:gravity="center"
+            android:visibility="invisible"
+            android:text="@string/pip_pause"
+            android:fontFamily="sans-serif"
+            android:textSize="12sp"
+            android:textColor="#EEEEEE" />
+    </LinearLayout>
+</com.android.systemui.tv.pip.PipControlsView>
diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml
index 1fec49e..c2c83ff 100644
--- a/packages/SystemUI/res/layout/tv_pip_menu.xml
+++ b/packages/SystemUI/res/layout/tv_pip_menu.xml
@@ -26,85 +26,7 @@
     android:gravity="top|center_horizontal"
     android:clipChildren="false">
 
-    <LinearLayout
-        android:layout_width="34dp"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="3dp"
-        android:orientation="vertical"
-        android:gravity="center"
-        android:clipChildren="false">
-
-        <ImageView android:id="@+id/full_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:focusable="true"
-            android:src="@drawable/tv_pip_full_button" />
-
-        <TextView android:id="@+id/full_desc"
-            android:layout_width="100dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="3dp"
-            android:gravity="center"
-            android:visibility="invisible"
-            android:text="@string/pip_fullscreen"
-            android:fontFamily="sans-serif"
-            android:textSize="12sp"
-            android:textColor="#EEEEEE"
-            android:clipChildren="false" />
-    </LinearLayout>
-
-    <LinearLayout android:id="@+id/play_pause"
-        android:layout_width="34dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="3dp"
-        android:layout_marginEnd="3dp"
-        android:orientation="vertical"
-        android:gravity="center"
-        android:clipChildren="false">
-
-        <ImageView android:id="@+id/play_pause_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:focusable="true"
-            android:src="@drawable/tv_pip_pause_button" />
-
-        <TextView android:id="@+id/play_pause_desc"
-            android:layout_width="100dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="3dp"
-            android:gravity="center"
-            android:visibility="invisible"
-            android:text="@string/pip_pause"
-            android:fontFamily="sans-serif"
-            android:textSize="12sp"
-            android:textColor="#EEEEEE"
-            android:clipChildren="false" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="34dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="3dp"
-        android:orientation="vertical"
-        android:gravity="center"
-        android:clipChildren="false">
-
-        <ImageView android:id="@+id/close_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:focusable="true"
-            android:src="@drawable/tv_pip_close_button" />
-
-        <TextView android:id="@+id/close_desc"
-            android:layout_width="100dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="3dp"
-            android:gravity="center"
-            android:visibility="invisible"
-            android:text="@string/pip_close"
-            android:fontFamily="sans-serif"
-            android:textSize="12sp"
-            android:textColor="#EEEEEE"
-            android:clipChildren="false" />
-    </LinearLayout>
+    <include
+        layout="@layout/tv_pip_controls"
+        android:clipChildren="false" />
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml
index 1ba423b..c5c7e84 100644
--- a/packages/SystemUI/res/layout/tv_pip_overlay.xml
+++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml
@@ -46,16 +46,17 @@
         android:layout_centerHorizontal="true"
         android:orientation="horizontal">
         <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="19dp"
+            android:layout_height="19dp"
             android:src="@drawable/ic_fullscreen_white_24dp" />
         <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_pause_white_24dp" />
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="19dp"
+            android:layout_height="19dp"
             android:src="@drawable/ic_close_white" />
+        <ImageView
+            android:id="@+id/guide_button_play_pause"
+            android:layout_width="19dp"
+            android:layout_height="19dp"
+            android:src="@drawable/ic_pause_white_24dp" />
     </LinearLayout>
 </RelativeLayout>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 43e7bac..f7e2344 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -28,14 +28,4 @@
 
     <!-- We have only space for one notification on phone landscape layouts. -->
     <integer name="keyguard_max_notification_count">1</integer>
-
-    <!-- Recents: The relative range of visible tasks from the current scroll position
-         while the stack is focused. -->
-    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
-    <item name="recents_layout_focused_range_max" format="float" type="integer">2</item>
-
-    <!-- Recents: The relative range of visible tasks from the current scroll position
-         while the stack is not focused. -->
-    <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
-    <item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 585984c..26a81c8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -22,9 +22,6 @@
     <!-- Standard notification gravity -->
     <integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
 
-    <!-- The size of the initial peek area at the bottom of the stack (above the nav bar). -->
-    <dimen name="recents_initial_bottom_peek_size">@dimen/recents_task_bar_height</dimen>
-
     <dimen name="docked_divider_handle_width">2dp</dimen>
     <dimen name="docked_divider_handle_height">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index db4da10..1f6bbd3 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -33,16 +33,6 @@
     <!-- Set to true to enable the user switcher on the keyguard. -->
     <bool name="config_keyguardUserSwitcher">true</bool>
 
-    <!-- Recents: The relative range of visible tasks from the current scroll position
-         while the stack is focused. -->
-    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
-    <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
-
-    <!-- Recents: The relative range of visible tasks from the current scroll position
-         while the stack is not focused. -->
-    <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
-    <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
-
     <!-- Nav bar button default ordering/layout -->
     <string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
 
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 66963c4..a2010be 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -96,15 +96,6 @@
 
     <dimen name="qs_expand_margin">0dp</dimen>
 
-    <!-- The top padding for the task stack. -->
-    <dimen name="recents_stack_top_padding">40dp</dimen>
-
-    <!-- The size of the initial peek area at the bottom of the stack (above the nav bar). -->
-    <dimen name="recents_initial_bottom_peek_size">100dp</dimen>
-
-    <!-- The side padding for the task stack. -->
-    <dimen name="recents_stack_left_right_padding">64dp</dimen>
-
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">488dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 8fe6be9..25e96c8 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -29,11 +29,6 @@
     <!-- Bottom margin (from display edge) for status bar panels -->
     <dimen name="panel_float">56dp</dimen>
 
-    <!-- The radius of the rounded corners on a task view. -->
-    <dimen name="recents_task_view_rounded_corners_radius">3dp</dimen>
-    <!-- The radius of the rounded corners on a task view's shadow. -->
-    <dimen name="recents_task_view_shadow_rounded_corners_radius">12dp</dimen>
-
     <!-- The fraction of the screen height where the clock on the Keyguard has its center. The
      max value is used when no notifications are displaying, and the min value is when the
      highest possible number of notifications are showing. -->
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index a3f8b85..4f3db05 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -63,7 +63,7 @@
     <!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
     <color name="recents_task_bar_dark_text_color">#cc000000</color>
     <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
-    <color name="recents_task_bar_light_icon_color">#ffeeeeee</color>
+    <color name="recents_task_bar_light_icon_color">#ccffffff</color>
     <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
     <color name="recents_task_bar_dark_icon_color">#99000000</color>
     <!-- The lock to task button background color. -->
@@ -79,7 +79,7 @@
     <color name="notification_legacy_background_color">#ff1a1a1a</color>
 
     <!-- The color of the material notification background -->
-    <color name="notification_material_background_color">#ffffffff</color>
+    <color name="notification_material_background_color">@*android:color/notification_material_background_color</color>
 
     <!-- The color of the material notification background when dimmed -->
     <color name="notification_material_background_dimmed_color">#ccffffff</color>
@@ -153,7 +153,7 @@
     <color name="docked_divider_handle">#ffffff</color>
 
     <color name="default_remote_input_background">@*android:color/notification_default_color</color>
-    <color name="remote_input_hint">#4dffffff</color>
+    <color name="remote_input_hint">#99ffffff</color>
 
     <color name="remote_input_accent">#eeeeee</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4e1680d..622ae71 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -86,9 +86,6 @@
 
     <bool name="config_dead_zone_flash">false</bool>
 
-    <!-- Min alpha % that recent items will fade to while being dismissed -->
-    <integer name="config_recent_item_min_alpha">3</integer>
-
     <!-- Whether QuickSettings is in a phone landscape -->
     <bool name="quick_settings_wide">false</bool>
 
@@ -165,9 +162,6 @@
     <!-- The animation duration for subsequent scrolling the stack to a particular item. -->
     <integer name="recents_subsequent_auto_advance_duration">1000</integer>
 
-    <!-- The animation duration for entering and exiting the history. -->
-    <integer name="recents_history_transition_duration">250</integer>
-
     <!-- The delay to enforce between each alt-tab key press. -->
     <integer name="recents_alt_tab_key_delay">200</integer>
 
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
new file mode 100644
index 0000000..22b7578
--- /dev/null
+++ b/packages/SystemUI/res/values/config_tv.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<resources>
+    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
+         when the PIP menu is shown in center. -->
+    <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
+
+    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
+         when the PIP is shown in Recents without focus. -->
+    <string translatable="false" name="pip_recents_bounds">"800 54 1120 234"</string>
+
+    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
+         when the PIP is shown in Recents with focus. -->
+    <string translatable="false" name="pip_recents_focused_bounds">"775 54 1145 262"</string>
+</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8cd2167..88230bc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -234,85 +234,6 @@
     <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
     <dimen name="glowpadview_inner_radius">15dip</dimen>
 
-    <!-- The size of the icon in the recents task view header. -->
-    <dimen name="recents_task_view_header_icon_width">56dp</dimen>
-    <dimen name="recents_task_view_header_icon_height">@dimen/recents_task_bar_height</dimen>
-
-    <!-- The size of a button in the recents task view header. -->
-    <dimen name="recents_task_view_header_button_width">@dimen/recents_task_bar_height</dimen>
-    <dimen name="recents_task_view_header_button_height">@dimen/recents_task_bar_height</dimen>
-
-    <!-- The radius of the rounded corners on a task view. -->
-    <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
-    <!-- The radius of the rounded corners on a task view's shadow. -->
-    <dimen name="recents_task_view_shadow_rounded_corners_radius">12dp</dimen>
-
-    <!-- The min translation in the Z index for the last task. -->
-    <dimen name="recents_task_view_z_min">3dp</dimen>
-
-    <!-- The max translation in the Z index for the last task. -->
-    <dimen name="recents_task_view_z_max">24dp</dimen>
-
-    <!-- The amount to translate when animating the removal of a task. -->
-    <dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
-
-    <!-- The amount of highlight to make on each task view. -->
-    <dimen name="recents_task_view_highlight">1dp</dimen>
-
-    <!-- The amount to offset when animating into an affiliate group. -->
-    <dimen name="recents_task_view_affiliate_group_enter_offset">32dp</dimen>
-
-    <!-- The height of a task view bar. -->
-    <dimen name="recents_task_bar_height">50dp</dimen>
-
-    <!-- The height of the search bar space. -->
-    <dimen name="recents_search_bar_space_height">64dp</dimen>
-
-    <!-- The overscroll percentage allowed on the stack. -->
-    <item name="recents_stack_overscroll_percentage" format="float" type="dimen">0.0875</item>
-
-    <!-- The top padding for the task stack. -->
-    <dimen name="recents_stack_top_padding">16dp</dimen>
-
-    <!-- The side padding for the task stack. -->
-    <dimen name="recents_stack_left_right_padding">16dp</dimen>
-
-    <!-- The dimesnsions of the dismiss all recents button. -->
-    <dimen name="recents_dismiss_all_button_size">48dp</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>
-
-    <!-- The size of the lock-to-app button. -->
-    <dimen name="recents_lock_to_app_size">56dp</dimen>
-
-    <!-- The size of the lock-to-app button icon. -->
-    <dimen name="recents_lock_to_app_icon_size">28dp</dimen>
-
-    <!-- The amount to allow the stack to overscroll. -->
-    <dimen name="recents_stack_overscroll">24dp</dimen>
-
-    <!-- The size of the initial peek area at the top of the stack (below the status bar). -->
-    <dimen name="recents_initial_top_peek_size">8dp</dimen>
-
-    <!-- The size of the initial peek area at the bottom of the stack (above the nav bar). -->
-    <dimen name="recents_initial_bottom_peek_size">100dp</dimen>
-
-    <!-- The size of the peek area at the top of the stack (below the status bar). -->
-    <dimen name="recents_layout_focused_top_peek_size">@dimen/recents_history_button_height</dimen>
-
-    <!-- The size of each task peek area at the bottom of the stack (above the nav bar). -->
-    <dimen name="recents_layout_focused_bottom_task_peek_size">16dp</dimen>
-
-    <!-- The height of the history button. -->
-    <dimen name="recents_history_button_height">48dp</dimen>
-
-    <!-- The padding between freeform workspace tasks -->
-    <dimen name="recents_freeform_workspace_task_padding">8dp</dimen>
-
-    <!-- The offsets the tasks animate from when recents is launched while docking -->
-    <dimen name="recents_task_view_launched_while_docking_offset">144dp</dimen>
-
     <!-- Space reserved for the cards behind the top card in the bottom stack -->
     <dimen name="bottom_stack_peek_amount">12dp</dimen>
 
@@ -639,4 +560,83 @@
 
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">@dimen/match_parent</dimen>
+
+<!-- Recents Layout -->
+
+    <!-- The amount to inset the stack, specifically at the top and the other sides.  We also
+         don't want this to change across configurations that Recents can be opened in, so we
+         define them statically for all display sizes. -->
+    <dimen name="recents_layout_min_margin">16dp</dimen>
+    <dimen name="recents_layout_top_margin_phone">16dp</dimen>
+    <dimen name="recents_layout_top_margin_tablet">32dp</dimen>
+    <dimen name="recents_layout_top_margin_tablet_xlarge">40dp</dimen>
+    <dimen name="recents_layout_bottom_margin">16dp</dimen>
+    <dimen name="recents_layout_side_margin_phone">16dp</dimen>
+    <dimen name="recents_layout_side_margin_tablet">48dp</dimen>
+    <dimen name="recents_layout_side_margin_tablet_xlarge">64dp</dimen>
+
+    <!-- The height between the top margin and the top of the focused task. -->
+    <dimen name="recents_layout_top_peek_size">56dp</dimen>
+    <!-- The height between the bottom margin and the top of task in front of the focused task. -->
+    <dimen name="recents_layout_bottom_peek_size">56dp</dimen>
+
+    <!-- The offset from the top and bottom of the stack of the focused task.  The bottom offset
+         will be additionally offset by the bottom system insets since it goes under the nav bar
+         in certain orientations. -->
+    <dimen name="recents_layout_initial_top_offset_phone_port">128dp</dimen>
+    <dimen name="recents_layout_initial_bottom_offset_phone_port">80dp</dimen>
+    <dimen name="recents_layout_initial_top_offset_phone_land">72dp</dimen>
+    <dimen name="recents_layout_initial_bottom_offset_phone_land">72dp</dimen>
+    <dimen name="recents_layout_initial_top_offset_tablet">160dp</dimen>
+    <dimen name="recents_layout_initial_bottom_offset_tablet">112dp</dimen>
+
+    <!-- The min/max translationZ for the tasks in the stack. -->
+    <dimen name="recents_layout_z_min">3dp</dimen>
+    <dimen name="recents_layout_z_max">24dp</dimen>
+
+    <!-- The margin between the freeform and stack.  We also don't want this to change across 
+         configurations that Recents can be opened in, so we define them statically for all 
+         display sizes. -->
+    <dimen name="recents_freeform_layout_bottom_margin">16dp</dimen>
+
+    <!-- The padding between each freeform task. -->
+    <dimen name="recents_freeform_layout_task_padding">8dp</dimen>
+
+<!-- Recents Views -->
+
+    <!-- The height of a task view bar.  This has to be large enough to cover the action bar
+         height in either orientation at this smallest width. -->
+    <dimen name="recents_task_view_header_height">56dp</dimen>
+    <dimen name="recents_task_view_header_height_tablet_land">64dp</dimen>
+
+    <!-- The padding of a button in the recents task view header. -->
+    <dimen name="recents_task_view_header_button_padding">16dp</dimen>
+    <dimen name="recents_task_view_header_button_padding_tablet_land">20dp</dimen>
+
+    <!-- The radius of the rounded corners on a task view and its shadow (which can be larger
+         to create a softer corner effect. -->
+    <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
+    <dimen name="recents_task_view_shadow_rounded_corners_radius">6dp</dimen>
+
+    <!-- The amount of highlight to make on each task view. -->
+    <dimen name="recents_task_view_highlight">1dp</dimen>
+
+    <!-- The size of the lock-to-app button and its icon. -->
+    <dimen name="recents_lock_to_app_size">56dp</dimen>
+    <dimen name="recents_lock_to_app_icon_size">28dp</dimen>
+
+    <!-- The amount of overscroll allowed when flinging to the end of the stack. -->
+    <dimen name="recents_fling_overscroll_distance">24dp</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>
+
+    <!-- The amount to offset when animating into an affiliate group. -->
+    <dimen name="recents_task_stack_animation_affiliate_enter_offset">32dp</dimen>
+
+    <!-- The offsets the tasks animate from when recents is launched while docking -->
+    <dimen name="recents_task_stack_animation_launched_while_docking_offset">144dp</dimen>
+
+    <!-- The amount to translate when animating the removal of a task. -->
+    <dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/integers_tv.xml b/packages/SystemUI/res/values/integers_tv.xml
index c60c245..20cd330 100644
--- a/packages/SystemUI/res/values/integers_tv.xml
+++ b/packages/SystemUI/res/values/integers_tv.xml
@@ -17,4 +17,5 @@
     <integer name="item_scale_anim_duration">150</integer>
     <integer name="dismiss_short_duration">200</integer>
     <integer name="dismiss_long_duration">400</integer>
-</resources>
\ No newline at end of file
+    <integer name="recents_tv_pip_focus_anim_duration">200</integer>
+</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 091ba51..6f0f30d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -731,10 +731,8 @@
     <string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
     <!-- Recents: Launch disabled string. [CHAR LIMIT=NONE] -->
     <string name="recents_launch_disabled_message"><xliff:g id="app" example="Calendar">%s</xliff:g> is disabled in safe-mode.</string>
-    <!-- Recents: Show history string. [CHAR LIMIT=NONE] -->
-    <string name="recents_history_button_label">History</string>
-    <!-- Recents: History clear all string. [CHAR LIMIT=NONE] -->
-    <string name="recents_history_clear_all_button_label">Clear</string>
+    <!-- Recents: Stack action button string. [CHAR LIMIT=NONE] -->
+    <string name="recents_stack_action_button_label">Clear all</string>
     <!-- Recents: Non-dockable task drag message. [CHAR LIMIT=NONE] -->
     <string name="recents_drag_non_dockable_task_message">This app does not support multi-window</string>
     <!-- Recents: Non-dockable task launch sub header. [CHAR LIMIT=NONE] -->
@@ -1329,6 +1327,59 @@
     <!-- Summary of switch for battery saver [CHAR LIMIT=NONE] -->
     <string name="battery_detail_switch_summary">Reduces performance and background data</string>
 
+    <!-- Name used for certain Keyboard keys on gamepads, e.g. "Button L1". -->
+    <string name="keyboard_key_button_template">Button <xliff:g id="name">%1$s</xliff:g></string>
+    <!-- Name used to refer to the "Home" key on the keyboard. -->
+    <string name="keyboard_key_home">Home</string>
+    <!-- Name used to refer to the "Back" key on the keyboard. -->
+    <string name="keyboard_key_back">Back</string>
+    <!-- Name used to refer to the "Up" arrow key on the keyboard. -->
+    <string name="keyboard_key_dpad_up">Up</string>
+    <!-- Name used to refer to the "Down" arrow key on the keyboard. -->
+    <string name="keyboard_key_dpad_down">Down</string>
+    <!-- Name used to refer to the "Left" arrow key on the keyboard. -->
+    <string name="keyboard_key_dpad_left">Left</string>
+    <!-- Name used to refer to the "Right" arrow key on the keyboard. -->
+    <string name="keyboard_key_dpad_right">Right</string>
+    <!-- Name used to refer to the "Center" arrow key on the keyboard. -->
+    <string name="keyboard_key_dpad_center">Center</string>
+    <!-- Name used to refer to the "Tab" key on the keyboard. -->
+    <string name="keyboard_key_tab">Tab</string>
+    <!-- Name used to refer to the "Space" key on the keyboard. -->
+    <string name="keyboard_key_space">Space</string>
+    <!-- Name used to refer to the "Enter" key on the keyboard. -->
+    <string name="keyboard_key_enter">Enter</string>
+    <!-- Name used to refer to the "Backspace" key on the keyboard. -->
+    <string name="keyboard_key_backspace">Backspace</string>
+    <!-- Name used to refer to the "Play/Pause" media key on the keyboard. -->
+    <string name="keyboard_key_media_play_pause">Play/Pause</string>
+    <!-- Name used to refer to the "Stop" media key on the keyboard. -->
+    <string name="keyboard_key_media_stop">Stop</string>
+    <!-- Name used to refer to the "Next" media key on the keyboard. -->
+    <string name="keyboard_key_media_next">Next</string>
+    <!-- Name used to refer to the "Previous" media key on the keyboard. -->
+    <string name="keyboard_key_media_previous">Previous</string>
+    <!-- Name used to refer to the "Rewind" media key on the keyboard. -->
+    <string name="keyboard_key_media_rewind">Rewind</string>
+    <!-- Name used to refer to the "Fast Forward" media key on the keyboard. -->
+    <string name="keyboard_key_media_fast_forward">Fast Forward</string>
+    <!-- Name used to refer to the "Page Up" key on the keyboard. -->
+    <string name="keyboard_key_page_up">Page Up</string>
+    <!-- Name used to refer to the "Page Down" key on the keyboard. -->
+    <string name="keyboard_key_page_down">Page Down</string>
+    <!-- Name used to refer to the "Delete" key on the keyboard. -->
+    <string name="keyboard_key_forward_del">Delete</string>
+    <!-- Name used to refer to the "Home" move key on the keyboard. -->
+    <string name="keyboard_key_move_home">Home</string>
+    <!-- Name used to refer to the "End" move key on the keyboard. -->
+    <string name="keyboard_key_move_end">End</string>
+    <!-- Name used to refer to the "Insert" key on the keyboard. -->
+    <string name="keyboard_key_insert">Insert</string>
+    <!-- Name used to refer to the "Num Lock" key on the keyboard. -->
+    <string name="keyboard_key_num_lock">Num Lock</string>
+    <!-- Name used to refer to keys on the numeric pad of the keyboard, e.g. "Numpad 9". -->
+    <string name="keyboard_key_numpad_template">Numpad <xliff:g id="name">%1$s</xliff:g></string>
+
     <!-- User visible title for the system-wide keyboard shortcuts list. -->
     <string name="keyboard_shortcut_group_system">System</string>
     <!-- User visible title for the keyboard shortcut that takes the user to the home screen. -->
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 9f2745b..28ed84f 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -30,8 +30,6 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @StringDef({
-        Key.OVERVIEW_SEARCH_APP_WIDGET_ID,
-        Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
         Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
         Key.DEBUG_MODE_ENABLED,
         Key.HOTSPOT_TILE_LAST_USED,
@@ -51,8 +49,6 @@
         Key.QS_NIGHT_ADDED,
     })
     public @interface Key {
-        String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
-        String OVERVIEW_SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
         String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
         String DEBUG_MODE_ENABLED = "debugModeEnabled";
         String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index a08b2c1..8f79bda 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -274,10 +274,7 @@
                             mWatchLongPress = new Runnable() {
                                 @Override
                                 public void run() {
-                                    float pos = getPos(ev);
-                                    float delta = pos - mInitialTouchPos;
-                                    if (mCurrView != null && !mLongPressSent
-                                            && Math.abs(delta) < mPagingTouchSlop) {
+                                    if (mCurrView != null && !mLongPressSent) {
                                         mLongPressSent = true;
                                         mCurrView.sendAccessibilityEvent(
                                                 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 2e94bc7..0d75fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -25,6 +25,8 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
@@ -75,6 +77,11 @@
         return new ScrimController(scrimBehind, scrimInFront, headsUpScrim);
     }
 
+    public NotificationIconAreaController createNotificationIconAreaController(Context context,
+            PhoneStatusBar phoneStatusBar) {
+        return new NotificationIconAreaController(context, phoneStatusBar);
+    }
+
     public <T> T createInstance(Class<T> classType) {
         return null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 6d8b476..51efbf0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -36,12 +36,12 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.SecurityController;
 
+import static android.provider.Settings.ACTION_VPN_SETTINGS;
+
 public class QSFooter implements OnClickListener, DialogInterface.OnClickListener {
     protected static final String TAG = "QSFooter";
     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
-
     private final View mRootView;
     private final TextView mFooterText;
     private final ImageView mFooterIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 070b395..003379f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -27,6 +27,7 @@
         public static final int DismissSourceKeyboard = 0;
         public static final int DismissSourceSwipeGesture = 1;
         public static final int DismissSourceHeaderButton = 2;
+        @Deprecated
         public static final int DismissSourceHistorySwipeGesture = 3;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index da07aec..287bb22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -180,7 +180,7 @@
     @Override
     public void start() {
         sDebugFlags = new RecentsDebugFlags(mContext);
-        sSystemServicesProxy = new SystemServicesProxy(mContext);
+        sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
         sTaskLoader = new RecentsTaskLoader(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
         mHandler = new Handler();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index aab45b5d..1261a9a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -18,10 +18,7 @@
 
 import android.app.Activity;
 import android.app.ActivityOptions;
-import android.app.SearchManager;
 import android.app.TaskStackBuilder;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -42,7 +39,6 @@
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
@@ -50,12 +46,10 @@
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.HideHistoryEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.activity.ShowHistoryEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
@@ -89,9 +83,6 @@
     private final static String TAG = "RecentsActivity";
     private final static boolean DEBUG = false;
 
-    private final static String KEY_SAVED_STATE_HISTORY_VISIBLE =
-            "saved_instance_state_history_visible";
-
     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
 
     private RecentsPackageMonitor mPackageMonitor;
@@ -104,11 +95,6 @@
     private RecentsView mRecentsView;
     private SystemBarScrimViews mScrimViews;
 
-    // Search AppWidget
-    private AppWidgetProviderInfo mSearchWidgetInfo;
-    private RecentsAppWidgetHost mAppWidgetHost;
-    private RecentsAppWidgetHostView mSearchWidgetHostView;
-
     // Runnables to finish the Recents activity
     private Intent mHomeIntent;
 
@@ -138,17 +124,10 @@
         @Override
         public void run() {
             try {
-                RecentsActivityLaunchState launchState =
-                        Recents.getConfiguration().getLaunchState();
                 ActivityOptions opts = mOpts;
                 if (opts == null) {
                     opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
-                            launchState.launchedFromSearchHome ?
-                                    R.anim.recents_to_search_launcher_enter :
-                                    R.anim.recents_to_launcher_enter,
-                            launchState.launchedFromSearchHome ?
-                                    R.anim.recents_to_search_launcher_exit :
-                                    R.anim.recents_to_launcher_exit);
+                            R.anim.recents_to_launcher_enter, R.anim.recents_to_launcher_exit);
                 }
                 startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
             } catch (Exception e) {
@@ -167,27 +146,11 @@
             if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 // When the screen turns off, dismiss Recents to Home
                 dismissRecentsToHomeIfVisible(false);
-            } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
-                // When the search activity changes, update the search widget view
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(context, mAppWidgetHost);
-                refreshSearchWidgetView();
             }
         }
     };
 
     /**
-     * Dismisses the history view back into the stack view.
-     */
-    boolean dismissHistory() {
-        if (RecentsDebugFlags.Static.EnableHistory && mRecentsView.isHistoryVisible()) {
-            EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
-            return true;
-        }
-        return false;
-    }
-
-    /**
      * Dismisses recents if we are already visible and the intent is to toggle the recents view.
      */
     boolean dismissRecentsToFocusedTask(int logCategory) {
@@ -285,10 +248,7 @@
         // Register this activity with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
-        // Initialize the widget host (the host id is static and does not change)
-        if (RecentsDebugFlags.Static.EnableSearchBar) {
-            mAppWidgetHost = new RecentsAppWidgetHost(this, RecentsAppWidgetHost.HOST_ID);
-        }
+        // Initialize the package monitor
         mPackageMonitor = new RecentsPackageMonitor();
         mPackageMonitor.register(this);
 
@@ -317,17 +277,9 @@
         mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
 
-        // Bind the search app widget when we first start up
-        if (RecentsDebugFlags.Static.EnableSearchBar) {
-            mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
-        }
-
         // Register the broadcast receiver to handle messages when the screen is turned off
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        if (RecentsDebugFlags.Static.EnableSearchBar) {
-            filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
-        }
         registerReceiver(mSystemBroadcastReceiver, filter);
     }
 
@@ -372,7 +324,7 @@
         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
         loader.loadTasks(this, loadPlan, loadOpts);
         TaskStack stack = loadPlan.getTaskStack();
-        mRecentsView.onResume(mIsVisible, stack);
+        mRecentsView.onResume(mIsVisible, false /* multiWindowChange */, stack);
 
         // Animate the SystemUI scrims into view
         Task launchTarget = stack.getLaunchTarget();
@@ -446,11 +398,6 @@
     protected void onStop() {
         super.onStop();
 
-        // Only hide the history if Recents is completely hidden
-        if (RecentsDebugFlags.Static.EnableHistory && mRecentsView.isHistoryVisible()) {
-            EventBus.getDefault().send(new HideHistoryEvent(false /* animate */));
-        }
-
         // Notify that recents is now hidden
         mIsVisible = false;
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
@@ -479,11 +426,6 @@
         // Unregister any broadcast receivers for the task loader
         mPackageMonitor.unregister();
 
-        // Stop listening for widget package changes if there was one bound
-        if (RecentsDebugFlags.Static.EnableSearchBar) {
-            mAppWidgetHost.stopListening();
-        }
-
         EventBus.getDefault().unregister(this);
     }
 
@@ -500,23 +442,6 @@
     }
 
     @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            outState.putBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, mRecentsView.isHistoryVisible());
-        }
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Bundle savedInstanceState) {
-        super.onRestoreInstanceState(savedInstanceState);
-        if (RecentsDebugFlags.Static.EnableHistory &&
-                savedInstanceState.getBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, false)) {
-            EventBus.getDefault().send(new ShowHistoryEvent());
-        }
-    }
-
-    @Override
     public void onTrimMemory(int level) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
         if (loader != null) {
@@ -541,7 +466,7 @@
         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
         loader.loadTasks(this, loadPlan, loadOpts);
 
-        mRecentsView.onResume(mIsVisible, loadPlan.getTaskStack());
+        mRecentsView.onResume(mIsVisible, true /* multiWindowChange */, loadPlan.getTaskStack());
 
         EventBus.getDefault().send(new MultiWindowStateChangedEvent(inMultiWindow));
     }
@@ -611,39 +536,35 @@
     /**** EventBus events ****/
 
     public final void onBusEvent(ToggleRecentsEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory || !dismissHistory()) {
-            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-            if (launchState.launchedFromHome) {
-                dismissRecentsToHome(true /* animateTaskViews */);
-            } else {
-                dismissRecentsToLaunchTargetTaskOrHome();
-            }
+        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+        if (launchState.launchedFromHome) {
+            dismissRecentsToHome(true /* animateTaskViews */);
+        } else {
+            dismissRecentsToLaunchTargetTaskOrHome();
         }
     }
 
     public final void onBusEvent(IterateRecentsEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory || !dismissHistory()) {
-            final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
 
-            // Start dozing after the recents button is clicked
-            int timerIndicatorDuration = 0;
-            if (debugFlags.isFastToggleRecentsEnabled()) {
-                timerIndicatorDuration = getResources().getInteger(
-                        R.integer.recents_subsequent_auto_advance_duration);
+        // Start dozing after the recents button is clicked
+        int timerIndicatorDuration = 0;
+        if (debugFlags.isFastToggleRecentsEnabled()) {
+            timerIndicatorDuration = getResources().getInteger(
+                    R.integer.recents_subsequent_auto_advance_duration);
 
-                mIterateTrigger.setDozeDuration(timerIndicatorDuration);
-                if (!mIterateTrigger.isDozing()) {
-                    mIterateTrigger.startDozing();
-                } else {
-                    mIterateTrigger.poke();
-                }
+            mIterateTrigger.setDozeDuration(timerIndicatorDuration);
+            if (!mIterateTrigger.isDozing()) {
+                mIterateTrigger.startDozing();
+            } else {
+                mIterateTrigger.poke();
             }
-
-            // Focus the next task
-            EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
-
-            MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
         }
+
+        // Focus the next task
+        EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
+
+        MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
     }
 
     public final void onBusEvent(UserInteractionEvent event) {
@@ -658,17 +579,7 @@
                 dismissRecentsToFocusedTaskOrHome();
             }
         } else if (event.triggeredFromHomeKey) {
-            // Otherwise, dismiss Recents to Home
-            if (RecentsDebugFlags.Static.EnableHistory && mRecentsView.isHistoryVisible()) {
-                // If the history view is visible, then just cross-fade home
-                ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
-                                R.anim.recents_to_launcher_enter,
-                                R.anim.recents_to_launcher_exit);
-                dismissRecentsToHome(false /* animate */, opts);
-
-            } else {
-                dismissRecentsToHome(true /* animateTaskViews */);
-            }
+            dismissRecentsToHome(true /* animateTaskViews */);
 
             // Cancel any pending dozes
             EventBus.getDefault().send(mUserInteractionEvent);
@@ -677,23 +588,6 @@
         }
     }
 
-    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
-        // Try and start the enter animation (or restart it on configuration changed)
-        if (RecentsDebugFlags.Static.EnableSearchBar) {
-            if (mSearchWidgetInfo != null) {
-                event.addPostAnimationCallback(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Start listening for widget package changes if there is one bound
-                        if (mAppWidgetHost != null) {
-                            mAppWidgetHost.startListening();
-                        }
-                    }
-                });
-            }
-        }
-    }
-
     public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
         EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
@@ -719,10 +613,6 @@
         }
     }
 
-    public final void onBusEvent(AppWidgetProviderChangedEvent event) {
-        refreshSearchWidgetView();
-    }
-
     public final void onBusEvent(ShowApplicationInfoEvent event) {
         // Create a new task stack with the application info details activity
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
@@ -785,24 +675,6 @@
         mIgnoreAltTabRelease = true;
     }
 
-    private void refreshSearchWidgetView() {
-        if (mSearchWidgetInfo != null) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            int searchWidgetId = ssp.getSearchAppWidgetId(this);
-            mSearchWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView(
-                    this, searchWidgetId, mSearchWidgetInfo);
-            Bundle opts = new Bundle();
-            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
-                    AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
-            mSearchWidgetHostView.updateAppWidgetOptions(opts);
-            // Set the padding to 0 for this search widget
-            mSearchWidgetHostView.setPadding(0, 0, 0, 0);
-            mRecentsView.setSearchBar(mSearchWidgetHostView);
-        } else {
-            mRecentsView.setSearchBar(null);
-        }
-    }
-
     @Override
     public boolean onPreDraw() {
         mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index ec4820a..ab3b79e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -31,7 +31,6 @@
     public boolean launchedFromApp;
     public boolean launchedFromAppDocked;
     public boolean launchedFromHome;
-    public boolean launchedFromSearchHome;
     public boolean launchedReuseTaskStackViews;
     public boolean launchedHasConfigurationChanged;
     public boolean launchedViaDragGesture;
@@ -42,7 +41,6 @@
 
     public void reset() {
         launchedFromHome = false;
-        launchedFromSearchHome = false;
         launchedFromApp = false;
         launchedFromAppDocked = false;
         launchedToTaskId = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
deleted file mode 100644
index 318c69f..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
-
-/** Our special app widget host for the Search widget */
-public class RecentsAppWidgetHost extends AppWidgetHost {
-
-    public static final int HOST_ID = 1024;
-
-    boolean mIsListening;
-
-    public RecentsAppWidgetHost(Context context, int hostId) {
-        super(context, hostId);
-    }
-
-    public void startListening() {
-        if (!mIsListening) {
-            mIsListening = true;
-            super.startListening();
-        }
-    }
-
-    @Override
-    public void stopListening() {
-        if (mIsListening) {
-            mIsListening = false;
-            super.stopListening();
-        }
-    }
-
-    @Override
-    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
-                                             AppWidgetProviderInfo appWidget) {
-        return new RecentsAppWidgetHostView(context);
-    }
-
-    /**
-     * Note: this is only called for packages that have updated, not removed.
-     */
-    @Override
-    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
-        super.onProviderChanged(appWidgetId, appWidgetInfo);
-        if (mIsListening) {
-            EventBus.getDefault().send(new AppWidgetProviderChangedEvent());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java
deleted file mode 100644
index 672d602..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.appwidget.AppWidgetHostView;
-import android.content.Context;
-import android.view.View;
-import android.widget.RemoteViews;
-
-public class RecentsAppWidgetHostView extends AppWidgetHostView {
-
-    private Context mContext;
-    private int mPreviousOrientation;
-
-    public RecentsAppWidgetHostView(Context context) {
-        super(context);
-        mContext = context;
-    }
-
-    @Override
-    public void updateAppWidget(RemoteViews remoteViews) {
-        // Store the orientation in which the widget was inflated
-        updateLastInflationOrientation();
-        super.updateAppWidget(remoteViews);
-    }
-
-    @Override
-    protected View getErrorView() {
-        // Just return an empty view as the error view when failing to inflate the Recents search
-        // bar widget (this is mainly to catch the case where we try and inflate the widget view
-        // while the search provider is updating)
-        return new View(mContext);
-    }
-
-    /**
-     * Updates the last orientation that this widget was inflated.
-     */
-    private void updateLastInflationOrientation() {
-        mPreviousOrientation = mContext.getResources().getConfiguration().orientation;
-    }
-
-    /**
-     * @return whether the search widget was updated while Recents was in a different orientation
-     *         in the background.
-     */
-    public boolean isReinflateRequired() {
-        // Re-inflate is required if the orientation has changed since last inflated.
-        int orientation = mContext.getResources().getConfiguration().orientation;
-        if (mPreviousOrientation != orientation) {
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index eec0411..2afb09a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -32,13 +32,6 @@
     private static final int LARGE_SCREEN_MIN_DP = 600;
     private static final int XLARGE_SCREEN_MIN_DP = 720;
 
-    // Variables that are used for global calculations
-    private static final float STACK_SIDE_PADDING_PHONES_PCT = 0.03333f;
-    private static final float STACK_SIZE_PADDING_TABLETS_PCT = 0.075f;
-    private static final float STACK_SIZE_PADDING_LARGE_TABLETS_PCT = 0.15f;
-    private static final int SEARCH_BAR_SPACE_HEIGHT_PHONES_DPS = 64;
-    private static final int SEARCH_BAR_SPACE_HEIGHT_TABLETS_DPS = 72;
-
     /** Levels of svelte in increasing severity/austerity. */
     // No svelting.
     public static final int SVELTE_NONE = 0;
@@ -57,9 +50,7 @@
     // TODO: Values determined by the current context, needs to be refactored into something that is
     //       agnostic of the activity context, but still calculable from the Recents component for
     //       the transition into recents
-    boolean hasTransposedSearchBar;
-    boolean hasTransposedNavBar;
-    public float taskStackWidthPaddingPct;
+    public boolean hasTransposedNavBar;
 
     // Since the positions in Recents has to be calculated globally (before the RecentsActivity
     // starts), we need to calculate some resource values ourselves, instead of relying on framework
@@ -71,7 +62,6 @@
     /** Misc **/
     public boolean fakeShadows;
     public int svelteLevel;
-    public int searchBarSpaceHeightPx;
 
     public RecentsConfiguration(Context context) {
         // Load only resources that can not change after the first load either through developer
@@ -82,20 +72,10 @@
         fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
         svelteLevel = res.getInteger(R.integer.recents_svelte_level);
 
-        float density = context.getResources().getDisplayMetrics().density;
+        float screenDensity = context.getResources().getDisplayMetrics().density;
         smallestWidth = ssp.getDeviceSmallestWidth();
-        isLargeScreen = smallestWidth >= (int) (density * LARGE_SCREEN_MIN_DP);
-        isXLargeScreen = smallestWidth >= (int) (density * XLARGE_SCREEN_MIN_DP);
-        searchBarSpaceHeightPx = isLargeScreen ?
-                (int) (density * SEARCH_BAR_SPACE_HEIGHT_TABLETS_DPS) :
-                (int) (density * SEARCH_BAR_SPACE_HEIGHT_PHONES_DPS);
-        if (isLargeScreen) {
-            taskStackWidthPaddingPct = STACK_SIZE_PADDING_TABLETS_PCT;
-        } else if (isXLargeScreen) {
-            taskStackWidthPaddingPct = STACK_SIZE_PADDING_LARGE_TABLETS_PCT;
-        } else {
-            taskStackWidthPaddingPct = STACK_SIDE_PADDING_PHONES_PCT;
-        }
+        isLargeScreen = smallestWidth >= (int) (screenDensity * LARGE_SCREEN_MIN_DP);
+        isXLargeScreen = smallestWidth >= (int) (screenDensity * XLARGE_SCREEN_MIN_DP);
     }
 
     /**
@@ -103,7 +83,6 @@
      */
     void update(Rect systemInsets) {
         hasTransposedNavBar = systemInsets.right > 0;
-        hasTransposedSearchBar = systemInsets.right > 0;
     }
 
     /**
@@ -121,40 +100,4 @@
     public void updateOnConfigurationChange() {
         mLaunchState.updateOnConfigurationChange();
     }
-
-    /**
-     * Returns the task stack bounds in the current orientation. These bounds do not account for
-     * the system insets.
-     */
-    public void getTaskStackBounds(Rect windowBounds, int topInset,
-            int rightInset, Rect searchBarBounds, Rect taskStackBounds) {
-        if (hasTransposedNavBar) {
-            // In landscape phones, the search bar appears on the left, but we overlay it on top
-            taskStackBounds.set(windowBounds.left, windowBounds.top + topInset,
-                    windowBounds.right - rightInset, windowBounds.bottom);
-        } else {
-            // In portrait, the search bar appears on the top (which already has the inset)
-            int top = searchBarBounds.isEmpty() ? topInset : 0;
-            taskStackBounds.set(windowBounds.left, windowBounds.top + searchBarBounds.bottom + top,
-                    windowBounds.right - rightInset, windowBounds.bottom);
-        }
-    }
-
-    /**
-     * Returns the search bar bounds in the current orientation.  These bounds do not account for
-     * the system insets.
-     */
-    public void getSearchBarBounds(Rect windowBounds, int topInset, Rect searchBarSpaceBounds) {
-        // Return empty rects if search is not enabled
-        int searchBarSize = searchBarSpaceHeightPx;
-        if (hasTransposedSearchBar) {
-            // In landscape phones, the search bar appears on the left
-            searchBarSpaceBounds.set(windowBounds.left, windowBounds.top + topInset,
-                    windowBounds.left + searchBarSize, windowBounds.bottom);
-        } else {
-            // In portrait, the search bar appears on the top
-            searchBarSpaceBounds.set(windowBounds.left, windowBounds.top + topInset,
-                    windowBounds.right, windowBounds.top + topInset + searchBarSize);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 40bf6d3..043510e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -31,14 +31,12 @@
     public static class Static {
         // Enables debug drawing for the transition thumbnail
         public static final boolean EnableTransitionThumbnailDebugMode = false;
-        // This enables the search bar integration
-        public static final boolean EnableSearchBar = false;
         // This disables the bitmap and icon caches
         public static final boolean DisableBackgroundCache = false;
         // Enables the task affiliations
-        public static final boolean EnableAffiliatedTaskGroups = true;
-        // Enables the history
-        public static final boolean EnableHistory = false;
+        public static final boolean EnableAffiliatedTaskGroups = false;
+        // TODO: To be repurposed
+        public static final boolean EnableStackActionButton = false;
         // Overrides the Tuner flags and enables the timeout
         private static final boolean EnableFastToggleTimeout = false;
         // Overrides the Tuner flags and enables the paging via the Recents button
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 880fe10..3ff33a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -20,8 +20,6 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.ITaskStackListener;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +28,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -41,7 +40,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.recents.events.EventBus;
@@ -59,6 +57,7 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
@@ -95,37 +94,13 @@
     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
 
     /**
-     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
+     * An implementation of TaskStackListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
      */
-    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
-        Handler mHandler;
-
-        public TaskStackListenerImpl(Handler handler) {
-            mHandler = handler;
-        }
-
+    class TaskStackListenerImpl extends TaskStackListener {
         @Override
         public void onTaskStackChanged() {
-            // Debounce any task stack changes
-            mHandler.removeCallbacks(this);
-            mHandler.post(this);
-        }
-
-        @Override
-        public void onActivityPinned() {
-        }
-
-        @Override
-        public void onPinnedActivityRestartAttempt() {
-        }
-
-        @Override
-        public void onPinnedStackAnimationEnded() {
-        }
-
-        /** Preloads the next task */
-        public void run() {
+            // Preloads the next task
             RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
                 RecentsTaskLoader loader = Recents.getTaskLoader();
@@ -156,13 +131,11 @@
     protected Context mContext;
     protected Handler mHandler;
     TaskStackListenerImpl mTaskStackListener;
-    RecentsAppWidgetHost mAppWidgetHost;
     protected boolean mCanReuseTaskStackViews = true;
     boolean mDraggingInRecents;
     boolean mLaunchedWhileDocking;
 
     // Task launching
-    Rect mSearchBarBounds = new Rect();
     Rect mTaskStackBounds = new Rect();
     Rect mLastTaskViewBounds = new Rect();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -195,19 +168,18 @@
     public RecentsImpl(Context context) {
         mContext = context;
         mHandler = new Handler();
-        mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
 
         // Initialize the static foreground thread
         ForegroundThread.get();
 
         // Register the task stack listener
-        mTaskStackListener = new TaskStackListenerImpl(mHandler);
+        mTaskStackListener = new TaskStackListenerImpl();
         SystemServicesProxy ssp = Recents.getSystemServices();
         ssp.registerTaskStackListener(mTaskStackListener);
 
         // Initialize the static configuration resources
         reloadHeaderBarLayout();
-        updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
+        updateHeaderBarLayout(null /* stack */);
 
         // When we start, preload the data associated with the previous recent tasks.
         // We can use a new plan since the caches will be the same.
@@ -222,12 +194,12 @@
     }
 
     public void onBootCompleted() {
-        updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
+        updateHeaderBarLayout(null /* stack */);
     }
 
     public void onConfigurationChanged() {
         reloadHeaderBarLayout();
-        updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
+        updateHeaderBarLayout(null /* stack */);
         // Don't reuse task stack views if the configuration changes
         mCanReuseTaskStackViews = false;
         Recents.getConfiguration().updateOnConfigurationChange();
@@ -585,8 +557,13 @@
                 com.android.internal.R.dimen.navigation_bar_height);
         mNavBarWidth = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.navigation_bar_width);
-        mTaskBarHeight = res.getDimensionPixelSize(
-                R.dimen.recents_task_bar_height);
+        mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(res,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height_tablet_land,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height_tablet_land);
         mDummyStackView = new TaskStackView(mContext);
         mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
                 null, false);
@@ -596,12 +573,9 @@
      * Prepares the header bar layout for the next transition, if the task view bounds has changed
      * since the last call, it will attempt to re-measure and layout the header bar to the new size.
      *
-     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
-     *                               is not already bound (can be expensive)
      * @param stack the stack to initialize the stack layout with
      */
-    private void updateHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) {
-        RecentsConfiguration config = Recents.getConfiguration();
+    private void updateHeaderBarLayout(TaskStack stack) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         Rect systemInsets = new Rect();
         ssp.getStableInsets(systemInsets);
@@ -610,28 +584,20 @@
         windowRect.offsetTo(0, 0);
 
         // Update the configuration for the current state
-        config.update(systemInsets);
+        Recents.getConfiguration().update(systemInsets);
 
-        if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
-            // Try and pre-emptively bind the search widget on startup to ensure that we
-            // have the right thumbnail bounds to animate to.
-            // Note: We have to reload the widget id before we get the task stack bounds below
-            if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
-                config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
-            }
-        }
-        config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
-                mSearchBarBounds, mTaskStackBounds);
+        TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+        stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+                mTaskStackBounds);
 
         // Rebind the header bar and draw it for the transition
-        TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
         Rect taskStackBounds = new Rect(mTaskStackBounds);
         stackLayout.setSystemInsets(systemInsets);
         if (stack != null) {
-            stackLayout.initialize(taskStackBounds,
+            stackLayout.initialize(windowRect, taskStackBounds,
                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
             mDummyStackView.setTasks(stack, false /* notifyStackChanges */,
-                    false /* relayoutTaskStack */);
+                    false /* relayoutTaskStack */, false /* multiWindowChange */);
         }
         Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
         if (!taskViewBounds.equals(mLastTaskViewBounds)) {
@@ -688,7 +654,7 @@
         preloadIcon(topTask);
 
         // Update the header bar if necessary
-        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
+        updateHeaderBarLayout(stack);
 
         // Update the destination rect
         final Task toTask = new Task();
@@ -721,13 +687,7 @@
     /**
      * Creates the activity options for a home->recents transition.
      */
-    protected ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
-        if (fromSearchHome) {
-            return ActivityOptions.makeCustomAnimation(mContext,
-                    R.anim.recents_from_search_launcher_enter,
-                    R.anim.recents_from_search_launcher_exit,
-                    mHandler, null);
-        }
+    protected ActivityOptions getHomeTransitionActivityOptions() {
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit,
@@ -839,6 +799,12 @@
                 } else {
                     Canvas c = new Canvas(thumbnail);
                     c.scale(toTransform.scale, toTransform.scale);
+                    // Workaround for b/27815919, reset the callback so that we do not trigger an
+                    // invalidate on the header bar as a result of updating the icon
+                    Drawable icon = mHeaderBar.getIconView().getDrawable();
+                    if (icon != null) {
+                        icon.setCallback(null);
+                    }
                     mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
                             disabledInSafeMode);
                     mHeaderBar.setDimAlpha(toTransform.dimAlpha);
@@ -857,6 +823,7 @@
     protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
             boolean isTopTaskHome, boolean animate) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
 
         // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
         // should always preload the tasks now. If we are dragging in recents, reload them as
@@ -868,31 +835,43 @@
         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
             loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
         }
+
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
+        boolean hasRecentTasks = stack.getTaskCount() > 0;
+        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
 
         // Update the header bar if necessary
-        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
+        updateHeaderBarLayout(stack);
 
         // Prepare the dummy stack for the transition
         TaskStackLayoutAlgorithm.VisibilityReport stackVr =
                 mDummyStackView.computeStackVisibilityReport();
 
+        // Update the launch state
+        launchState.launchedFromHome = false;
+        launchState.launchedFromApp = mLaunchedWhileDocking;
+        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+        launchState.launchedFromAppDocked = mLaunchedWhileDocking;
+        launchState.launchedWithAltTab = mTriggeredFromAltTab;
+        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+        launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
+        launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
+        launchState.launchedHasConfigurationChanged = false;
+        launchState.launchedViaDragGesture = mDraggingInRecents;
+        launchState.launchedWhileDocking = mLaunchedWhileDocking;
+
         if (!animate) {
-            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
-            startRecentsActivity(topTask, opts, false /* fromHome */,
-                    false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
+            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
             return;
         }
 
-        boolean hasRecentTasks = stack.getTaskCount() > 0;
-        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
-
         if (useThumbnailTransition) {
+            launchState.launchedFromApp = true;
+
             // Try starting with a thumbnail transition
             ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
             if (opts != null) {
-                startRecentsActivity(topTask, opts, false /* fromHome */,
-                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
+                startRecentsActivity(opts);
             } else {
                 // Fall through below to the non-thumbnail transition
                 useThumbnailTransition = false;
@@ -900,34 +879,14 @@
         }
 
         if (!useThumbnailTransition) {
-            // If there is no thumbnail transition, but is launching from home into recents, then
-            // use a quick home transition and do the animation from home
-            if (hasRecentTasks) {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                String homeActivityPackage = ssp.getHomeActivityPackageName();
-                String searchWidgetPackage = null;
-                if (RecentsDebugFlags.Static.EnableSearchBar) {
-                    searchWidgetPackage = Prefs.getString(mContext,
-                            Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
-                } else {
-                    AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
-                    if (searchWidgetInfo != null) {
-                        searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
-                    }
-                }
+            launchState.launchedFromHome = true;
 
-                // Determine whether we are coming from a search owned home activity
-                boolean fromSearchHome = (homeActivityPackage != null) &&
-                        homeActivityPackage.equals(searchWidgetPackage);
-                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
-                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
-                        false /* fromThumbnail */, stackVr);
-            } else {
-                // Otherwise we do the normal fade from an unknown source
-                ActivityOptions opts = getUnknownTransitionActivityOptions();
-                startRecentsActivity(topTask, opts, true /* fromHome */,
-                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
-            }
+            // If there is no thumbnail transition, but is launching from home into recents, then
+            // use a quick home transition
+            ActivityOptions opts = hasRecentTasks
+                    ? getHomeTransitionActivityOptions()
+                    : getUnknownTransitionActivityOptions();
+            startRecentsActivity(opts);
         }
         mLastToggleTime = SystemClock.elapsedRealtime();
     }
@@ -935,25 +894,7 @@
     /**
      * Starts the recents activity.
      */
-    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
-                ActivityOptions opts, boolean fromHome, boolean fromSearchHome,
-                boolean fromThumbnail, TaskStackLayoutAlgorithm.VisibilityReport vr) {
-        // Update the configuration based on the launch options
-        RecentsConfiguration config = Recents.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        launchState.launchedFromHome = fromSearchHome || fromHome;
-        launchState.launchedFromSearchHome = fromSearchHome;
-        launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking;
-        launchState.launchedFromAppDocked = mLaunchedWhileDocking;
-        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
-        launchState.launchedWithAltTab = mTriggeredFromAltTab;
-        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
-        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
-        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
-        launchState.launchedHasConfigurationChanged = false;
-        launchState.launchedViaDragGesture = mDraggingInRecents;
-        launchState.launchedWhileDocking = mLaunchedWhileDocking;
-
+    private void startRecentsActivity(ActivityOptions opts) {
         Intent intent = new Intent();
         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java
deleted file mode 100644
index 52cfe18..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.RecentsAppWidgetHost;
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent by the {@link RecentsAppWidgetHost} whenever the search provider widget changes, and
- * subscribers can update accordingly.
- */
-public class AppWidgetProviderChangedEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ClearHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ClearHistoryEvent.java
deleted file mode 100644
index 98c0a69..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ClearHistoryEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the history is to be cleared
- */
-public class ClearHistoryEvent extends EventBus.AnimatedEvent {
-    // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryButtonEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryButtonEvent.java
deleted file mode 100644
index 6c767e4..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryButtonEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the history view button should be hidden.
- */
-public class HideHistoryButtonEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java
deleted file mode 100644
index bacf3bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the history view will be closed.
- */
-public class HideHistoryEvent extends EventBus.Event {
-
-    public final boolean animate;
-
-    public HideHistoryEvent(boolean animate) {
-        this.animate = animate;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
index 469f336..e02fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
@@ -19,10 +19,8 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the history view button is clicked.
+ * This is sent when the stack action button should be hidden.
  */
-public class ShowHistoryEvent extends EventBus.Event {
-
+public class HideStackActionButtonEvent extends EventBus.Event {
     // Simple event
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryButtonEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowStackActionButtonEvent.java
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryButtonEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowStackActionButtonEvent.java
index ae803ea..d81f89c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryButtonEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowStackActionButtonEvent.java
@@ -19,14 +19,14 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the history view button should be shown.
+ * This is sent when the stack action view button should be shown.
  */
-public class ShowHistoryButtonEvent extends EventBus.Event {
+public class ShowStackActionButtonEvent extends EventBus.Event {
 
-    // Whether or not to translate the history button when showing it
+    // Whether or not to translate the stack action button when showing it
     public final boolean translate;
 
-    public ShowHistoryButtonEvent(boolean translate) {
+    public ShowStackActionButtonEvent(boolean translate) {
         this.translate = translate;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
deleted file mode 100644
index 16385c9..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.history;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.text.format.DateFormat;
-import android.util.SparseIntArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
-import com.android.systemui.recents.events.activity.HideHistoryEvent;
-import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.AnimationProps;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-
-
-/**
- * An adapter for the list of recent tasks in the history view.
- */
-public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAdapter.ViewHolder> {
-
-    private static final String TAG = "RecentsHistoryView";
-    private static final boolean DEBUG = false;
-
-    static final int DATE_ROW_VIEW_TYPE = 0;
-    static final int TASK_ROW_VIEW_TYPE = 1;
-
-    /**
-     * View holder implementation. The {@param TaskCallbacks} are only called for TaskRow view
-     * holders.
-     */
-    public static class ViewHolder extends RecyclerView.ViewHolder implements Task.TaskCallbacks {
-        public final View content;
-
-        private Task mTask;
-
-        public ViewHolder(View content) {
-            super(content);
-            this.content = content;
-        }
-
-        /**
-         * Binds this view holder to the given task.
-         */
-        public void bindToTask(Task newTask) {
-            unbindFromTask();
-            mTask = newTask;
-            mTask.addCallback(this);
-        }
-
-        /**
-         * Unbinds this view holder from the
-         */
-        public void unbindFromTask() {
-            if (mTask != null) {
-                mTask.removeCallback(this);
-                mTask = null;
-            }
-        }
-
-        @Override
-        public void onTaskDataLoaded(Task task) {
-            // This callback is only made for TaskRow view holders
-            ImageView iv = (ImageView) content.findViewById(R.id.icon);
-            iv.setImageDrawable(task.icon);
-            iv.animate()
-                    .alpha(1f)
-                    .setDuration(100)
-                    .start();
-        }
-
-        @Override
-        public void onTaskDataUnloaded() {
-            // This callback is only made for TaskRow view holders
-            ImageView iv = (ImageView) content.findViewById(R.id.icon);
-            iv.setImageBitmap(null);
-            iv.animate().cancel();
-        }
-
-        @Override
-        public void onTaskStackIdChanged() {
-            // Do nothing, this callback is only made for TaskRow view holders
-        }
-    }
-
-    /**
-     * A single row of content.
-     */
-    interface Row {
-        int getViewType();
-    }
-
-    /**
-     * A date row.
-     */
-    static class DateRow implements Row {
-
-        public final String date;
-
-        public DateRow(String date) {
-            this.date = date;
-        }
-
-        @Override
-        public int getViewType() {
-            return RecentsHistoryAdapter.DATE_ROW_VIEW_TYPE;
-        }
-    }
-
-    /**
-     * A task row.
-     */
-    static class TaskRow implements Row, View.OnClickListener {
-
-        public final Task task;
-        public final int dateKey;
-
-        public TaskRow(Task task, int dateKey) {
-            this.task = task;
-            this.dateKey = dateKey;
-        }
-
-        @Override
-        public void onClick(View v) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            ssp.startActivityFromRecents(v.getContext(), task.key.id, task.title,
-                    ActivityOptions.makeBasic());
-
-            MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
-                    task.key.getComponent().toString());
-        }
-
-        @Override
-        public int getViewType() {
-            return RecentsHistoryAdapter.TASK_ROW_VIEW_TYPE;
-        }
-    }
-
-    private Context mContext;
-    private LayoutInflater mLayoutInflater;
-    private final List<Row> mRows = new ArrayList<>();
-    private final SparseIntArray mTaskRowCount = new SparseIntArray();
-    private TaskStack mStack;
-
-    public RecentsHistoryAdapter(Context context) {
-        mLayoutInflater = LayoutInflater.from(context);
-    }
-
-    /**
-     * Updates this adapter with the given tasks.
-     */
-    public void updateTasks(Context context, TaskStack stack) {
-        mContext = context;
-        mStack = stack;
-
-        final Locale l = context.getResources().getConfiguration().locale;
-        final String dateFormatStr = DateFormat.getBestDateTimePattern(l, "EEEEMMMMd");
-        final List<Task> tasksMostRecent = new ArrayList<>(stack.getHistoricalTasks());
-        Collections.reverse(tasksMostRecent);
-        int prevDateKey = -1;
-        int taskCount = tasksMostRecent.size();
-        mRows.clear();
-        mTaskRowCount.clear();
-        Calendar cal = Calendar.getInstance(l);
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasksMostRecent.get(i);
-            if (task.isFreeformTask()) {
-                continue;
-            }
-
-            cal.setTimeInMillis(task.key.lastActiveTime);
-            int dateKey = Objects.hash(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR));
-            if (dateKey != prevDateKey) {
-                prevDateKey = dateKey;
-                mRows.add(new DateRow(DateFormat.format(dateFormatStr, cal).toString()));
-            }
-            mRows.add(new TaskRow(task, dateKey));
-            mTaskRowCount.put(dateKey, mTaskRowCount.get(dateKey, 0) + 1);
-        }
-        notifyDataSetChanged();
-    }
-
-    /**
-     * Removes historical tasks belonging to the specified package and user. We do not need to
-     * remove the task from the TaskStack since the TaskStackView will also receive this event.
-     */
-    public void removeTasks(String packageName, int userId) {
-        for (int i = mRows.size() - 1; i >= 0; i--) {
-            Row row = mRows.get(i);
-            if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
-                TaskRow taskRow = (TaskRow) row;
-                Task task = taskRow.task;
-                String taskPackage = task.key.getComponent().getPackageName();
-                if (task.key.userId == userId && taskPackage.equals(packageName)) {
-                    i = removeTaskRow(i);
-                }
-            }
-        }
-        if (mRows.isEmpty()) {
-            dismissHistory();
-        }
-    }
-
-    /**
-     * Removes all historical tasks.
-     */
-    public void removeAllTasks() {
-        for (int i = mRows.size() - 1; i >= 0; i--) {
-            Row row = mRows.get(i);
-            if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
-                TaskRow taskRow = (TaskRow) row;
-                Task task = taskRow.task;
-                mStack.removeTask(task, AnimationProps.IMMEDIATE, false /* fromDockGesture */);
-                EventBus.getDefault().send(new DeleteTaskDataEvent(task));
-                i = removeTaskRow(i);
-            }
-        }
-        dismissHistory();
-    }
-
-    /**
-     * Returns the row at the given {@param position}.
-     */
-    public Row getRow(int position) {
-        return mRows.get(position);
-    }
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        switch (viewType) {
-            case DATE_ROW_VIEW_TYPE:
-                return new ViewHolder(mLayoutInflater.inflate(R.layout.recents_history_date, parent,
-                        false));
-            case TASK_ROW_VIEW_TYPE:
-                return new ViewHolder(mLayoutInflater.inflate(R.layout.recents_history_task, parent,
-                        false));
-            default:
-                return new ViewHolder(null);
-        }
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        RecentsTaskLoader loader = Recents.getTaskLoader();
-
-        Row row = mRows.get(position);
-        int viewType = row.getViewType();
-        switch (viewType) {
-            case DATE_ROW_VIEW_TYPE: {
-                TextView tv = (TextView) holder.content;
-                tv.setText(((DateRow) row).date);
-                break;
-            }
-            case TASK_ROW_VIEW_TYPE: {
-                TaskRow taskRow = (TaskRow) row;
-                TextView tv = (TextView) holder.content.findViewById(R.id.description);
-                tv.setText(taskRow.task.title);
-                ImageView iv = (ImageView) holder.content.findViewById(R.id.icon);
-                iv.setAlpha(0f);
-                holder.content.setOnClickListener(taskRow);
-
-                holder.bindToTask(taskRow.task);
-                loader.loadTaskData(taskRow.task, false /* fetchAndInvalidateThumbnails */);
-                break;
-            }
-        }
-    }
-
-    @Override
-    public void onViewRecycled(ViewHolder holder) {
-        RecentsTaskLoader loader = Recents.getTaskLoader();
-        int position = holder.getAdapterPosition();
-        if (position != RecyclerView.NO_POSITION) {
-            Row row = mRows.get(position);
-            int viewType = row.getViewType();
-            if (viewType == TASK_ROW_VIEW_TYPE) {
-                TaskRow taskRow = (TaskRow) row;
-                loader.unloadTaskData(taskRow.task);
-                holder.unbindFromTask();
-            }
-        }
-    }
-
-    @Override
-    public boolean onFailedToRecycleView(ViewHolder holder) {
-        // Always recycle views, even if it is animating
-        onViewRecycled(holder);
-        return true;
-    }
-
-    public void onTaskRemoved(Task task, int position) {
-        // Since this is removed from the history, we need to update the stack as well to ensure
-        // that the model is correct. Since the stack is hidden, we can update it immediately.
-        mStack.removeTask(task, AnimationProps.IMMEDIATE, false /* fromDockGesture */);
-        removeTaskRow(position);
-        if (mRows.isEmpty()) {
-            dismissHistory();
-        }
-    }
-
-    @Override
-    public int getItemCount() {
-        return mRows.size();
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        return mRows.get(position).getViewType();
-    }
-
-    /**
-     * Removes a task row, also removing the associated {@link DateRow} if there are no more tasks
-     * in that date group.
-     *
-     * @param position an adapter position of a task row such that 0 < position < num rows.
-     * @return the index of the last removed row
-     */
-    private int removeTaskRow(int position) {
-        // Remove the task at that row
-        TaskRow taskRow = (TaskRow) mRows.remove(position);
-        int numTasks = mTaskRowCount.get(taskRow.dateKey) - 1;
-        mTaskRowCount.put(taskRow.dateKey, numTasks);
-        notifyItemRemoved(position);
-
-        if (numTasks == 0) {
-            // If that was the last task row in the group, then remove the date as well
-            mRows.remove(position - 1);
-            mTaskRowCount.removeAt(mTaskRowCount.indexOfKey(taskRow.dateKey));
-            notifyItemRemoved(position - 1);
-            return position - 1;
-        } else {
-            return position;
-        }
-    }
-
-    /**
-     * Dismisses history back to the stack view.
-     */
-    private void dismissHistory() {
-        EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
-        EventBus.getDefault().send(new HideHistoryButtonEvent());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
deleted file mode 100644
index 3d1ea8e..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.history;
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.helper.ItemTouchHelper;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
-
-
-/**
- * An item touch handler for items in the history view.
- */
-public class RecentsHistoryItemTouchCallbacks extends ItemTouchHelper.SimpleCallback {
-
-    private Context mContext;
-    private RecentsHistoryAdapter mAdapter;
-
-    public RecentsHistoryItemTouchCallbacks(Context context, RecentsHistoryAdapter adapter) {
-        super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
-        mContext = context;
-        mAdapter = adapter;
-    }
-
-    @Override
-    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
-            RecyclerView.ViewHolder target) {
-        return false;
-    }
-
-    @Override
-    public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
-        int viewType = mAdapter.getItemViewType(viewHolder.getAdapterPosition());
-        switch (viewType) {
-            case RecentsHistoryAdapter.DATE_ROW_VIEW_TYPE:
-                // Disallow swiping
-                return 0;
-            default:
-                return super.getSwipeDirs(recyclerView, viewHolder);
-        }
-    }
-
-    @Override
-    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
-        int position = viewHolder.getAdapterPosition();
-        if (position != RecyclerView.NO_POSITION) {
-            RecentsHistoryAdapter.Row row = mAdapter.getRow(position);
-            RecentsHistoryAdapter.TaskRow taskRow = (RecentsHistoryAdapter.TaskRow) row;
-
-            // Remove the task from the system
-            EventBus.getDefault().send(new DeleteTaskDataEvent(taskRow.task));
-            mAdapter.onTaskRemoved(taskRow.task, position);
-
-            // Keep track of deletions by swiping within history
-            MetricsLogger.histogram(mContext, "overview_task_dismissed_source",
-                    Constants.Metrics.DismissSourceHistorySwipeGesture);
-            MetricsLogger.action(mContext, MetricsEvent.OVERVIEW_DISMISS,
-                    taskRow.task.key.getComponent().toString());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
deleted file mode 100644
index 3c4adb2..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.history;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowInsets;
-import android.widget.LinearLayout;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.ClearHistoryEvent;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent;
-import com.android.systemui.recents.events.ui.UpdateBackgroundScrimEvent;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.AnimateableViewBounds;
-
-/**
- * A list of the recent tasks that are not in the stack.
- */
-public class RecentsHistoryView extends LinearLayout
-        implements ValueAnimator.AnimatorUpdateListener {
-
-    private static final float TRANSLATION_Y_PCT = 0.25f;
-    private static final float BG_SCRIM_ALPHA = 0.625f;
-
-    private RecyclerView mRecyclerView;
-    private RecentsHistoryAdapter mAdapter;
-    private RecentsHistoryItemTouchCallbacks mItemTouchHandler;
-    private AnimateableViewBounds mViewBounds;
-    private boolean mIsVisible;
-    private Rect mSystemInsets = new Rect();
-    private int mHeaderHeight;
-
-    private int mHistoryTransitionDuration;
-
-    public RecentsHistoryView(Context context) {
-        super(context);
-    }
-
-    public RecentsHistoryView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public RecentsHistoryView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public RecentsHistoryView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        Resources res = context.getResources();
-        mAdapter = new RecentsHistoryAdapter(context);
-        mItemTouchHandler = new RecentsHistoryItemTouchCallbacks(context, mAdapter);
-        mHistoryTransitionDuration = res.getInteger(R.integer.recents_history_transition_duration);
-        mViewBounds = new AnimateableViewBounds(this, 0);
-        setOutlineProvider(mViewBounds);
-    }
-
-    /**
-     * Updates this history view with the recent tasks, and then shows it.
-     */
-    public void show(TaskStack stack, int stackHeight, View clearAllButton) {
-        setVisibility(View.VISIBLE);
-        setAlpha(0f);
-        setTranslationY(-stackHeight * TRANSLATION_Y_PCT);
-        animate()
-                .alpha(1f)
-                .translationY(0f)
-                .setDuration(mHistoryTransitionDuration)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .setUpdateListener(this)
-                .start();
-        clearAllButton.setVisibility(View.VISIBLE);
-        clearAllButton.setAlpha(0f);
-        clearAllButton.animate()
-                .alpha(1f)
-                .setDuration(mHistoryTransitionDuration)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .withLayer()
-                .start();
-        mAdapter.updateTasks(getContext(), stack);
-        mIsVisible = true;
-        EventBus.getDefault().send(new UpdateBackgroundScrimEvent(BG_SCRIM_ALPHA));
-
-        MetricsLogger.visible(mRecyclerView.getContext(), MetricsEvent.OVERVIEW_HISTORY);
-    }
-
-    /**
-     * Hides this history view.
-     */
-    public void hide(boolean animate, int stackHeight, final View clearAllButton) {
-        if (animate) {
-            animate()
-                    .alpha(0f)
-                    .translationY(-stackHeight * TRANSLATION_Y_PCT)
-                    .setDuration(mHistoryTransitionDuration)
-                    .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                    .setUpdateListener(this)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            setVisibility(View.INVISIBLE);
-                        }
-                    })
-                    .start();
-            clearAllButton.animate()
-                    .alpha(0f)
-                    .translationY(0f)
-                    .setDuration(mHistoryTransitionDuration)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            clearAllButton.setVisibility(View.INVISIBLE);
-                        }
-                    })
-                    .withLayer()
-                    .start();
-        } else {
-            setAlpha(0f);
-            setVisibility(View.INVISIBLE);
-            clearAllButton.setAlpha(0f);
-            clearAllButton.setVisibility(View.INVISIBLE);
-        }
-        mIsVisible = false;
-        EventBus.getDefault().send(new ResetBackgroundScrimEvent());
-
-        MetricsLogger.hidden(mRecyclerView.getContext(), MetricsEvent.OVERVIEW_HISTORY);
-    }
-
-    /**
-     * Updates the system insets of this history view to the provided values.
-     */
-    public void setSystemInsets(Rect systemInsets) {
-        mSystemInsets.set(systemInsets);
-        requestLayout();
-    }
-
-    /**
-     * Updates the header height to account for the history button bar.
-     */
-    public void setHeaderHeight(int height) {
-        mHeaderHeight = height;
-        requestLayout();
-    }
-
-    /**
-     * Returns whether this view is visible.
-     */
-    public boolean isVisible() {
-        return mIsVisible;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mRecyclerView = (RecyclerView) findViewById(R.id.list);
-        mRecyclerView.setAdapter(mAdapter);
-        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-        mRecyclerView.getItemAnimator().setRemoveDuration(100);
-        ItemTouchHelper touchHelper = new ItemTouchHelper(mItemTouchHandler);
-        touchHelper.attachToRecyclerView(mRecyclerView);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        RecentsConfiguration config = Recents.getConfiguration();
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Pad the view to align the history with the stack layout
-        Rect taskStackBounds = new Rect();
-        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
-                mSystemInsets.right, new Rect() /* searchBarSpaceBounds */, taskStackBounds);
-        int stackWidthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
-        int stackHeightPadding = mContext.getResources().getDimensionPixelSize(
-                R.dimen.recents_stack_top_padding);
-        mRecyclerView.setPadding(stackWidthPadding + mSystemInsets.left,
-                stackHeightPadding + mSystemInsets.top + mHeaderHeight,
-                stackWidthPadding + mSystemInsets.right, mSystemInsets.bottom);
-
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-        super.onAttachedToWindow();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        EventBus.getDefault().unregister(this);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        setSystemInsets(insets.getSystemWindowInsets());
-        return insets;
-    }
-
-    @Override
-    public void onAnimationUpdate(ValueAnimator animation) {
-        // Clip the top of the view by the header bar height
-        int top = Math.max(0, (int) -getTranslationY()) + mSystemInsets.top + mHeaderHeight;
-        mViewBounds.setClipTop(top);
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    /**** EventBus Events ****/
-
-    public final void onBusEvent(PackagesChangedEvent event) {
-        mAdapter.removeTasks(event.packageName, event.userId);
-    }
-
-    public final void onBusEvent(ClearHistoryEvent event) {
-        mAdapter.removeAllTasks();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 330d138..ea4888d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,15 +16,18 @@
 
 package com.android.systemui.recents.misc;
 
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.ITaskStackListener;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -34,6 +37,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -47,7 +51,9 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -57,9 +63,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MutableBoolean;
-import android.util.Pair;
 import android.view.Display;
 import android.view.IDockedStackListener;
+import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.KeyboardShortcutsReceiver;
@@ -68,11 +74,11 @@
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
-import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
 import com.android.systemui.recents.tv.RecentsTvImpl;
+import com.android.systemui.recents.model.ThumbnailData;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -80,12 +86,6 @@
 import java.util.List;
 import java.util.Random;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
-
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
  * a point of injection when testing UI.
@@ -106,10 +106,11 @@
         sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity");
     }
 
+    private static SystemServicesProxy sSystemServicesProxy;
+
     AccessibilityManager mAccm;
     ActivityManager mAm;
     IActivityManager mIam;
-    AppWidgetManager mAwm;
     PackageManager mPm;
     IPackageManager mIpm;
     AssistUtils mAssistUtils;
@@ -128,12 +129,61 @@
     Paint mBgProtectionPaint;
     Canvas mBgProtectionCanvas;
 
+    private final Handler mHandler = new H();
+
+    /**
+     * An abstract class to track task stack changes.
+     * Classes should implement this instead of {@link android.app.ITaskStackListener}
+     * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+     */
+    public abstract static class TaskStackListener {
+        public void onTaskStackChanged() { }
+        public void onActivityPinned() { }
+        public void onPinnedActivityRestartAttempt() { }
+        public void onPinnedStackAnimationEnded() { }
+    }
+
+    /**
+     * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from
+     * ActivityManagerNative.
+     * This simply passes callbacks to listeners through {@link H}.
+     * */
+    private ITaskStackListener.Stub mTaskStackListener = new ITaskStackListener.Stub() {
+        @Override
+        public void onTaskStackChanged() throws RemoteException {
+            mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
+            mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
+        }
+
+        @Override
+        public void onActivityPinned() throws RemoteException {
+            mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
+            mHandler.sendEmptyMessage(H.ON_ACTIVITY_PINNED);
+        }
+
+        @Override
+        public void onPinnedActivityRestartAttempt() throws RemoteException{
+            mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
+            mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
+        }
+
+        @Override
+        public void onPinnedStackAnimationEnded() throws RemoteException {
+            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
+            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
+        }
+    };
+
+    /**
+     * List of {@link TaskStackListener} registered from {@link registerTaskStackListener}.
+     */
+    private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
+
     /** Private constructor */
-    public SystemServicesProxy(Context context) {
+    private SystemServicesProxy(Context context) {
         mAccm = AccessibilityManager.getInstance(context);
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mIam = ActivityManagerNative.getDefault();
-        mAwm = AppWidgetManager.getInstance(context);
         mPm = context.getPackageManager();
         mIpm = AppGlobals.getPackageManager();
         mAssistUtils = new AssistUtils(context);
@@ -170,6 +220,20 @@
         }
     }
 
+    /**
+     * Returns the single instance of the {@link SystemServicesProxy}.
+     * This should only be called on the main thread.
+     */
+    public static SystemServicesProxy getInstance(Context context) {
+        if (!Looper.getMainLooper().isCurrentThread()) {
+            throw new RuntimeException("Must be called on the UI thread");
+        }
+        if (sSystemServicesProxy == null) {
+            sSystemServicesProxy = new SystemServicesProxy(context);
+        }
+        return sSystemServicesProxy;
+    }
+
     /** Returns a list of the recents tasks */
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
             boolean isTopTaskHome, ArraySet<Integer> quietProfileIds) {
@@ -450,44 +514,47 @@
     }
 
     /** Returns the top task thumbnail for the given task id */
-    public Bitmap getTaskThumbnail(int taskId) {
+    public ThumbnailData getTaskThumbnail(int taskId) {
         if (mAm == null) return null;
+        ThumbnailData thumbnailData = new ThumbnailData();
 
         // If we are mocking, then just return a dummy thumbnail
         if (RecentsDebugFlags.Static.EnableMockTasks) {
-            Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
-                    Bitmap.Config.ARGB_8888);
-            thumbnail.eraseColor(0xff333333);
-            return thumbnail;
+            thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
+                    mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
+            thumbnailData.thumbnail.eraseColor(0xff333333);
+            return thumbnailData;
         }
 
-        Bitmap thumbnail = getThumbnail(taskId);
-        if (thumbnail != null) {
-            thumbnail.setHasAlpha(false);
+        getThumbnail(taskId, thumbnailData);
+        if (thumbnailData.thumbnail != null) {
+            thumbnailData.thumbnail.setHasAlpha(false);
             // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
             // left pixel, then assume the whole thumbnail is transparent. Generally, proper
             // screenshots are always composed onto a bitmap that has no alpha.
-            if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) {
-                mBgProtectionCanvas.setBitmap(thumbnail);
-                mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(),
-                        mBgProtectionPaint);
+            if (Color.alpha(thumbnailData.thumbnail.getPixel(0, 0)) == 0) {
+                mBgProtectionCanvas.setBitmap(thumbnailData.thumbnail);
+                mBgProtectionCanvas.drawRect(0, 0, thumbnailData.thumbnail.getWidth(),
+                        thumbnailData.thumbnail.getHeight(), mBgProtectionPaint);
                 mBgProtectionCanvas.setBitmap(null);
                 Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
             }
         }
-        return thumbnail;
+        return thumbnailData;
     }
 
     /**
      * Returns a task thumbnail from the activity manager
      */
-    public Bitmap getThumbnail(int taskId) {
+    public void getThumbnail(int taskId, ThumbnailData thumbnailDataOut) {
         if (mAm == null) {
-            return null;
+            return;
         }
 
         ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
-        if (taskThumbnail == null) return null;
+        if (taskThumbnail == null) {
+            return;
+        }
 
         Bitmap thumbnail = taskThumbnail.mainThumbnail;
         ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
@@ -501,7 +568,8 @@
             } catch (IOException e) {
             }
         }
-        return thumbnail;
+        thumbnailDataOut.thumbnail = thumbnail;
+        thumbnailDataOut.thumbnailInfo = taskThumbnail.thumbnailInfo;
     }
 
     /**
@@ -785,89 +853,6 @@
     }
 
     /**
-     * Returns the current search widget id.
-     */
-    public int getSearchAppWidgetId(Context context) {
-        return Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
-    }
-
-    /**
-     * Returns the current search widget info, binding a new one if necessary.
-     */
-    public AppWidgetProviderInfo getOrBindSearchAppWidget(Context context, AppWidgetHost host) {
-        int searchWidgetId = Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
-        AppWidgetProviderInfo searchWidgetInfo = mAwm.getAppWidgetInfo(searchWidgetId);
-        AppWidgetProviderInfo resolvedSearchWidgetInfo = resolveSearchAppWidget();
-
-        // Return the search widget info if it hasn't changed
-        if (searchWidgetInfo != null && resolvedSearchWidgetInfo != null &&
-                searchWidgetInfo.provider.equals(resolvedSearchWidgetInfo.provider)) {
-            if (Prefs.getString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null) == null) {
-                Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
-                        searchWidgetInfo.provider.getPackageName());
-            }
-            return searchWidgetInfo;
-        }
-
-        // Delete the old widget
-        if (searchWidgetId != -1) {
-            host.deleteAppWidgetId(searchWidgetId);
-        }
-
-        // And rebind a new search widget
-        if (resolvedSearchWidgetInfo != null) {
-            Pair<Integer, AppWidgetProviderInfo> widgetInfo = bindSearchAppWidget(host,
-                    resolvedSearchWidgetInfo);
-            if (widgetInfo != null) {
-                Prefs.putInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, widgetInfo.first);
-                Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
-                        widgetInfo.second.provider.getPackageName());
-                return widgetInfo.second;
-            }
-        }
-
-        // If we fall through here, then there is no resolved search widget, so clear the state
-        Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID);
-        Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE);
-        return null;
-    }
-
-    /**
-     * Returns the first Recents widget from the same package as the global assist activity.
-     */
-    public AppWidgetProviderInfo resolveSearchAppWidget() {
-        if (mAssistComponent == null) return null;
-        List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders(
-                AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
-        for (AppWidgetProviderInfo info : widgets) {
-            if (info.provider.getPackageName().equals(mAssistComponent.getPackageName())) {
-                return info;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Resolves and binds the search app widget that is to appear in the recents.
-     */
-    private Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host,
-            AppWidgetProviderInfo resolvedSearchWidgetInfo) {
-        if (mAwm == null) return null;
-        if (mAssistComponent == null) return null;
-
-        // Allocate a new widget id and try and bind the app widget (if that fails, then just skip)
-        int searchWidgetId = host.allocateAppWidgetId();
-        Bundle opts = new Bundle();
-        opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
-                AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
-        if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, resolvedSearchWidgetInfo.provider, opts)) {
-            host.deleteAppWidgetId(searchWidgetId);
-            return null;
-        }
-        return new Pair<>(searchWidgetId, resolvedSearchWidgetInfo);
-    }
-
-    /**
      * Returns whether touch exploration is currently enabled.
      */
     public boolean isTouchExplorationEnabled() {
@@ -916,28 +901,40 @@
      * Returns the smallest width/height.
      */
     public int getDeviceSmallestWidth() {
-        if (mWm == null) return 0;
+        if (mDisplay == null) return 0;
 
         Point smallestSizeRange = new Point();
         Point largestSizeRange = new Point();
-        mWm.getDefaultDisplay().getCurrentSizeRange(smallestSizeRange, largestSizeRange);
+        mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange);
         return smallestSizeRange.x;
     }
 
     /**
-     * Returns the display rect.
+     * Returns the current display rect in the current display orientation.
      */
     public Rect getDisplayRect() {
         Rect displayRect = new Rect();
-        if (mWm == null) return displayRect;
+        if (mDisplay == null) return displayRect;
 
         Point p = new Point();
-        mWm.getDefaultDisplay().getRealSize(p);
+        mDisplay.getRealSize(p);
         displayRect.set(0, 0, p.x, p.y);
         return displayRect;
     }
 
     /**
+     * Returns the current display orientation.
+     */
+    public int getDisplayOrientation() {
+        // Because of multi-window, the configuration orientation does not necessarily reflect the
+        // orientation of the display, instead we just use the display's real-size.
+        Rect displayRect = getDisplayRect();
+        return displayRect.width() > displayRect.height()
+                ? Configuration.ORIENTATION_LANDSCAPE
+                : Configuration.ORIENTATION_PORTRAIT;
+    }
+
+    /**
      * Returns the window rect for the RecentsActivity, based on the dimensions of the home stack.
      */
     public Rect getWindowRect() {
@@ -982,14 +979,21 @@
         }
     }
 
-    /** Registers a task stack listener with the system. */
-    public void registerTaskStackListener(ITaskStackListener listener) {
+    /**
+     * Registers a task stack listener with the system.
+     * This should be called on the main thread.
+     */
+    public void registerTaskStackListener(TaskStackListener listener) {
         if (mIam == null) return;
 
-        try {
-            mIam.registerTaskStackListener(listener);
-        } catch (Exception e) {
-            e.printStackTrace();
+        mTaskStackListeners.add(listener);
+        if (mTaskStackListeners.size() == 1) {
+            // Register mTaskStackListener to IActivityManager only once if needed.
+            try {
+                mIam.registerTaskStackListener(mTaskStackListener);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to call registerTaskStackListener", e);
+            }
         }
     }
 
@@ -1026,8 +1030,9 @@
         return dividerWindowWidth - 2 * dividerInsets;
     }
 
-    public void requestKeyboardShortcuts(Context context, KeyboardShortcutsReceiver receiver) {
-        mWm.requestAppKeyboardShortcuts(receiver);
+    public void requestKeyboardShortcuts(
+            Context context, KeyboardShortcutsReceiver receiver, int deviceId) {
+        mWm.requestAppKeyboardShortcuts(receiver, deviceId);
     }
 
     public void getStableInsets(Rect outStableInsets) {
@@ -1039,4 +1044,41 @@
             e.printStackTrace();
         }
     }
+
+    private final class H extends Handler {
+        private static final int ON_TASK_STACK_CHANGED = 1;
+        private static final int ON_ACTIVITY_PINNED = 2;
+        private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 3;
+        private static final int ON_PINNED_STACK_ANIMATION_ENDED = 4;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ON_TASK_STACK_CHANGED: {
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onTaskStackChanged();
+                    }
+                    break;
+                }
+                case ON_ACTIVITY_PINNED: {
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onActivityPinned();
+                    }
+                    break;
+                }
+                case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onPinnedActivityRestartAttempt();
+                    }
+                    break;
+                }
+                case ON_PINNED_STACK_ANIMATION_ENDED: {
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
+                    }
+                    break;
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 72b1cab..e28612a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.annotation.FloatRange;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -26,6 +27,7 @@
 import android.util.ArraySet;
 import android.util.IntProperty;
 import android.util.Property;
+import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewParent;
 
@@ -67,6 +69,8 @@
 
     public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
 
+    public static final Rect EMPTY_RECT = new Rect();
+
     /**
      * @return the first parent walking up the view hierarchy that has the given class type.
      *
@@ -232,4 +236,11 @@
             transforms.subList(taskCount, taskTransformCount).clear();
         }
     }
+
+    /**
+     * Used for debugging, converts DP to PX.
+     */
+    public static float dpToPx(Resources res, float dp) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6ae07fc..76ca6ca 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -39,7 +39,6 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Formatter;
 import java.util.List;
 
 
@@ -161,8 +160,15 @@
                 long parentTaskLastActiveTime = parentTask != null
                         ? parentTask.lastActiveTime
                         : prevLastActiveTime;
-                t.lastActiveTime = parentTaskLastActiveTime +
-                        affiliatedTaskCounts.get(t.affiliatedTaskId, 0) + 1;
+                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
+                    t.lastActiveTime = parentTaskLastActiveTime +
+                            affiliatedTaskCounts.get(t.affiliatedTaskId, 0) + 1;
+                } else {
+                    if (t.lastActiveTime == 0) {
+                        t.lastActiveTime = parentTaskLastActiveTime -
+                                affiliatedTaskCounts.get(t.affiliatedTaskId, 0) - 1;
+                    }
+                }
             }
 
             // Compose the task key
@@ -196,13 +202,12 @@
             // Add the task to the stack
             Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
                     thumbnail, title, contentDescription, dismissDescription, activityColor,
-                    backgroundColor, !isStackTask, isLaunchTarget, isSystemApp, t.isDockable,
+                    backgroundColor, isLaunchTarget, isStackTask, isSystemApp, t.isDockable,
                     t.bounds, t.taskDescription);
 
             allTasks.add(task);
             affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
             affiliatedTasks.put(taskKey.id, taskKey);
-
             prevLastActiveTime = t.lastActiveTime;
         }
         if (newLastStackActiveTime != -1) {
@@ -241,8 +246,8 @@
 
             if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
                 if (task.icon == null) {
-                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription,
-                            res, true);
+                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
+                            true);
                 }
             }
             if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
@@ -273,7 +278,7 @@
     }
 
     /**
-     * Returns whether this task is considered a task to be shown in the history.
+     * Returns whether this task is too old to be shown.
      */
     private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
         return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
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 ee4d95e..dbb692c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -95,7 +95,7 @@
 
     TaskResourceLoadQueue mLoadQueue;
     TaskKeyLruCache<Drawable> mIconCache;
-    TaskKeyLruCache<Bitmap> mThumbnailCache;
+    TaskKeyLruCache<ThumbnailData> mThumbnailCache;
     Bitmap mDefaultThumbnail;
     BitmapDrawable mDefaultIcon;
 
@@ -104,7 +104,7 @@
 
     /** Constructor, creates a new loading thread that loads task resources in the background */
     public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
-            TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<Bitmap> thumbnailCache,
+            TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<ThumbnailData> thumbnailCache,
             Bitmap defaultThumbnail, BitmapDrawable defaultIcon) {
         mLoadQueue = loadQueue;
         mIconCache = iconCache;
@@ -165,7 +165,7 @@
                     final Task t = mLoadQueue.nextTask();
                     if (t != null) {
                         Drawable cachedIcon = mIconCache.get(t.key);
-                        Bitmap cachedThumbnail = mThumbnailCache.get(t.key);
+                        ThumbnailData cachedThumbnailData = mThumbnailCache.get(t.key);
 
                         // Load the icon if it is stale or we haven't cached one yet
                         if (cachedIcon == null) {
@@ -190,30 +190,32 @@
                             mIconCache.put(t.key, cachedIcon);
                         }
                         // Load the thumbnail if it is stale or we haven't cached one yet
-                        if (cachedThumbnail == null) {
+                        if (cachedThumbnailData == null) {
                             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
                                 if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-                                cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
+                                cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
                             }
-                            if (cachedThumbnail == null) {
-                                cachedThumbnail = mDefaultThumbnail;
+
+                            if (cachedThumbnailData.thumbnail == null) {
+                                cachedThumbnailData.thumbnail = mDefaultThumbnail;
                             }
+
                             // When svelte, we trim the memory to just the visible thumbnails when
                             // leaving, so don't thrash the cache as the user scrolls (just load
                             // them from scratch each time)
                             if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
-                                mThumbnailCache.put(t.key, cachedThumbnail);
+                                mThumbnailCache.put(t.key, cachedThumbnailData);
                             }
                         }
                         if (!mCancelled) {
                             // Notify that the task data has changed
                             final Drawable newIcon = cachedIcon;
-                            final Bitmap newThumbnail = cachedThumbnail == mDefaultThumbnail
-                                    ? null : cachedThumbnail;
+                            final ThumbnailData newThumbnailData = cachedThumbnailData;
                             mMainThreadHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
-                                    t.notifyTaskDataLoaded(newThumbnail, newIcon);
+                                    t.notifyTaskDataLoaded(newThumbnailData.thumbnail, newIcon,
+                                            newThumbnailData.thumbnailInfo);
                                 }
                             });
                         }
@@ -252,7 +254,7 @@
     // package in the cache has been updated, so that we may remove it.
     private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
     private final TaskKeyLruCache<Drawable> mIconCache;
-    private final TaskKeyLruCache<Bitmap> mThumbnailCache;
+    private final TaskKeyLruCache<ThumbnailData> mThumbnailCache;
     private final TaskKeyLruCache<String> mActivityLabelCache;
     private final TaskKeyLruCache<String> mContentDescriptionCache;
     private final TaskResourceLoadQueue mLoadQueue;
@@ -356,9 +358,16 @@
      */
     public void loadTaskData(Task t, boolean fetchAndInvalidateThumbnails) {
         Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
-        Bitmap thumbnail = mDefaultThumbnail;
+        Bitmap thumbnail = null;
+        ActivityManager.TaskThumbnailInfo thumbnailInfo = null;
         if (fetchAndInvalidateThumbnails) {
-            thumbnail = mThumbnailCache.getAndInvalidateIfModified(t.key);
+            ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(t.key);
+            if (thumbnailData != null) {
+                thumbnail = thumbnailData.thumbnail;
+                thumbnailInfo = thumbnailData.thumbnailInfo;
+            }
+        } else {
+            thumbnail = mDefaultThumbnail;
         }
 
         // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
@@ -368,7 +377,8 @@
         if (requiresLoad) {
             mLoadQueue.addTask(t);
         }
-        t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, icon);
+        t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, icon,
+                thumbnailInfo);
     }
 
     /** Releases the task resource data back into the pool. */
@@ -535,19 +545,19 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Return the cached thumbnail if it exists
-        Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (thumbnail != null) {
-            return thumbnail;
+        ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+        if (thumbnailData != null) {
+            return thumbnailData.thumbnail;
         }
 
         if (loadIfNotCached) {
             RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
                 // Load the thumbnail from the system
-                thumbnail = ssp.getTaskThumbnail(taskKey.id);
-                if (thumbnail != null) {
-                    mThumbnailCache.put(taskKey, thumbnail);
-                    return thumbnail;
+                thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+                if (thumbnailData.thumbnail != null) {
+                    mThumbnailCache.put(taskKey, thumbnailData);
+                    return thumbnailData.thumbnail;
                 }
             }
         }
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 b5a5949..d5d5aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -40,7 +40,7 @@
     /* Task callbacks */
     public interface TaskCallbacks {
         /* Notifies when a task has been bound */
-        public void onTaskDataLoaded(Task task);
+        public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo);
         /* Notifies when a task has been unbound */
         public void onTaskDataUnloaded();
         /* Notifies when a task's stack id has changed. */
@@ -161,7 +161,7 @@
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isLaunchTarget;
     @ViewDebug.ExportedProperty(category="recents")
-    public boolean isHistorical;
+    public boolean isStackTask;
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isSystemApp;
     @ViewDebug.ExportedProperty(category="recents")
@@ -176,7 +176,7 @@
     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
                 Bitmap thumbnail, String title, String contentDescription,
                 String dismissDescription, int colorPrimary, int colorBackground,
-                boolean isHistorical, boolean isLaunchTarget, boolean isSystemApp,
+                boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
                 boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
@@ -195,7 +195,7 @@
         this.bounds = bounds;
         this.taskDescription = taskDescription;
         this.isLaunchTarget = isLaunchTarget;
-        this.isHistorical = isHistorical;
+        this.isStackTask = isStackTask;
         this.isSystemApp = isSystemApp;
         this.isDockable = isDockable;
     }
@@ -217,8 +217,9 @@
         this.colorBackground = o.colorBackground;
         this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
         this.bounds = o.bounds;
+        this.taskDescription = o.taskDescription;
         this.isLaunchTarget = o.isLaunchTarget;
-        this.isHistorical = o.isHistorical;
+        this.isStackTask = o.isStackTask;
         this.isSystemApp = o.isSystemApp;
         this.isDockable = o.isDockable;
     }
@@ -264,12 +265,13 @@
     }
 
     /** Notifies the callback listeners that this task has been loaded */
-    public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
+    public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon,
+            ActivityManager.TaskThumbnailInfo thumbnailInfo) {
         this.icon = applicationIcon;
         this.thumbnail = thumbnail;
         int callbackCount = mCallbacks.size();
         for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskDataLoaded(this);
+            mCallbacks.get(i).onTaskDataLoaded(this, thumbnailInfo);
         }
     }
 
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 193bfff..a930791 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,6 +16,16 @@
 
 package com.android.systemui.recents.model;
 
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -37,13 +47,13 @@
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.misc.NamedCounter;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.views.DropTarget;
 import com.android.systemui.recents.views.AnimationProps;
+import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -51,16 +61,6 @@
 import java.util.List;
 import java.util.Random;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
 
 /**
  * An interface for a task filter to query whether a particular task should show in a stack.
@@ -220,11 +220,6 @@
          */
         void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
             Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture);
-
-        /**
-         * Notifies when a task has been removed from the history.
-         */
-        void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation);
     }
 
     /**
@@ -375,29 +370,24 @@
          * {@param height}.
          */
         public Rect getDockedTaskStackBounds(int width, int height, int dividerSize, Rect insets,
-                Resources res) {
-            RecentsConfiguration config = Recents.getConfiguration();
-
+                TaskStackLayoutAlgorithm layoutAlgorithm, Resources res, Rect windowRectOut) {
             // Calculate the inverse docked task bounds
             boolean isHorizontalDivision =
                     res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
             int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
                     insets, width, height, dividerSize);
-            Rect newWindowBounds = new Rect();
             DockedDividerUtils.calculateBoundsForPosition(position,
-                    DockedDividerUtils.invertDockSide(dockSide), newWindowBounds, width, height,
+                    DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
                     dividerSize);
 
             // Calculate the task stack bounds from the new window bounds
-            Rect searchBarSpaceBounds = new Rect();
             Rect taskStackBounds = new Rect();
             // If the task stack bounds is specifically under the dock area, then ignore the top
             // inset
             int top = dockArea.bottom < 1f
                     ? 0
                     : insets.top;
-            config.getTaskStackBounds(newWindowBounds, top, insets.right,
-                    searchBarSpaceBounds, taskStackBounds);
+            layoutAlgorithm.getTaskStackBounds(windowRectOut, top, insets.right, taskStackBounds);
             return taskStackBounds;
         }
     }
@@ -429,7 +419,6 @@
 
     ArrayList<Task> mRawTaskList = new ArrayList<>();
     FilteredTaskList mStackTaskList = new FilteredTaskList();
-    FilteredTaskList mHistoryTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
 
     ArrayList<TaskGrouping> mGroups = new ArrayList<>();
@@ -440,29 +429,17 @@
         mStackTaskList.setFilter(new TaskFilter() {
             @Override
             public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
-                if (t.isAffiliatedTask()) {
-                    // If this task is affiliated with another parent in the stack, then the
-                    // historical state of this task depends on the state of the parent task
-                    Task parentTask = taskIdMap.get(t.affiliationTaskId);
-                    if (parentTask != null) {
-                        t = parentTask;
+                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
+                    if (t.isAffiliatedTask()) {
+                        // If this task is affiliated with another parent in the stack, then the
+                        // historical state of this task depends on the state of the parent task
+                        Task parentTask = taskIdMap.get(t.affiliationTaskId);
+                        if (parentTask != null) {
+                            t = parentTask;
+                        }
                     }
                 }
-                return !t.isHistorical;
-            }
-        });
-        mHistoryTaskList.setFilter(new TaskFilter() {
-            @Override
-            public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
-                if (t.isAffiliatedTask()) {
-                    // If this task is affiliated with another parent in the stack, then the
-                    // historical state of this task depends on the state of the parent task
-                    Task parentTask = taskIdMap.get(t.affiliationTaskId);
-                    if (parentTask != null) {
-                        t = parentTask;
-                    }
-                }
-                return t.isHistorical;
+                return t.isStackTask;
             }
         });
     }
@@ -523,12 +500,6 @@
                 mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask, animation,
                         fromDockGesture);
             }
-        } else if (mHistoryTaskList.contains(t)) {
-            removeTaskImpl(mHistoryTaskList, t);
-            if (mCb != null) {
-                // Notify that a task has been removed
-                mCb.onHistoryTaskRemoved(this, t, animation);
-            }
         }
         mRawTaskList.remove(t);
     }
@@ -586,21 +557,7 @@
         // Sort all the tasks to ensure they are ordered correctly
         Collections.sort(allTasks, FREEFORM_LAST_ACTIVE_TIME_COMPARATOR);
 
-        // Filter out the historical tasks from this new list
-        ArrayList<Task> stackTasks = new ArrayList<>();
-        ArrayList<Task> historyTasks = new ArrayList<>();
-        int newTaskCount = allTasks.size();
-        for (int i = 0; i < newTaskCount; i++) {
-            Task task = allTasks.get(i);
-            if (task.isHistorical) {
-                historyTasks.add(task);
-            } else {
-                stackTasks.add(task);
-            }
-        }
-
-        mStackTaskList.set(stackTasks);
-        mHistoryTaskList.set(historyTasks);
+        mStackTaskList.set(allTasks);
         mRawTaskList = allTasks;
 
         // Only callback for the newly added tasks after this stack has been updated
@@ -650,14 +607,6 @@
     }
 
     /**
-     * Returns the set of tasks that are inactive. These tasks will be presented in a separate
-     * history view.
-     */
-    public ArrayList<Task> getHistoricalTasks() {
-        return mHistoryTaskList.getTasks();
-    }
-
-    /**
      * Returns the set of "freeform" tasks in the stack.
      */
     public ArrayList<Task> getFreeformTasks() {
@@ -679,7 +628,6 @@
     public ArrayList<Task> computeAllTasksList() {
         ArrayList<Task> tasks = new ArrayList<>();
         tasks.addAll(mStackTaskList.getTasks());
-        tasks.addAll(mHistoryTaskList.getTasks());
         Collections.sort(tasks, LAST_ACTIVE_TIME_COMPARATOR);
         return tasks;
     }
@@ -927,10 +875,6 @@
         for (Task t : mStackTaskList.getTasks()) {
             str += "    " + t.toString() + "\n";
         }
-        str += "Historical Tasks(" + mHistoryTaskList.size() + "):\n";
-        for (Task t : mHistoryTaskList.getTasks()) {
-            str += "    " + t.toString() + "\n";
-        }
         return str;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleHistoryEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index aaf77af..d0cdae5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleHistoryEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.events.activity;
+package com.android.systemui.recents.model;
 
-import com.android.systemui.recents.events.EventBus;
+import android.app.ActivityManager;
+import android.graphics.Bitmap;
 
 /**
- * This is sent when the history view button is clicked.
+ * Data for a single thumbnail.
  */
-public class ToggleHistoryEvent extends EventBus.AnimatedEvent {
-
-    // Simple event
-
+public class ThumbnailData {
+    public Bitmap thumbnail;
+    public ActivityManager.TaskThumbnailInfo thumbnailInfo;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index 960bd8c..2c34523 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.recents.tv;
 
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.content.Intent;
@@ -57,6 +59,7 @@
 import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.tv.pip.PipManager;
+import com.android.systemui.tv.pip.PipControlsView;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -77,8 +80,10 @@
     private boolean mIgnoreAltTabRelease;
 
     private RecentsTvView mRecentsView;
-    private View mPipView;
+    private PipControlsView mPipControlsView;
     private View mPipShadeView;
+    private AnimatorSet mPipControlsViewFadeInAnimator;
+    private AnimatorSet mPipControlsViewFadeOutAnimator;
     private TaskStackHorizontalViewAdapter mTaskStackViewAdapter;
     private FinishRecentsRunnable mFinishLaunchHomeRunnable;
 
@@ -95,7 +100,9 @@
         }
 
         @Override
-        public void onShowPipMenu() { }
+        public void onShowPipMenu() {
+            updatePipUI();
+        }
 
         @Override
         public void onMoveToFullscreen() { }
@@ -106,7 +113,6 @@
         @Override
         public void onMediaControllerChanged() { }
     };
-    private boolean mHasPip;
 
     /**
      * A common Runnable to finish Recents by launching Home with an animation depending on the
@@ -126,15 +132,8 @@
         @Override
         public void run() {
             try {
-                RecentsActivityLaunchState launchState =
-                        Recents.getConfiguration().getLaunchState();
                 ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsTvActivity.this,
-                        launchState.launchedFromSearchHome ?
-                                R.anim.recents_to_search_launcher_enter :
-                                R.anim.recents_to_launcher_enter,
-                        launchState.launchedFromSearchHome ?
-                                R.anim.recents_to_search_launcher_exit :
-                                R.anim.recents_to_launcher_exit);
+                        R.anim.recents_to_launcher_enter, R.anim.recents_to_launcher_exit);
                 startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
             } catch (Exception e) {
                 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
@@ -260,8 +259,22 @@
         mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-        mPipView = findViewById(R.id.pip);
+        mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls);
+        mPipControlsView.setListener(new PipControlsView.Listener() {
+            @Override
+            public void onClosed() {
+                dismissRecentsToLaunchTargetTaskOrHome();
+            }
+        });
         mPipShadeView = findViewById(R.id.pip_shade);
+
+        mPipControlsViewFadeInAnimator = (AnimatorSet) AnimatorInflater.loadAnimator(this,
+                R.anim.tv_pip_controls_fade_in);
+        mPipControlsViewFadeInAnimator.setTarget(mPipControlsView);
+        mPipControlsViewFadeOutAnimator = (AnimatorSet) AnimatorInflater.loadAnimator(this,
+                R.anim.tv_pip_controls_fade_out);
+        mPipControlsViewFadeOutAnimator.setTarget(mPipControlsView);
+
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 
@@ -272,7 +285,6 @@
                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
 
-        mHasPip = false;
         updatePipUI();
         mPipManager.addListener(mPipListener);
     }
@@ -463,37 +475,25 @@
     }
 
     private void updatePipUI() {
-        if (mHasPip == mPipManager.isPipShown()) {
-            return;
-        }
-        mHasPip = mPipManager.isPipShown();
-        if (mHasPip) {
-            // Place mPipView at the PIP bounds for fine tuned focus handling.
-            Rect pipBounds = mPipManager.getPipBounds();
-            LayoutParams lp = (LayoutParams) mPipView.getLayoutParams();
-            lp.width = pipBounds.width();
-            lp.height = pipBounds.height();
-            lp.leftMargin = pipBounds.left;
-            lp.topMargin = pipBounds.top;
-            mPipView.setLayoutParams(lp);
-
-            mPipView.setVisibility(View.VISIBLE);
-            mPipView.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    mPipManager.resizePinnedStack(PipManager.STATE_PIP_MENU);
-                }
-            });
-            mPipView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+        if (mPipManager.isPipShown()) {
+            mPipControlsView.setAlpha(0);
+            mPipControlsView.setVisibility(View.VISIBLE);
+            mPipShadeView.setVisibility(View.INVISIBLE);
+            mPipControlsView.setOnChildFocusChangeListener(new View.OnFocusChangeListener() {
                 @Override
                 public void onFocusChange(View v, boolean hasFocus) {
                     mPipManager.onPipViewFocusChangedInRecents(hasFocus);
+                    if (hasFocus) {
+                        mPipControlsViewFadeInAnimator.start();
+                    } else {
+                        mPipControlsViewFadeOutAnimator.start();
+                    }
                     mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
                 }
             });
             mPipShadeView.setVisibility(View.GONE);
         } else {
-            mPipView.setVisibility(View.GONE);
+            mPipControlsView.setVisibility(View.GONE);
             mPipShadeView.setVisibility(View.GONE);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
index 9fd5d55..aa27325 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
@@ -81,8 +81,7 @@
             // If there is no thumbnail transition, but is launching from home into recents, then
             // use a quick home transition and do the animation from home
             if (hasRecentTasks) {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                ActivityOptions opts = getHomeTransitionActivityOptions(false);
+                ActivityOptions opts = getHomeTransitionActivityOptions();
                 startRecentsActivity(topTask, opts, true /* fromHome */, false /* fromThumbnail */);
             } else {
                 // Otherwise we do the normal fade from an unknown source
@@ -99,7 +98,6 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         launchState.launchedFromHome = fromHome;
-        launchState.launchedFromSearchHome = false;
         launchState.launchedFromApp = fromThumbnail;
         launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
         launchState.launchedWithAltTab = mTriggeredFromAltTab;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
index bf6229c..3d5176f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
@@ -104,28 +104,6 @@
         requestLayout();
     }
 
-    public Task getNextTaskOrTopTask(Task taskToSearch) {
-        Task returnTask = null;
-        boolean found = false;
-        if (mTaskStackHorizontalView != null) {
-            TaskStack stack = mTaskStackHorizontalView.getStack();
-            ArrayList<Task> taskList = stack.getStackTasks();
-            // Iterate the stack views and try and find the focused task
-            for (int j = taskList.size() - 1; j >= 0; --j) {
-                Task task = taskList.get(j);
-                // Return the next task in the line.
-                if (found)
-                    return task;
-                // Remember the first possible task as the top task.
-                if (returnTask == null)
-                    returnTask = task;
-                if (task == taskToSearch)
-                    found = true;
-            }
-        }
-        return returnTask;
-    }
-
     public boolean launchFocusedTask() {
         if (mTaskStackHorizontalView != null) {
             Task task = mTaskStackHorizontalView.getFocusedTask();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index 5c2de8e..22ade9f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -156,9 +156,4 @@
             }
         }
     }
-
-    @Override
-    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation) {
-        //No history task on tv
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 72b914c..035c058 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -46,7 +46,7 @@
     public void reloadOnConfigurationChange(Context context) {
         // This is applied to the edges of each task
         mTaskPadding = context.getResources().getDimensionPixelSize(
-                R.dimen.recents_freeform_workspace_task_padding) / 2;
+                R.dimen.recents_freeform_layout_task_padding) / 2;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index a91bbd4..29da476 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.recents.views;
 
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
@@ -49,11 +54,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 /**
  * A helper class to create transitions to/from Recents
  */
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 db97e8f..70c4c81 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -48,19 +48,14 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.ClearHistoryEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
-import com.android.systemui.recents.events.activity.HideHistoryEvent;
+import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
-import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
-import com.android.systemui.recents.events.activity.ShowHistoryEvent;
-import com.android.systemui.recents.events.activity.ToggleHistoryEvent;
+import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
 import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent;
@@ -68,7 +63,6 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.history.RecentsHistoryView;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
@@ -94,11 +88,8 @@
 
     private TaskStack mStack;
     private TaskStackView mTaskStackView;
-    private RecentsAppWidgetHostView mSearchBar;
-    private TextView mHistoryButton;
-    private TextView mHistoryClearAllButton;
+    private TextView mStackActionButton;
     private TextView mEmptyView;
-    private RecentsHistoryView mHistoryView;
 
     private boolean mAwaitingFirstLayout = true;
     private boolean mLastTaskLaunchedWasFreeform;
@@ -141,18 +132,18 @@
         final float cornerRadius = context.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_view_rounded_corners_radius);
         LayoutInflater inflater = LayoutInflater.from(context);
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            mHistoryButton = (TextView) inflater.inflate(R.layout.recents_history_button, this,
+        if (RecentsDebugFlags.Static.EnableStackActionButton) {
+            mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button, this,
                     false);
-            mHistoryButton.setOnClickListener(new View.OnClickListener() {
+            mStackActionButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    EventBus.getDefault().send(new ToggleHistoryEvent());
+                    // TODO: To be implemented
                 }
             });
-            addView(mHistoryButton);
-            mHistoryButton.setClipToOutline(true);
-            mHistoryButton.setOutlineProvider(new ViewOutlineProvider() {
+            addView(mStackActionButton);
+            mStackActionButton.setClipToOutline(true);
+            mStackActionButton.setOutlineProvider(new ViewOutlineProvider() {
                 @Override
                 public void getOutline(View view, Outline outline) {
                     outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
@@ -166,14 +157,17 @@
     }
 
     /** Set/get the bsp root node */
-    public void onResume(boolean isResumingFromVisible, TaskStack stack) {
+    public void onResume(boolean isResumingFromVisible, boolean multiWindowChange,
+            TaskStack stack) {
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
 
-        if (mTaskStackView == null || !launchState.launchedReuseTaskStackViews) {
+        if (!multiWindowChange &&
+                (mTaskStackView == null || !launchState.launchedReuseTaskStackViews)) {
             isResumingFromVisible = false;
             removeView(mTaskStackView);
             mTaskStackView = new TaskStackView(getContext());
+            mTaskStackView.setSystemInsets(mSystemInsets);
             mStack = mTaskStackView.getStack();
             addView(mTaskStackView);
         }
@@ -185,7 +179,7 @@
         // Update the stack
         mTaskStackView.onResume(isResumingFromVisible);
         mTaskStackView.setTasks(stack, isResumingFromVisible /* notifyStackChanges */,
-                true /* relayoutTaskStack */);
+                true /* relayoutTaskStack */, multiWindowChange);
 
         if (isResumingFromVisible) {
             // If we are already visible, then restore the background scrim
@@ -217,43 +211,6 @@
         return mLastTaskLaunchedWasFreeform;
     }
 
-    /**
-     * Returns whether the history is visible or not.
-     */
-    public boolean isHistoryVisible() {
-        return mHistoryView != null && mHistoryView.isVisible();
-    }
-
-    /**
-     * Returns the currently set task stack.
-     */
-    public TaskStack getTaskStack() {
-        return mStack;
-    }
-
-    /** Gets the next task in the stack - or if the last - the top task */
-    public Task getNextTaskOrTopTask(Task taskToSearch) {
-        Task returnTask = null;
-        boolean found = false;
-        if (mTaskStackView != null) {
-            TaskStack stack = mTaskStackView.getStack();
-            ArrayList<Task> taskList = stack.getStackTasks();
-            // Iterate the stack views and try and find the focused task
-            for (int j = taskList.size() - 1; j >= 0; --j) {
-                Task task = taskList.get(j);
-                // Return the next task in the line.
-                if (found)
-                    return task;
-                // Remember the first possible task as the top task.
-                if (returnTask == null)
-                    returnTask = task;
-                if (task == taskToSearch)
-                    found = true;
-            }
-        }
-        return returnTask;
-    }
-
     /** Launches the focused task from the first stack if possible */
     public boolean launchFocusedTask(int logEvent) {
         if (mTaskStackView != null) {
@@ -306,37 +263,16 @@
         return false;
     }
 
-    /** Adds the search bar */
-    public void setSearchBar(RecentsAppWidgetHostView searchBar) {
-        // Remove the previous search bar if one exists
-        if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
-            removeView(mSearchBar);
-        }
-        // Add the new search bar
-        if (searchBar != null) {
-            mSearchBar = searchBar;
-            addView(mSearchBar);
-        }
-    }
-
-    /** Returns whether there is currently a search bar */
-    public boolean hasValidSearchBar() {
-        return mSearchBar != null && !mSearchBar.isReinflateRequired();
-    }
-
     /**
      * Hides the task stack and shows the empty view.
      */
     public void showEmptyView(int msgResId) {
-        if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) {
-            mSearchBar.setVisibility(View.INVISIBLE);
-        }
         mTaskStackView.setVisibility(View.INVISIBLE);
         mEmptyView.setText(msgResId);
         mEmptyView.setVisibility(View.VISIBLE);
         mEmptyView.bringToFront();
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            mHistoryButton.bringToFront();
+        if (RecentsDebugFlags.Static.EnableStackActionButton) {
+            mStackActionButton.bringToFront();
         }
     }
 
@@ -346,15 +282,9 @@
     public void hideEmptyView() {
         mEmptyView.setVisibility(View.INVISIBLE);
         mTaskStackView.setVisibility(View.VISIBLE);
-        if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) {
-            mSearchBar.setVisibility(View.VISIBLE);
-        }
         mTaskStackView.bringToFront();
-        if (mSearchBar != null) {
-            mSearchBar.bringToFront();
-        }
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            mHistoryButton.bringToFront();
+        if (RecentsDebugFlags.Static.EnableStackActionButton) {
+            mStackActionButton.bringToFront();
         }
     }
 
@@ -377,25 +307,10 @@
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        RecentsConfiguration config = Recents.getConfiguration();
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        // Get the search bar bounds and measure the search bar layout
-        Rect searchBarSpaceBounds = new Rect();
-        if (mSearchBar != null) {
-            config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
-                    searchBarSpaceBounds);
-            mSearchBar.measure(
-                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
-        }
-
-        Rect taskStackBounds = new Rect();
-        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
-                mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
-        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
-            mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
+        if (mTaskStackView.getVisibility() != GONE) {
             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
         }
 
@@ -405,23 +320,12 @@
                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
         }
 
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            // Measure the history view
-            if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
-                measureChild(mHistoryView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-            }
-
-            // Measure the history button within the constraints of the space above the stack
-            Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
-            measureChild(mHistoryButton,
-                    MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
-            if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
-                measureChild(mHistoryClearAllButton,
-                    MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
-            }
+        if (RecentsDebugFlags.Static.EnableStackActionButton) {
+            // Measure the stack action button within the constraints of the space above the stack
+            Rect actionButtonRect = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
+            measureChild(mStackActionButton,
+                    MeasureSpec.makeMeasureSpec(actionButtonRect.width(), MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(actionButtonRect.height(), MeasureSpec.AT_MOST));
         }
 
         setMeasuredDimension(width, height);
@@ -432,19 +336,7 @@
      */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        RecentsConfiguration config = Recents.getConfiguration();
-
-        // Get the search bar bounds so that we lay it out
-        Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
-        Rect searchBarSpaceBounds = new Rect();
-        if (mSearchBar != null) {
-            config.getSearchBarBounds(measuredRect,
-                    mSystemInsets.top, searchBarSpaceBounds);
-            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
-                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
-        }
-
-        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
+        if (mTaskStackView.getVisibility() != GONE) {
             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
         }
 
@@ -459,39 +351,19 @@
             mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
         }
 
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            // Layout the history view
-            if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
-                mHistoryView.layout(left, top, right, bottom);
-            }
-
-            // Layout the history button such that its drawable is start-aligned with the stack,
-            // vertically centered in the available space above the stack
-            Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
-            int historyLeft = isLayoutRtl()
-                    ? historyButtonRect.right + mHistoryButton.getPaddingStart()
-                    - mHistoryButton.getMeasuredWidth()
-                    : historyButtonRect.left - mHistoryButton.getPaddingStart();
-            int historyTop = historyButtonRect.top +
-                    (historyButtonRect.height() - mHistoryButton.getMeasuredHeight()) / 2;
-            mHistoryButton.layout(historyLeft, historyTop,
-                    historyLeft + mHistoryButton.getMeasuredWidth(),
-                    historyTop + mHistoryButton.getMeasuredHeight());
-
-            // Layout the history clear all button such that it is end-aligned with the stack,
-            // vertically centered in the available space above the stack
-            if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
-                int clearAllLeft = isLayoutRtl()
-                        ? historyButtonRect.left - mHistoryClearAllButton.getPaddingStart()
-                        : historyButtonRect.right + mHistoryClearAllButton.getPaddingStart()
-                        - mHistoryClearAllButton.getMeasuredWidth();
-                int clearAllTop = historyButtonRect.top +
-                        (historyButtonRect.height() - mHistoryClearAllButton.getMeasuredHeight()) /
-                                2;
-                mHistoryClearAllButton.layout(clearAllLeft, clearAllTop,
-                        clearAllLeft + mHistoryClearAllButton.getMeasuredWidth(),
-                        clearAllTop + mHistoryClearAllButton.getMeasuredHeight());
-            }
+        if (RecentsDebugFlags.Static.EnableStackActionButton) {
+            // Layout the stack action button such that its drawable is start-aligned with the
+            // stack, vertically centered in the available space above the stack
+            Rect actionButtonRect = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
+            int buttonLeft = isLayoutRtl()
+                    ? actionButtonRect.right + mStackActionButton.getPaddingStart()
+                    - mStackActionButton.getMeasuredWidth()
+                    : actionButtonRect.left - mStackActionButton.getPaddingStart();
+            int buttonTop = actionButtonRect.top +
+                    (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
+            mStackActionButton.layout(buttonLeft, buttonTop,
+                    buttonLeft + mStackActionButton.getMeasuredWidth(),
+                    buttonTop + mStackActionButton.getMeasuredHeight());
         }
 
         if (mAwaitingFirstLayout) {
@@ -511,6 +383,7 @@
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mSystemInsets.set(insets.getSystemWindowInsets());
+        mTaskStackView.setSystemInsets(mSystemInsets);
         requestLayout();
         return insets;
     }
@@ -560,9 +433,9 @@
 
     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
-        if (RecentsDebugFlags.Static.EnableHistory) {
-            // Hide the history button
-            hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
+        if (RecentsDebugFlags.Static.EnableStackActionButton) {
+            // Hide the stack action button
+            hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
         }
         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
     }
@@ -687,144 +560,47 @@
         animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION);
     }
 
-    public final void onBusEvent(ToggleHistoryEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
+    public final void onBusEvent(ShowStackActionButtonEvent event) {
+        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
             return;
         }
 
-        if (mHistoryView != null && mHistoryView.isVisible()) {
-            EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
-        } else {
-            EventBus.getDefault().send(new ShowHistoryEvent());
-        }
+        showStackActionButton(150, event.translate);
     }
 
-    public final void onBusEvent(ShowHistoryEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
+    public final void onBusEvent(HideStackActionButtonEvent event) {
+        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
             return;
         }
 
-        if (mHistoryView == null) {
-            LayoutInflater inflater = LayoutInflater.from(getContext());
-            mHistoryView = (RecentsHistoryView) inflater.inflate(R.layout.recents_history, this,
-                    false);
-            addView(mHistoryView);
-
-            final float cornerRadius = getResources().getDimensionPixelSize(
-                    R.dimen.recents_task_view_rounded_corners_radius);
-            mHistoryClearAllButton = (TextView) inflater.inflate(
-                    R.layout.recents_history_clear_all_button, this, false);
-            mHistoryClearAllButton.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    EventBus.getDefault().send(new ClearHistoryEvent());
-                }
-            });
-            mHistoryClearAllButton.setClipToOutline(true);
-            mHistoryClearAllButton.setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
-                }
-            });
-            addView(mHistoryClearAllButton);
-
-            // Since this history view is inflated by a view stub after the insets have already
-            // been applied, we have to set them ourselves initial from the insets that were last
-            // provided.
-            mHistoryView.setSystemInsets(mSystemInsets);
-            mHistoryView.setHeaderHeight(mHistoryButton.getMeasuredHeight());
-            mHistoryButton.bringToFront();
-            mHistoryClearAllButton.bringToFront();
-        }
-
-        // Animate the empty view in parallel with the history view (the task view animations are
-        // handled in TaskStackView)
-        Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect;
-        if (mEmptyView.getVisibility() == View.VISIBLE) {
-            int historyTransitionDuration = getResources().getInteger(
-                    R.integer.recents_history_transition_duration);
-            mEmptyView.animate()
-                    .alpha(0f)
-                    .translationY(stackRect.height() / 2)
-                    .setDuration(historyTransitionDuration)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mEmptyView.setVisibility(View.INVISIBLE);
-                        }
-                    })
-                    .start();
-        }
-
-        mHistoryView.show(mStack, stackRect.height(), mHistoryClearAllButton);
-    }
-
-    public final void onBusEvent(HideHistoryEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
-            return;
-        }
-
-        // Animate the empty view in parallel with the history view (the task view animations are
-        // handled in TaskStackView)
-        Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect;
-        if (mStack.getTaskCount() == 0) {
-            int historyTransitionDuration = getResources().getInteger(
-                    R.integer.recents_history_transition_duration);
-            mEmptyView.setVisibility(View.VISIBLE);
-            mEmptyView.animate()
-                    .alpha(1f)
-                    .translationY(0)
-                    .setDuration(historyTransitionDuration)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .start();
-        }
-
-        mHistoryView.hide(event.animate, stackRect.height(), mHistoryClearAllButton);
-    }
-
-    public final void onBusEvent(ShowHistoryButtonEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
-            return;
-        }
-
-        showHistoryButton(150, event.translate);
-    }
-
-    public final void onBusEvent(HideHistoryButtonEvent event) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
-            return;
-        }
-
-        hideHistoryButton(100, true /* translate */);
+        hideStackActionButton(100, true /* translate */);
     }
 
     /**
-     * Shows the history button.
+     * Shows the stack action button.
      */
-    private void showHistoryButton(final int duration, final boolean translate) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
+    private void showStackActionButton(final int duration, final boolean translate) {
+        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
             return;
         }
 
         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
-        if (mHistoryButton.getVisibility() == View.INVISIBLE) {
-            mHistoryButton.setVisibility(View.VISIBLE);
-            mHistoryButton.setAlpha(0f);
+        if (mStackActionButton.getVisibility() == View.INVISIBLE) {
+            mStackActionButton.setVisibility(View.VISIBLE);
+            mStackActionButton.setAlpha(0f);
             if (translate) {
-                mHistoryButton.setTranslationY(-mHistoryButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
             } else {
-                mHistoryButton.setTranslationY(0f);
+                mStackActionButton.setTranslationY(0f);
             }
             postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
                 public void run() {
                     if (translate) {
-                        mHistoryButton.animate()
+                        mStackActionButton.animate()
                             .translationY(0f);
                     }
-                    mHistoryButton.animate()
+                    mStackActionButton.animate()
                             .alpha(1f)
                             .setDuration(duration)
                             .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
@@ -837,40 +613,40 @@
     }
 
     /**
-     * Hides the history button.
+     * Hides the stack action button.
      */
-    private void hideHistoryButton(int duration, boolean translate) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
+    private void hideStackActionButton(int duration, boolean translate) {
+        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
             return;
         }
 
         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
-        hideHistoryButton(duration, translate, postAnimationTrigger);
+        hideStackActionButton(duration, translate, postAnimationTrigger);
         postAnimationTrigger.flushLastDecrementRunnables();
     }
 
     /**
-     * Hides the history button.
+     * Hides the stack action button.
      */
-    private void hideHistoryButton(int duration, boolean translate,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        if (!RecentsDebugFlags.Static.EnableHistory) {
+    private void hideStackActionButton(int duration, boolean translate,
+                                       final ReferenceCountedTrigger postAnimationTrigger) {
+        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
             return;
         }
 
-        if (mHistoryButton.getVisibility() == View.VISIBLE) {
+        if (mStackActionButton.getVisibility() == View.VISIBLE) {
             if (translate) {
-                mHistoryButton.animate()
-                    .translationY(-mHistoryButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.animate()
+                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
             }
-            mHistoryButton.animate()
+            mStackActionButton.animate()
                     .alpha(0f)
                     .setDuration(duration)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
-                            mHistoryButton.setVisibility(View.INVISIBLE);
+                            mStackActionButton.setVisibility(View.INVISIBLE);
                             postAnimationTrigger.decrement();
                         }
                     })
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 19b219a..6bdaaf9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -16,11 +16,9 @@
 
 package com.android.systemui.recents.views;
 
-import android.animation.AnimatorListenerAdapter;
 import android.app.Activity;
 import android.content.Context;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index b36d5d1..4155dd2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -65,6 +65,12 @@
          */
         void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
                 ReferenceCountedTrigger postAnimationTrigger);
+
+        /**
+         * Callback to start the animation for the front {@link TaskView} if there is no launch
+         * target.
+         */
+        void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled);
     }
 
     private static final int FRAME_OFFSET_MS = 16;
@@ -126,9 +132,9 @@
 
         int offscreenYOffset = stackLayout.mStackRect.height();
         int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
-                R.dimen.recents_task_view_affiliate_group_enter_offset);
+                R.dimen.recents_task_stack_animation_affiliate_enter_offset);
         int launchedWhileDockingOffset = res.getDimensionPixelSize(
-                R.dimen.recents_task_view_launched_while_docking_offset);
+                R.dimen.recents_task_stack_animation_launched_while_docking_offset);
 
         // Prepare each of the task views for their enter animation from front to back
         List<TaskView> taskViews = mStackView.getTaskViews();
@@ -164,6 +170,7 @@
                 // Move the task view off screen (below) so we can animate it in
                 RectF bounds = new RectF(mTmpTransform.rect);
                 bounds.offset(0, offscreenYOffset);
+                tv.setAlpha(0f);
                 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right,
                         (int) bounds.bottom);
             } else if (launchState.launchedWhileDocking) {
@@ -232,7 +239,7 @@
                                     @Override
                                     public void onAnimationEnd(Animator animation) {
                                         postAnimationTrigger.decrement();
-                                        tv.setClipViewInStack(false);
+                                        tv.setClipViewInStack(true);
                                     }
                                 });
                         postAnimationTrigger.increment();
@@ -254,6 +261,9 @@
                         .setListener(postAnimationTrigger.decrementOnAnimationEnd());
                 postAnimationTrigger.increment();
                 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
+                if (i == taskViewCount - 1) {
+                    tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled);
+                }
             } else if (launchState.launchedWhileDocking) {
                 // Animate the tasks up
                 AnimationProps taskAnimation = new AnimationProps()
@@ -312,6 +322,7 @@
 
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
                     null);
+            mTmpTransform.alpha = 0f;
             mTmpTransform.rect.offset(0, offscreenYOffset);
             mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
         }
@@ -330,7 +341,7 @@
         int taskViewExitToAppDuration = res.getInteger(
                 R.integer.recents_task_exit_to_app_duration);
         int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
-                R.dimen.recents_task_view_affiliate_group_enter_offset);
+                R.dimen.recents_task_stack_animation_affiliate_enter_offset);
 
         Task launchingTask = launchingTaskView.getTask();
         List<TaskView> taskViews = mStackView.getTaskViews();
@@ -399,62 +410,6 @@
     }
 
     /**
-     * Starts the animation to hide the {@link TaskView}s when the history is shown.
-     */
-    public void startShowHistoryAnimation(ReferenceCountedTrigger postAnimationTrigger) {
-        Resources res = mStackView.getResources();
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
-
-        int offscreenY = stackLayout.mStackRect.bottom;
-        int historyTransitionDuration = res.getInteger(
-                R.integer.recents_history_transition_duration);
-        int startDelayIncr = 16;
-
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
-                    historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN,
-                    postAnimationTrigger.decrementOnAnimationEnd());
-            postAnimationTrigger.increment();
-
-            stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
-            mTmpTransform.alpha = 0f;
-            mTmpTransform.rect.offsetTo(mTmpTransform.rect.left, offscreenY);
-            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-        }
-    }
-
-    /**
-     * Starts the animation to show the {@link TaskView}s when the history is hidden.
-     */
-    public void startHideHistoryAnimation() {
-        Resources res = mStackView.getResources();
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
-
-        int historyTransitionDuration = res.getInteger(
-                R.integer.recents_history_transition_duration);
-        int startDelayIncr = 16;
-
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
-                    historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN);
-            stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(),
-                    mTmpTransform, null);
-            mTmpTransform.alpha = 1f;
-            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-        }
-    }
-
-    /**
      * Starts the animation to focus the next {@link TaskView} when paging through recents.
      *
      * @return whether or not this will trigger a scroll in the stack
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 6df5884..83f8b7e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.recents.views;
 
+import android.annotation.IntDef;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Path;
 import android.graphics.Rect;
@@ -36,6 +38,8 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -97,8 +101,10 @@
 }
 
 /**
- * The layout logic for a TaskStackView.  This layout can have two states focused and unfocused,
- * and in the focused state, there is a task that is displayed more prominently in the stack.
+ * The layout logic for a TaskStackView.  This layout needs to be able to calculate the stack layout
+ * without an activity-specific context only with the information passed in.  This layout can have
+ * two states focused and unfocused, and in the focused state, there is a task that is displayed
+ * more prominently in the stack.
  */
 public class TaskStackLayoutAlgorithm {
 
@@ -107,13 +113,28 @@
     public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
     public static final float OUTLINE_ALPHA_MAX_VALUE = 2f;
 
-    // The maximum dim on the tasks
+    // The medium/maximum dim on the tasks
+    private static final float MED_DIM = 0.15f;
     private static final float MAX_DIM = 0.25f;
 
     // The various focus states
     public static final int STATE_FOCUSED = 1;
     public static final int STATE_UNFOCUSED = 0;
 
+    // The side that an offset is anchored
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({FROM_TOP, FROM_BOTTOM})
+    public @interface AnchorSide {}
+    private static final int FROM_TOP = 0;
+    private static final int FROM_BOTTOM = 1;
+
+    // The extent that we care about when calculating fractions
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({WIDTH, HEIGHT})
+    public @interface Extent {}
+    private static final int WIDTH = 0;
+    private static final int HEIGHT = 1;
+
     public interface TaskStackLayoutAlgorithmCallbacks {
         void onFocusStateChanged(int prevFocusState, int curFocusState);
     }
@@ -165,22 +186,24 @@
          * @param taskStackBounds the full rect that the freeform rect can take up
          */
         public void computeRects(Rect freeformRectOut, Rect stackRectOut,
-                Rect taskStackBounds, int widthPadding, int heightPadding, int stackBottomOffset) {
-            int availableHeight = taskStackBounds.height() - stackBottomOffset;
+                Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
+            // The freeform height is the visible height (not including system insets) - padding
+            // above freeform and below stack - gap between the freeform and stack
+            int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
             int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
-            int ffHeight = Math.max(0, ffPaddedHeight - (2 * heightPadding));
-            freeformRectOut.set(taskStackBounds.left + widthPadding,
-                    taskStackBounds.top + heightPadding,
-                    taskStackBounds.right - widthPadding,
-                    taskStackBounds.top + heightPadding + ffHeight);
-            stackRectOut.set(taskStackBounds.left + widthPadding,
+            int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
+            freeformRectOut.set(taskStackBounds.left,
+                    taskStackBounds.top + topMargin,
+                    taskStackBounds.right,
+                    taskStackBounds.top + topMargin + ffHeight);
+            stackRectOut.set(taskStackBounds.left,
                     taskStackBounds.top,
-                    taskStackBounds.right - widthPadding,
+                    taskStackBounds.right,
                     taskStackBounds.bottom);
             if (ffPaddedHeight > 0) {
                 stackRectOut.top += ffPaddedHeight;
             } else {
-                stackRectOut.top += heightPadding;
+                stackRectOut.top += topMargin;
             }
         }
     }
@@ -204,44 +227,53 @@
     // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mTaskRect = new Rect();
-    // The freeform workspace bounds, inset from the top by the search bar, and is a fixed height
+    // The freeform workspace bounds, inset by the top system insets and is a fixed height
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mFreeformRect = new Rect();
-    // The stack bounds, inset from the top by the search bar, and runs to
-    // the bottom of the screen
+    // The stack bounds, inset from the top system insets, and runs to the bottom of the screen
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mStackRect = new Rect();
     // This is the current system insets
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mSystemInsets = new Rect();
-    // This is the bounds of the history button above the stack rect
+    // This is the bounds of the stack action above the stack rect
     @ViewDebug.ExportedProperty(category="recents")
-    public Rect mHistoryButtonRect = new Rect();
+    public Rect mStackActionButtonRect = new Rect();
 
     // The visible ranges when the stack is focused and unfocused
     private Range mUnfocusedRange;
     private Range mFocusedRange;
 
-    // The initial offset from the top and bottom of the stack
+    // The base top margin for the stack from the system insets
     @ViewDebug.ExportedProperty(category="recents")
-    private int mInitialTopPeekHeight;
+    private int mBaseTopMargin;
+    // The base side margin for the stack from the system insets
     @ViewDebug.ExportedProperty(category="recents")
-    private int mInitialBottomPeekHeight;
+    private int mBaseSideMargin;
+    // The base bottom margin for the stack from the system insets
+    @ViewDebug.ExportedProperty(category="recents")
+    private int mBaseBottomMargin;
+    private int mMinMargin;
 
-    // The offset from the top when scrolled to the top of the stack
+    // The gap between the freeform and stack layouts
+    @ViewDebug.ExportedProperty(category="recents")
+    private int mFreeformStackGap;
+
+    // The initial offset that the focused task is from the top
+    @ViewDebug.ExportedProperty(category="recents")
+    private int mInitialTopOffset;
+    private int mBaseInitialTopOffset;
+    // The initial offset that the launch-from task is from the bottom
+    @ViewDebug.ExportedProperty(category="recents")
+    private int mInitialBottomOffset;
+    private int mBaseInitialBottomOffset;
+
+    // The height between the top margin and the top of the focused task
     @ViewDebug.ExportedProperty(category="recents")
     private int mFocusedTopPeekHeight;
+    // The height between the bottom margin and the top of task in front of the focused task
     @ViewDebug.ExportedProperty(category="recents")
-    private int mFocusedBottomTaskPeekHeight;
-
-    // The offset from the top of the stack to the top of the bounds when the stack is scrolled to
-    // the end
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mStackTopOffset;
-
-    // The height of the header bar
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mHeaderBarHeight;
+    private int mFocusedBottomPeekHeight;
 
     // The offset from the bottom of the stack to the bottom of the bounds when the stack is
     // scrolled to the front
@@ -307,9 +339,23 @@
     TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
 
     public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
+        Resources res = context.getResources();
         mContext = context;
         mCb = cb;
         mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
+        mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
+        mBaseTopMargin = getDimensionForDevice(res,
+                R.dimen.recents_layout_top_margin_phone,
+                R.dimen.recents_layout_top_margin_tablet,
+                R.dimen.recents_layout_top_margin_tablet_xlarge);
+        mBaseSideMargin = getDimensionForDevice(res,
+                R.dimen.recents_layout_side_margin_phone,
+                R.dimen.recents_layout_side_margin_tablet,
+                R.dimen.recents_layout_side_margin_tablet_xlarge);
+        mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
+        mFreeformStackGap =
+                res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
+
         reloadOnConfigurationChange(context);
     }
 
@@ -323,17 +369,25 @@
         mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
                 res.getFloat(R.integer.recents_layout_unfocused_range_max));
         mFocusState = getInitialFocusState();
-        mInitialTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_initial_top_peek_size);
-        mInitialBottomPeekHeight =
-                res.getDimensionPixelSize(R.dimen.recents_initial_bottom_peek_size);
-        mFocusedTopPeekHeight =
-                res.getDimensionPixelSize(R.dimen.recents_layout_focused_top_peek_size);
-        mFocusedBottomTaskPeekHeight =
-                res.getDimensionPixelSize(R.dimen.recents_layout_focused_bottom_task_peek_size);
-        mHeaderBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
-
-        mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
-        mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
+        mFocusedTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_top_peek_size);
+        mFocusedBottomPeekHeight =
+                res.getDimensionPixelSize(R.dimen.recents_layout_bottom_peek_size);
+        mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_min);
+        mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_max);
+        mBaseInitialTopOffset = getDimensionForDevice(res,
+                R.dimen.recents_layout_initial_top_offset_phone_port,
+                R.dimen.recents_layout_initial_top_offset_phone_land,
+                R.dimen.recents_layout_initial_top_offset_tablet,
+                R.dimen.recents_layout_initial_top_offset_tablet,
+                R.dimen.recents_layout_initial_top_offset_tablet,
+                R.dimen.recents_layout_initial_top_offset_tablet);
+        mBaseInitialBottomOffset = getDimensionForDevice(res,
+                R.dimen.recents_layout_initial_bottom_offset_phone_port,
+                R.dimen.recents_layout_initial_bottom_offset_phone_land,
+                R.dimen.recents_layout_initial_bottom_offset_tablet,
+                R.dimen.recents_layout_initial_bottom_offset_tablet,
+                R.dimen.recents_layout_initial_bottom_offset_tablet,
+                R.dimen.recents_layout_initial_bottom_offset_tablet);
         mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
     }
 
@@ -372,52 +426,52 @@
     }
 
     /**
-     * Computes the stack and task rects.  The given task stack bounds is the whole bounds not
-     * including the search bar.
+     * Computes the stack and task rects.  The given task stack bounds already has the top/right
+     * insets and left/right padding already applied.
      */
-    public void initialize(Rect taskStackBounds, StackState state) {
-        RecentsConfiguration config = Recents.getConfiguration();
-        int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
-        int heightPadding = mContext.getResources().getDimensionPixelSize(
-                R.dimen.recents_stack_top_padding);
+    public void initialize(Rect windowRect, Rect taskStackBounds, StackState state) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
         Rect lastStackRect = new Rect(mStackRect);
+        Rect displayRect = ssp.getDisplayRect();
 
-        // The freeform height is the visible height (not including system insets) - padding above
-        // freeform and below stack - gap between the freeform and stack
+        int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
+        int bottomMargin = getScaleForExtent(windowRect, displayRect, mBaseBottomMargin, mMinMargin,
+                HEIGHT);
+        mInitialTopOffset = getScaleForExtent(windowRect, displayRect, mBaseInitialTopOffset,
+                mMinMargin, HEIGHT);
+        mInitialBottomOffset = mBaseInitialBottomOffset;
+
+        // Compute the stack bounds
         mState = state;
-        mStackTopOffset = mFocusedTopPeekHeight + heightPadding;
-        mStackBottomOffset = mSystemInsets.bottom + heightPadding;
-        state.computeRects(mFreeformRect, mStackRect, taskStackBounds, widthPadding, heightPadding,
-                mStackBottomOffset);
-        // The history button will take the full un-padded header space above the stack
-        mHistoryButtonRect.set(mStackRect.left, mStackRect.top - heightPadding,
+        mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
+        state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
+                mFreeformStackGap, mStackBottomOffset);
+
+        // The stack action button will take the full un-padded header space above the stack
+        mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
                 mStackRect.right, mStackRect.top + mFocusedTopPeekHeight);
 
-        // Anchor the task rect to the top-center of the non-freeform stack rect
-        float aspect = (float) (taskStackBounds.width() - mSystemInsets.left - mSystemInsets.right)
-                / (taskStackBounds.height() - mSystemInsets.bottom);
-        int width = mStackRect.width();
-        int minHeight = mStackRect.height() - mFocusedTopPeekHeight - mStackBottomOffset;
-        int height = (int) Math.min(width / aspect, minHeight);
-        mTaskRect.set(mStackRect.left, mStackRect.top,
-                mStackRect.left + width, mStackRect.top + height);
+        // Anchor the task rect top aligned to the non-freeform stack rect
+        float aspect = (float) (windowRect.width() - (mSystemInsets.left + mSystemInsets.right)) /
+                (windowRect.height() - (mSystemInsets.top + mSystemInsets.bottom));
+        int minHeight = mStackRect.height() - mInitialTopOffset - mStackBottomOffset;
+        int height = (int) Math.min(mStackRect.width() / aspect, minHeight);
+        mTaskRect.set(mStackRect.left, mStackRect.top, mStackRect.right, mStackRect.top + height);
 
         // Short circuit here if the stack rects haven't changed so we don't do all the work below
-        if (lastStackRect.equals(mStackRect)) {
-            return;
+        if (!lastStackRect.equals(mStackRect)) {
+            // Reinitialize the focused and unfocused curves
+            mUnfocusedCurve = constructUnfocusedCurve();
+            mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
+            mFocusedCurve = constructFocusedCurve();
+            mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
+            mUnfocusedDimCurve = constructUnfocusedDimCurve();
+            mUnfocusedDimCurveInterpolator = new FreePathInterpolator(mUnfocusedDimCurve);
+            mFocusedDimCurve = constructFocusedDimCurve();
+            mFocusedDimCurveInterpolator = new FreePathInterpolator(mFocusedDimCurve);
+
+            updateFrontBackTransforms();
         }
-
-        // Reinitialize the focused and unfocused curves
-        mUnfocusedCurve = constructUnfocusedCurve();
-        mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
-        mFocusedCurve = constructFocusedCurve();
-        mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
-        mUnfocusedDimCurve = constructUnfocusedDimCurve();
-        mUnfocusedDimCurveInterpolator = new FreePathInterpolator(mUnfocusedDimCurve);
-        mFocusedDimCurve = constructFocusedDimCurve();
-        mFocusedDimCurveInterpolator = new FreePathInterpolator(mFocusedDimCurve);
-
-        updateFrontBackTransforms();
     }
 
     /**
@@ -435,7 +489,7 @@
         ArrayList<Task> tasks = stack.getStackTasks();
         if (tasks.isEmpty()) {
             mFrontMostTaskP = 0;
-            mMinScrollP = mMaxScrollP = 0;
+            mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
             mNumStackTasks = mNumFreeformTasks = 0;
             return;
         }
@@ -492,8 +546,7 @@
             // Set the max scroll to be the point where the front most task is visible with the
             // stack bottom offset
             int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
-            float maxBottomOffsetPct = (float) maxBottomOffset / mStackRect.height();
-            float maxBottomNormX = mUnfocusedCurveInterpolator.getX(maxBottomOffsetPct);
+            float maxBottomNormX = getNormalizedXFromUnfocusedY(maxBottomOffset, FROM_BOTTOM);
             mUnfocusedRange.offset(0f);
             mMinScrollP = 0;
             mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
@@ -502,34 +555,19 @@
                     launchState.launchedFromAppDocked;
             if (scrollToFront) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+                mInitialNormX = null;
             } else {
-                mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
-            }
+                float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
+                mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2)) -
+                        Math.max(0, mUnfocusedRange.getAbsoluteX(normX)));
 
-            // Set the initial scroll to the predefined state (which differs from the stack)
-            int initialPeekOffset = mStackRect.height() - mInitialTopPeekHeight;
-            float initialPeekOffsetPct = (float) initialPeekOffset / mStackRect.height();
-            float initialPeekOffsetNormX = mUnfocusedCurveInterpolator.getX(initialPeekOffsetPct);
-            float initialFocusedOffset = mStackRect.height() - mInitialTopPeekHeight -
-                    (mHeaderBarHeight * 1f) + 1;
-            float initialFocusedOffsetPct = initialFocusedOffset / mStackRect.height();
-            float initialFocusedNormX = mUnfocusedCurveInterpolator.getX(initialFocusedOffsetPct);
-            float initialBottomOffset = mStackBottomOffset +
-                    (ssp.hasDockedTask()
-                        ? mHeaderBarHeight
-                        : mInitialBottomPeekHeight);
-            float initialBottomOffsetPct = initialBottomOffset / mStackRect.height();
-            float initialBottomNormX = mUnfocusedCurveInterpolator.getX(initialBottomOffsetPct);
-            /*
-            // If we want to offset the top card slightly
-            mInitialNormX = scrollToFront
-                    ? new float[] { initialFocusedNormX, initialPeekOffsetNormX, 0f }
-                    : new float[] { initialBottomNormX, initialFocusedNormX,
-                            initialPeekOffsetNormX, 0f };
-            */
-            mInitialNormX = scrollToFront
-                    ? new float[] { initialFocusedNormX, initialPeekOffsetNormX, 0f }
-                    : new float[] { initialBottomNormX, 0.5f, 0f };
+                // Set the initial scroll to the predefined state (which differs from the stack)
+                mInitialNormX = new float[] {
+                        getNormalizedXFromUnfocusedY(mSystemInsets.bottom + mInitialBottomOffset,
+                                FROM_BOTTOM),
+                        normX
+                };
+            }
         }
     }
 
@@ -669,7 +707,7 @@
         Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
         currentRange.offset(mInitialScrollP);
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.recents_task_bar_height);
+                R.dimen.recents_task_view_header_height);
         int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
         int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
         float prevScreenY = Integer.MAX_VALUE;
@@ -869,6 +907,25 @@
     }
 
     /**
+     * Returns the original scroll progress to scroll to such that the top of the task is at the top
+     * of the stack.
+     */
+    float getStackScrollForTaskIgnoreOverrides(Task t) {
+        return (float) mTaskIndexMap.get(t.key.id, 0);
+    }
+
+    /**
+     * Returns the scroll progress to scroll to such that the top of the task at the initial top
+     * offset (which is at the task's brightest point).
+     */
+    float getStackScrollForTaskAtInitialOffset(Task t) {
+        float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
+        mUnfocusedRange.offset(0f);
+        return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
+                mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
+    }
+
+    /**
      * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
      * length of the curve.  We know the curve is mostly flat, so we just map the length of the
      * screen along the arc-length proportionally (1/arclength).
@@ -890,6 +947,87 @@
     }
 
     /**
+     * Returns the task stack bounds in the current orientation.  This rect takes into account the
+     * top and right system insets (but not the bottom inset) and left/right paddings, but _not_
+     * the top/bottom padding or insets.
+     */
+    public void getTaskStackBounds(Rect windowRect, int topInset, int rightInset,
+            Rect taskStackBounds) {
+        RecentsConfiguration config = Recents.getConfiguration();
+        if (config.hasTransposedNavBar) {
+            taskStackBounds.set(windowRect.left, windowRect.top + topInset,
+                    windowRect.right - rightInset, windowRect.bottom);
+        } else {
+            taskStackBounds.set(windowRect.left, windowRect.top + topInset,
+                    windowRect.right - rightInset, windowRect.bottom);
+        }
+
+        // Ensure that the new width is at most the smaller display edge size
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect displayRect = ssp.getDisplayRect();
+        int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
+                WIDTH);
+        int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
+        if (ssp.getDisplayOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+            // If we are in landscape, calculate the width of the stack in portrait and ensure that
+            // we are not larger than that size
+            Rect portraitDisplayRect = new Rect(0, 0,
+                    Math.min(displayRect.width(), displayRect.height()),
+                    Math.max(displayRect.width(), displayRect.height()));
+            int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
+                    mBaseSideMargin, mMinMargin, WIDTH);
+            targetStackWidth = Math.min(targetStackWidth,
+                    portraitDisplayRect.width() - 2 * portraitSideMargin);
+        }
+        taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
+    }
+
+    /**
+     * Retrieves resources that are constant regardless of the current configuration of the device.
+     */
+    public static int getDimensionForDevice(Resources res, int phoneResId,
+            int tabletResId, int xlargeTabletResId) {
+        return getDimensionForDevice(res, phoneResId, phoneResId, tabletResId, tabletResId,
+                xlargeTabletResId, xlargeTabletResId);
+    }
+
+    /**
+     * Retrieves resources that are constant regardless of the current configuration of the device.
+     */
+    public static int getDimensionForDevice(Resources res, int phonePortResId, int phoneLandResId,
+            int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
+            int xlargeTabletLandResId) {
+        RecentsConfiguration config = Recents.getConfiguration();
+        boolean isLandscape = Recents.getSystemServices().getDisplayOrientation() ==
+                Configuration.ORIENTATION_LANDSCAPE;
+        if (config.isXLargeScreen) {
+            return res.getDimensionPixelSize(isLandscape
+                    ? xlargeTabletLandResId
+                    : xlargeTabletPortResId);
+        } else if (config.isLargeScreen) {
+            return res.getDimensionPixelSize(isLandscape
+                    ? tabletLandResId
+                    : tabletPortResId);
+        } else {
+            return res.getDimensionPixelSize(isLandscape
+                    ? phoneLandResId
+                    : phonePortResId);
+        }
+    }
+
+    /**
+     * Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
+     * stack height).
+     */
+    private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
+        float offset = (fromSide == FROM_TOP)
+                ? mStackRect.height() - y
+                : y;
+        float offsetPct = offset / mStackRect.height();
+        return mUnfocusedCurveInterpolator.getX(offsetPct);
+    }
+
+    /**
      * Creates a new path for the focused curve.
      */
     private Path constructFocusedCurve() {
@@ -897,13 +1035,12 @@
         // linear pieces that goes from (0,1) through (0.5, peek height offset),
         // (0.5, bottom task offsets), and (1,0).
         float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
-        float bottomPeekHeightPct = Math.max(
-                mSystemInsets.bottom + mFocusedRange.relativeMax * mFocusedBottomTaskPeekHeight,
-                mStackBottomOffset + mFocusedBottomTaskPeekHeight) / mStackRect.height();
+        float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
+                mStackRect.height();
         Path p = new Path();
         p.moveTo(0f, 1f);
         p.lineTo(0.5f, 1f - topPeekHeightPct);
-        p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), bottomPeekHeightPct);
+        p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), bottomPeekHeightPct);
         p.lineTo(1f, 0f);
         return p;
     }
@@ -919,16 +1056,16 @@
         // the control point of the second bezier such that between it and a first known point,
         // there is a tangent at (0.5, peek height offset).
         float cpoint1X = 0.4f;
-        float cpoint1Y = 1f;
-        float peekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
-        float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
+        float cpoint1Y = 0.975f;
+        float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
+        float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
         float b = 1f - slope * cpoint1X;
         float cpoint2X = 0.65f;
         float cpoint2Y = slope * cpoint2X + b;
         Path p = new Path();
         p.moveTo(0f, 1f);
-        p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - peekHeightPct);
-        p.cubicTo(0.5f, 1f - peekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
+        p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
+        p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
         return p;
     }
 
@@ -950,15 +1087,30 @@
      * Creates a new path for the unfocused dim curve.
      */
     private Path constructUnfocusedDimCurve() {
+        float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
+        float cpoint2X = focusX + (1f - focusX) / 2;
         Path p = new Path();
         // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
         // task), then goes back to max dim towards the front of the stack
         p.moveTo(0f, MAX_DIM);
-        p.cubicTo(0.1f, MAX_DIM, 0.4f, 0.0f, 0.5f, 0f);
-        p.cubicTo(0.6f, 0f, 0.9f, MAX_DIM / 2f, 1f, MAX_DIM / 2f);
+        p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
+        p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
         return p;
     }
 
+    /**
+     * Scales the given {@param value} to the scale of the {@param instance} rect relative to the
+     * {@param other} rect in the {@param extent} side.
+     */
+    private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
+                                  @Extent int extent) {
+        if (extent == WIDTH) {
+            return Math.max(minValue, (int) (((float) instance.width() / other.width()) * value));
+        } else if (extent == HEIGHT) {
+            return Math.max(minValue, (int) (((float) instance.height() / other.height()) * value));
+        }
+        return value;
+    }
 
     /**
      * Updates the current transforms that would put a TaskView at the front and back of the stack.
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 0b20d21..c9cc1e9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -59,8 +59,7 @@
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.EnterRecentsTaskStackAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
-import com.android.systemui.recents.events.activity.HideHistoryEvent;
+import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
@@ -68,8 +67,7 @@
 import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
-import com.android.systemui.recents.events.activity.ShowHistoryEvent;
+import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
@@ -84,7 +82,6 @@
 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
-import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
@@ -106,15 +103,15 @@
     private final static String KEY_SAVED_STATE_LAYOUT_STACK_SCROLL =
             "saved_instance_state_layout_stack_scroll";
 
-    // The thresholds at which to show/hide the history button.
-    private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
-    private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
+    // The thresholds at which to show/hide the stack action button.
+    private static final float SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f;
+    private static final float HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f;
 
     public static final int DEFAULT_SYNC_STACK_DURATION = 200;
     private static final int DRAG_SCALE_DURATION = 175;
     private static final float DRAG_SCALE_FACTOR = 1.05f;
 
-    private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 200;
+    private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216;
     private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32;
 
     private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
@@ -165,11 +162,18 @@
     // The current stack bounds are dynamic and may change as the user drags and drops
     @ViewDebug.ExportedProperty(category="recents")
     private Rect mStackBounds = new Rect();
+    // The current window bounds at the point we were measured
+    @ViewDebug.ExportedProperty(category="recents")
+    private Rect mStableWindowRect = new Rect();
+    // The current window bounds are dynamic and may change as the user drags and drops
+    @ViewDebug.ExportedProperty(category="recents")
+    private Rect mWindowRect = new Rect();
 
     private Rect mTmpRect = new Rect();
     private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
     private List<TaskView> mTmpTaskViews = new ArrayList<>();
     private TaskViewTransform mTmpTransform = new TaskViewTransform();
+    private ArrayList<TaskViewTransform> mTmpTaskTransforms = new ArrayList<>();
     private int[] mTmpIntPair = new int[2];
 
     // A convenience update listener to request updating clipping of tasks
@@ -302,7 +306,8 @@
     /**
      * Sets the stack tasks of this TaskStackView from the given TaskStack.
      */
-    public void setTasks(TaskStack stack, boolean notifyStackChanges, boolean relayoutTaskStack) {
+    public void setTasks(TaskStack stack, boolean notifyStackChanges, boolean relayoutTaskStack,
+            boolean multiWindowChange) {
         boolean isInitialized = mLayoutAlgorithm.isInitialized();
         mStack.setTasks(getContext(), stack.computeAllTasksList(),
                 notifyStackChanges && isInitialized);
@@ -310,7 +315,9 @@
             // Only update the layout if we are notifying, otherwise, we will update it in the next
             // measure/layout pass
             updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
-            updateToInitialState();
+            if (!multiWindowChange) {
+                updateToInitialState();
+            }
 
             if (relayoutTaskStack) {
                 relayoutTaskViews(AnimationProps.IMMEDIATE);
@@ -622,15 +629,17 @@
     }
 
     /**
-     * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
-     * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
-     * animations that are current running on those task views, and will ensure that the children
-     * {@link TaskView}s will match the set of visible tasks in the stack.
-     *
-     * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>)
+     * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>, boolean)
      */
     void relayoutTaskViews(AnimationProps animation) {
-        relayoutTaskViews(animation, mIgnoreTasks);
+        relayoutTaskViews(animation, mIgnoreTasks, false /* ignoreTaskOverrides */);
+    }
+
+    /**
+     * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>, boolean)
+     */
+    void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
+        relayoutTaskViews(animation, ignoreTasksSet, false /* ignoreTaskOverrides */);
     }
 
     /**
@@ -641,13 +650,14 @@
      *
      * @param ignoreTasksSet the set of tasks to ignore in the relayout
      */
-    void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
+    void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet,
+            boolean ignoreTaskOverrides) {
         // If we had a deferred animation, cancel that
         mDeferredTaskViewLayoutAnimation = null;
 
         // Synchronize the current set of TaskViews
         bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet,
-                false /* ignoreTaskOverrides */);
+                ignoreTaskOverrides /* ignoreTaskOverrides */);
 
         // Animate them to their final transforms with the given animation
         List<TaskView> taskViews = getTaskViews();
@@ -1059,8 +1069,10 @@
             event.setContentDescription(frontMostTask.getTask().title);
         }
         event.setItemCount(mStack.getTaskCount());
-        event.setScrollY(mStackScroller.mScroller.getCurrY());
-        event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
+
+        int stackHeight = mLayoutAlgorithm.mStackRect.height();
+        event.setScrollY((int) (mStackScroller.getStackScroll() * stackHeight));
+        event.setMaxScrollY((int) (mLayoutAlgorithm.mMaxScrollP * stackHeight));
     }
 
     @Override
@@ -1161,23 +1173,11 @@
     }
 
     /**
-     * Updates the expected task stack bounds for this stack view.
+     * Updates the system insets.
      */
-    public void setTaskStackBounds(Rect taskStackBounds, Rect systemInsets) {
-        // We can get spurious measure passes with the old bounds when docking, and since we are
-        // using the current stack bounds during drag and drop, don't overwrite them until we
-        // actually get new bounds
-        boolean requiresLayout = false;
-        if (!taskStackBounds.equals(mStableStackBounds)) {
-            mStableStackBounds.set(taskStackBounds);
-            mStackBounds.set(taskStackBounds);
-            requiresLayout = true;
-        }
+    public void setSystemInsets(Rect systemInsets) {
         if (!systemInsets.equals(mLayoutAlgorithm.mSystemInsets)) {
             mLayoutAlgorithm.setSystemInsets(systemInsets);
-            requiresLayout = true;
-        }
-        if (requiresLayout) {
             requestLayout();
         }
     }
@@ -1192,8 +1192,20 @@
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
+        // Update the stable stack bounds, but only update the current stack bounds if the stable
+        // bounds have changed.  This is because we may get spurious measures while dragging where
+        // our current stack bounds reflect the target drop region.
+        mLayoutAlgorithm.getTaskStackBounds(new Rect(0, 0, width, height),
+                mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.right, mTmpRect);
+        if (!mTmpRect.equals(mStableStackBounds)) {
+            mStableStackBounds.set(mTmpRect);
+            mStackBounds.set(mTmpRect);
+            mStableWindowRect.set(0, 0, width, height);
+            mWindowRect.set(0, 0, width, height);
+        }
+
         // Compute the rects in the stack algorithm
-        mLayoutAlgorithm.initialize(mStackBounds,
+        mLayoutAlgorithm.initialize(mWindowRect, mStackBounds,
                 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
         updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
 
@@ -1241,11 +1253,6 @@
                         MeasureSpec.EXACTLY));
     }
 
-    /**
-     * This is called with the size of the space not including the top or right insets, or the
-     * search bar height in portrait (but including the search bar width in landscape, since we want
-     * to draw under it.
-     */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         // Layout each of the TaskViews
@@ -1282,6 +1289,7 @@
             mTmpRect.setEmpty();
         }
         Rect taskRect = mLayoutAlgorithm.mTaskRect;
+        tv.cancelTransformAnimation();
         tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
                 taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
     }
@@ -1306,12 +1314,11 @@
                     false /* requestViewFocus */);
         }
 
-        // Update the history button visibility
-        if (shouldShowHistoryButton() &&
-                mStackScroller.getStackScroll() < SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) {
-            EventBus.getDefault().send(new ShowHistoryButtonEvent(false /* translate */));
+        // Update the stack action button visibility
+        if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
         } else {
-            EventBus.getDefault().send(new HideHistoryButtonEvent());
+            EventBus.getDefault().send(new HideStackActionButtonEvent());
         }
     }
 
@@ -1432,12 +1439,6 @@
         }
     }
 
-    @Override
-    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
-            AnimationProps animation) {
-        // To be implemented
-    }
-
     /**** ViewPoolConsumer Implementation ****/
 
     @Override
@@ -1572,13 +1573,12 @@
         mLayoutAlgorithm.updateFocusStateOnScroll(curScroll, curScroll - prevScroll);
 
         if (mEnterAnimationComplete) {
-            if (shouldShowHistoryButton() &&
-                    prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
-                    curScroll <= SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) {
-                EventBus.getDefault().send(new ShowHistoryButtonEvent(true /* translate */));
-            } else if (prevScroll < HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD &&
-                    curScroll >= HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD) {
-                EventBus.getDefault().send(new HideHistoryButtonEvent());
+            if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+                    curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+                EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
+            } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+                    curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+                EventBus.getDefault().send(new HideStackActionButtonEvent());
             }
         }
     }
@@ -1625,15 +1625,13 @@
             cancelAllTaskViewAnimations();
 
             final Task launchTask = mStack.getStackTasks().get(launchTaskIndex);
-            if (getChildViewForTask(launchTask) == null) {
-                List<TaskView> taskViews = getTaskViews();
-                int lastTaskIndex = !taskViews.isEmpty()
-                        ? mStack.indexOfStackTask(taskViews.get(taskViews.size() - 1).getTask())
-                        : mStack.getTaskCount() - 1;
-                int duration = LAUNCH_NEXT_SCROLL_BASE_DURATION +
-                        Math.abs(mStack.indexOfStackTask(launchTask) - lastTaskIndex)
-                                * LAUNCH_NEXT_SCROLL_INCR_DURATION;
-                mStackScroller.animateScroll(mLayoutAlgorithm.getStackScrollForTask(launchTask),
+            float curScroll = mStackScroller.getStackScroll();
+            float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(launchTask);
+            float absScrollDiff = Math.abs(targetScroll - curScroll);
+            if (getChildViewForTask(launchTask) == null || absScrollDiff > 0.35f) {
+                int duration = (int) (LAUNCH_NEXT_SCROLL_BASE_DURATION +
+                        absScrollDiff * LAUNCH_NEXT_SCROLL_INCR_DURATION);
+                mStackScroller.animateScroll(targetScroll,
                         duration, new Runnable() {
                             @Override
                             public void run() {
@@ -1757,32 +1755,38 @@
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
         AnimationProps animation = new AnimationProps(250, Interpolators.FAST_OUT_SLOW_IN);
+        boolean ignoreTaskOverrides = false;
         if (event.dropTarget instanceof TaskStack.DockState) {
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
             mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
                     getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
-                    getResources()));
-            mLayoutAlgorithm.initialize(mStackBounds,
+                    mLayoutAlgorithm, getResources(), mWindowRect));
+            mLayoutAlgorithm.initialize(mWindowRect, mStackBounds,
                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
             updateLayoutAlgorithm(true /* boundScroll */);
+            ignoreTaskOverrides = true;
         } else {
             // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
             // task view, so add it back to the ignore set after updating the layout
+            mWindowRect.set(mStableWindowRect);
             mStackBounds.set(mStableStackBounds);
             removeIgnoreTask(event.task);
-            mLayoutAlgorithm.initialize(mStackBounds,
+            mLayoutAlgorithm.initialize(mWindowRect, mStackBounds,
                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
             updateLayoutAlgorithm(true /* boundScroll */);
             addIgnoreTask(event.task);
         }
-        relayoutTaskViews(animation);
+        relayoutTaskViews(animation, mIgnoreTasks, ignoreTaskOverrides);
     }
 
     public final void onBusEvent(final DragEndEvent event) {
         // We don't handle drops on the dock regions
         if (event.dropTarget instanceof TaskStack.DockState) {
+            // However, we do need to reset the overrides, since the last state of this task stack
+            // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
+            mLayoutAlgorithm.clearUnfocusedTaskOverrides();
             return;
         }
 
@@ -1888,22 +1892,6 @@
         }
     }
 
-    public final void onBusEvent(ShowHistoryEvent event) {
-        ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
-        postAnimTrigger.addLastDecrementRunnable(new Runnable() {
-            @Override
-            public void run() {
-                setVisibility(View.INVISIBLE);
-            }
-        });
-        mAnimationHelper.startShowHistoryAnimation(postAnimTrigger);
-    }
-
-    public final void onBusEvent(HideHistoryEvent event) {
-        setVisibility(View.VISIBLE);
-        mAnimationHelper.startHideHistoryAnimation();
-    }
-
     public final void onBusEvent(MultiWindowStateChangedEvent event) {
         if (!event.inMultiWindow) {
             // Scroll the stack to the front to see the undocked task
@@ -1924,7 +1912,7 @@
 
     public final void onBusEvent(ConfigurationChangedEvent event) {
         mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
-        mLayoutAlgorithm.initialize(mStackBounds,
+        mLayoutAlgorithm.initialize(mWindowRect, mStackBounds,
                 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
     }
 
@@ -1993,13 +1981,6 @@
     }
 
     /**
-     * @return whether the history button should be visible
-     */
-    private boolean shouldShowHistoryButton() {
-        return !mStack.getHistoricalTasks().isEmpty();
-    }
-
-    /**
      * Reads current system flags related to accessibility and screen pinning.
      */
     private void readSystemFlags() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 9be3542..583fb88 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -28,8 +28,6 @@
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 
 /* The scrolling logic for a TaskStackView */
@@ -189,7 +187,7 @@
         // Finish any current scrolling animations
         if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
             setStackScroll(mFinalAnimatedScroll);
-            mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0);
+            mScroller.forceFinished(true);
         }
         stopScroller();
         stopBoundScrollAnimation();
@@ -223,12 +221,6 @@
 
     /**** OverScroller ****/
 
-    // TODO: Remove
-    @Deprecated
-    int progressToScrollRange(float p) {
-        return (int) (p * mLayoutAlgorithm.mStackRect.height());
-    }
-
     /** Called from the view draw, computes the next scroll. */
     boolean computeScroll() {
         if (mScroller.computeScrollOffset()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 8635911..aed19c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -121,7 +121,7 @@
         mScrollTouchSlop = configuration.getScaledTouchSlop();
         mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
         mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
-        mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
+        mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_fling_overscroll_distance);
         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context) {
             @Override
             protected float getSize(View v) {
@@ -458,12 +458,13 @@
                 newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
             } else if (pullStackForward) {
                 // Otherwise, offset the scroll by the movement of the anchor task
-                float anchorTaskScroll = layoutAlgorithm.getStackScrollForTask(anchorTask);
+                float anchorTaskScroll =
+                        layoutAlgorithm.getStackScrollForTaskIgnoreOverrides(anchorTask);
                 float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
                 if (layoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
                     // If we are focused, we don't want the front task to move, but otherwise, we
                     // allow the back task to move up, and the front task to move back
-                    stackScrollOffset /= 2;
+                    stackScrollOffset *= 0.75f;
                 }
                 newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
                         + stackScrollOffset);
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 7584a2e..0c47b13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -16,13 +16,15 @@
 
 package com.android.systemui.recents.views;
 
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Point;
@@ -59,8 +61,6 @@
 
 import java.util.ArrayList;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 /**
  * A {@link TaskView} represents a fixed view of a task. Because the TaskView's layout is directed
  * solely by the {@link TaskStackView}, we make it a fixed size layout which allows relayouts down
@@ -129,8 +129,6 @@
 
     @ViewDebug.ExportedProperty(category="recents")
     float mDimAlpha;
-    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
-    Paint mDimLayerPaint = new Paint();
     float mActionButtonTranslationZ;
 
     @ViewDebug.ExportedProperty(deepExport=true, prefix="task_")
@@ -559,6 +557,13 @@
                 postAnimationTrigger.decrementOnAnimationEnd());
     }
 
+    @Override
+    public void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled) {
+        if (screenPinningEnabled) {
+            showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
+        }
+    }
+
     /**** TaskCallbacks Implementation ****/
 
     public void onTaskBound(Task t) {
@@ -569,9 +574,9 @@
     }
 
     @Override
-    public void onTaskDataLoaded(Task task) {
+    public void onTaskDataLoaded(Task task, ActivityManager.TaskThumbnailInfo thumbnailInfo) {
         // Bind each of the views to the new task data
-        mThumbnailView.rebindToTask(mTask, mIsDisabledInSafeMode);
+        mThumbnailView.rebindToTask(mTask, thumbnailInfo, mIsDisabledInSafeMode);
         mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
         mTaskDataLoaded = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index b2a7d90..62995a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -34,9 +34,11 @@
 import android.os.CountDownTimer;
 import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewAnimationUtils;
 import android.view.ViewDebug;
+import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -153,6 +155,8 @@
     // Header drawables
     @ViewDebug.ExportedProperty(category="recents")
     Rect mTaskViewRect = new Rect();
+    int mHeaderBarHeight;
+    int mHeaderButtonPadding;
     int mCornerRadius;
     int mHighlightHeight;
     @ViewDebug.ExportedProperty(category="recents")
@@ -245,6 +249,57 @@
         }
         mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
         mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
+
+        // Update the dimensions of everything in the header. We do this because we need to use
+        // resources for the display, and not the current configuration.
+        Resources res = getResources();
+        mHeaderBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(res,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height_tablet_land,
+                R.dimen.recents_task_view_header_height,
+                R.dimen.recents_task_view_header_height_tablet_land);
+        mHeaderButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(res,
+                R.dimen.recents_task_view_header_button_padding,
+                R.dimen.recents_task_view_header_button_padding,
+                R.dimen.recents_task_view_header_button_padding,
+                R.dimen.recents_task_view_header_button_padding_tablet_land,
+                R.dimen.recents_task_view_header_button_padding,
+                R.dimen.recents_task_view_header_button_padding_tablet_land);
+        updateLayoutParams(mIconView, findViewById(R.id.title_container), mMoveTaskButton,
+                mDismissButton);
+    }
+
+    /**
+     * Programmatically sets the layout params for a header bar layout.  This is necessary because
+     * we can't get resources based on the current configuration, but instead need to get them
+     * based on the device configuration.
+     */
+    private void updateLayoutParams(View icon, View title, View secondaryButton, View button) {
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP);
+        setLayoutParams(lp);
+        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START);
+        icon.setLayoutParams(lp);
+        lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
+        lp.leftMargin = mHeaderBarHeight;
+        lp.rightMargin = mMoveTaskButton != null
+                ? 2 * mHeaderBarHeight
+                : mHeaderBarHeight;
+        title.setLayoutParams(lp);
+        if (secondaryButton != null) {
+            lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
+            lp.rightMargin = mHeaderBarHeight;
+            secondaryButton.setLayoutParams(lp);
+            secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding,
+                    mHeaderButtonPadding, mHeaderButtonPadding);
+        }
+        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
+        button.setLayoutParams(lp);
+        button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding,
+                mHeaderButtonPadding);
     }
 
     @Override
@@ -337,6 +392,11 @@
         }
     }
 
+    /** Only exposed for the workaround for b/27815919. */
+    public ImageView getIconView() {
+        return mIconView;
+    }
+
     /** Returns the secondary color for a primary color. */
     int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
         int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
@@ -350,6 +410,7 @@
     public void setDimAlpha(float dimAlpha) {
         if (Float.compare(mDimAlpha, dimAlpha) != 0) {
             mDimAlpha = dimAlpha;
+            mTitleView.setAlpha(1f - dimAlpha);
             updateBackgroundColor(mBackground.getColor(), dimAlpha);
         }
     }
@@ -573,6 +634,7 @@
             mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
             mAppInfoView.setOnClickListener(this);
             mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
+            updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
         }
 
         // Update the overlay contents for the current app
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e46708e..e9c09ac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.recents.views;
 
+import android.app.ActivityManager;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
@@ -34,6 +36,8 @@
 import android.view.ViewDebug;
 
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.Task;
 
 
@@ -43,17 +47,24 @@
  */
 public class TaskViewThumbnail extends View {
 
-
     private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix();
     private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix();
 
     private Task mTask;
 
+    private Rect mDisplayRect = new Rect();
+    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
+
     // Drawing
     @ViewDebug.ExportedProperty(category="recents")
+    Rect mTaskViewRect = new Rect();
+    @ViewDebug.ExportedProperty(category="recents")
     Rect mThumbnailRect = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mTaskViewRect = new Rect();
+    float mThumbnailScale;
+    float mFullscreenThumbnailScale;
+    ActivityManager.TaskThumbnailInfo mThumbnailInfo;
+
     int mCornerRadius;
     @ViewDebug.ExportedProperty(category="recents")
     float mDimAlpha;
@@ -97,6 +108,8 @@
         mCornerRadius = getResources().getDimensionPixelSize(
                 R.dimen.recents_task_view_rounded_corners_radius);
         mBgFillPaint.setColor(Color.WHITE);
+        mFullscreenThumbnailScale = context.getResources().getFraction(
+                com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
     }
 
     /**
@@ -114,57 +127,75 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mOrientation = ssp.getDisplayOrientation();
+        mDisplayRect = ssp.getDisplayRect();
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         if (mInvisible) {
             return;
         }
 
-        int thumbnailHeight = (int) (((float) mTaskViewRect.width() / mThumbnailRect.width()) *
-                mThumbnailRect.height());
-        if (thumbnailHeight >= mTaskViewRect.height()) {
-            // The thumbnail fills the full task view bounds, so just draw it
-            canvas.drawRoundRect(0, 0, mTaskViewRect.width(), mTaskViewRect.height(),
-                    mCornerRadius, mCornerRadius, mDrawPaint);
-        } else {
-            int count = 0;
-            if (thumbnailHeight > 0) {
-                // The thumbnail only covers part of the task view bounds, so fill in the
-                // non-thumbnail space with the default background color.  This is the equivalent of
-                // the GL border texture mode.
-                count = canvas.save();
+        if (mBitmapShader != null) {
+            int viewWidth = mTaskViewRect.width();
+            int viewHeight = mTaskViewRect.height();
+
+            // We are drawing the thumbnail in the same orientation, so just fit the width
+            int thumbnailWidth = (int) (mThumbnailRect.width() * mThumbnailScale);
+            int thumbnailHeight = (int) (mThumbnailRect.height() * mThumbnailScale);
+
+            if (thumbnailWidth >= viewWidth && thumbnailHeight >= viewHeight) {
+                // Thumbnail fills the full task view bounds, so just draw it
+                canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
+                        mDrawPaint);
+            } else {
+                // Thumbnail does not fill the full task view bounds, so just draw it and fill the
+                // empty areas with the background color
+                int count = canvas.save();
 
                 // Since we only want the top corners to be rounded, draw slightly beyond the
                 // thumbnail height, but clip to the thumbnail height
-                canvas.clipRect(0, 0, mTaskViewRect.width(), thumbnailHeight, Region.Op.REPLACE);
-                canvas.drawRoundRect(0, 0, mTaskViewRect.width(), thumbnailHeight + mCornerRadius,
+                canvas.clipRect(0, 0, thumbnailWidth, thumbnailHeight, Region.Op.REPLACE);
+                canvas.drawRoundRect(0, 0,
+                        thumbnailWidth + (thumbnailWidth < viewWidth ? mCornerRadius : 0),
+                        thumbnailHeight + (thumbnailHeight < viewHeight ? mCornerRadius : 0),
                         mCornerRadius, mCornerRadius, mDrawPaint);
-            }
 
-            // In the remaining space, draw the background color
-            canvas.clipRect(0, thumbnailHeight, mTaskViewRect.width(), mTaskViewRect.height(),
-                    Region.Op.REPLACE);
-            canvas.drawRoundRect(0, Math.max(0, thumbnailHeight - mCornerRadius),
-                    mTaskViewRect.width(), mTaskViewRect.height(), mCornerRadius, mCornerRadius,
-                    mBgFillPaint);
+                // In the remaining space, draw the background color
+                if (thumbnailWidth < viewWidth) {
+                    canvas.clipRect(thumbnailWidth, 0, viewWidth, viewHeight, Region.Op.REPLACE);
+                    canvas.drawRoundRect(Math.max(0, thumbnailWidth - mCornerRadius), 0,
+                            viewWidth, viewHeight, mCornerRadius, mCornerRadius, mBgFillPaint);
+                }
+                if (thumbnailWidth > 0 && thumbnailHeight < viewHeight) {
+                    canvas.clipRect(0, thumbnailHeight, viewWidth, viewHeight, Region.Op.REPLACE);
+                    canvas.drawRoundRect(0, Math.max(0, thumbnailHeight - mCornerRadius),
+                            viewWidth, viewHeight, mCornerRadius, mCornerRadius, mBgFillPaint);
+                }
 
-            if (thumbnailHeight > 0) {
                 canvas.restoreToCount(count);
             }
         }
     }
 
     /** Sets the thumbnail to a given bitmap. */
-    void setThumbnail(Bitmap bm) {
+    void setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo) {
         if (bm != null) {
-            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
-                    Shader.TileMode.CLAMP);
+            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mDrawPaint.setShader(mBitmapShader);
             mThumbnailRect.set(0, 0, bm.getWidth(), bm.getHeight());
+            mThumbnailInfo = thumbnailInfo;
             updateThumbnailScale();
         } else {
             mBitmapShader = null;
             mDrawPaint.setShader(null);
             mThumbnailRect.setEmpty();
+            mThumbnailInfo = null;
         }
     }
 
@@ -210,20 +241,46 @@
      * Updates the scale of the bitmap relative to this view.
      */
     public void updateThumbnailScale() {
+        mThumbnailScale = 1f;
         if (mBitmapShader != null) {
-            float thumbnailScale;
-            if (!mTask.isFreeformTask() || mTask.bounds == null) {
-                // If this is a stack task, or a stack task moved into the freeform workspace, then
-                // just scale this thumbnail to fit the width of the view
-                thumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
+
+            if (mThumbnailInfo != null) {
+                System.out.println(mTask.title + " bounds: " + mThumbnailInfo.taskWidth + "x" + mThumbnailInfo.taskHeight + ", " + mThumbnailInfo.screenOrientation);
+            }
+
+            // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
+            // dragged into the stack from the freeform workspace
+            boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
+            if (mTaskViewRect.isEmpty() || mThumbnailInfo == null ||
+                    mThumbnailInfo.taskWidth == 0 || mThumbnailInfo.taskHeight == 0) {
+                // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing
+                // and only draw the background color
+                mThumbnailScale = 0f;
+            } else if (isStackTask) {
+                float invThumbnailScale = 1f / mFullscreenThumbnailScale;
+                if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
+                    if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) {
+                        // If we are in the same orientation as the screenshot, just scale it to the
+                        // width of the task view
+                        mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
+                    } else {
+                        // Scale the landscape thumbnail up to app size, then scale that to the task
+                        // view size to match other portrait screenshots
+                        mThumbnailScale = invThumbnailScale *
+                                ((float) mTaskViewRect.width() / mDisplayRect.width());
+                    }
+                } else {
+                    // Otherwise, scale the screenshot to fit 1:1 in the current orientation
+                    mThumbnailScale = invThumbnailScale;
+                }
             } else {
                 // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
                 // to fit the entire bitmap into the task bounds
-                thumbnailScale = Math.min(
+                mThumbnailScale = Math.min(
                         (float) mTaskViewRect.width() / mThumbnailRect.width(),
                         (float) mTaskViewRect.height() / mThumbnailRect.height());
             }
-            mScaleMatrix.setScale(thumbnailScale, thumbnailScale);
+            mScaleMatrix.setScale(mThumbnailScale, mThumbnailScale);
             mBitmapShader.setLocalMatrix(mScaleMatrix);
         }
         if (!mInvisible) {
@@ -261,22 +318,23 @@
     }
 
     /** Binds the thumbnail view to the task */
-    void rebindToTask(Task t, boolean disabledInSafeMode) {
+    void rebindToTask(Task t, ActivityManager.TaskThumbnailInfo thumbnailInfo,
+            boolean disabledInSafeMode) {
         mTask = t;
         mDisabledInSafeMode = disabledInSafeMode;
         if (t.thumbnail != null) {
-            setThumbnail(t.thumbnail);
+            setThumbnail(t.thumbnail, thumbnailInfo);
             if (t.colorBackground != 0) {
                 mBgFillPaint.setColor(t.colorBackground);
             }
         } else {
-            setThumbnail(null);
+            setThumbnail(null, null);
         }
     }
 
     /** Unbinds the thumbnail view from the task */
     void unbindFromTask() {
         mTask = null;
-        setThumbnail(null);
+        setThumbnail(null, null);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 2d2a08a..1c5d28a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1206,10 +1206,10 @@
     }
 
     @Override
-    public void toggleKeyboardShortcutsMenu() {
+    public void toggleKeyboardShortcutsMenu(int deviceId) {
         int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
         mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
+        mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
     }
 
     /** Jumps to the next affiliated task in the group. */
@@ -1295,8 +1295,8 @@
         }
     }
 
-    protected void toggleKeyboardShortcuts() {
-        getKeyboardShortcuts().toggleKeyboardShortcuts();
+    protected void toggleKeyboardShortcuts(int deviceId) {
+        getKeyboardShortcuts().toggleKeyboardShortcuts(deviceId);
     }
 
     protected void cancelPreloadingRecents() {
@@ -1469,7 +1469,7 @@
                   showRecentsPreviousAffiliatedTask();
                   break;
              case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
-                  toggleKeyboardShortcuts();
+                  toggleKeyboardShortcuts(m.arg1);
                   break;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6a98488..0ffab5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -107,7 +107,7 @@
         public void toggleRecentApps();
         public void toggleSplitScreen();
         public void preloadRecentApps();
-        public void toggleKeyboardShortcutsMenu();
+        public void toggleKeyboardShortcutsMenu(int deviceId);
         public void cancelPreloadRecentApps();
         public void setWindowState(int window, int state);
         public void buzzBeepBlinked();
@@ -254,10 +254,10 @@
     }
 
     @Override
-    public void toggleKeyboardShortcutsMenu() {
+    public void toggleKeyboardShortcutsMenu(int deviceId) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_TOGGLE_KEYBOARD_SHORTCUTS);
-            mHandler.obtainMessage(MSG_TOGGLE_KEYBOARD_SHORTCUTS).sendToTarget();
+            mHandler.obtainMessage(MSG_TOGGLE_KEYBOARD_SHORTCUTS, deviceId, 0).sendToTarget();
         }
     }
 
@@ -425,7 +425,7 @@
                     mCallbacks.cancelPreloadRecentApps();
                     break;
                 case MSG_TOGGLE_KEYBOARD_SHORTCUTS:
-                    mCallbacks.toggleKeyboardShortcutsMenu();
+                    mCallbacks.toggleKeyboardShortcutsMenu(msg.arg1);
                     break;
                 case MSG_SET_WINDOW_STATE:
                     mCallbacks.setWindowState(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 60c2fa6..8fe60a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -21,9 +21,14 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
+import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.Log;
+import android.util.SparseArray;
 import android.view.ContextThemeWrapper;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
@@ -42,16 +47,163 @@
 import java.util.List;
 
 import static android.content.Context.LAYOUT_INFLATER_SERVICE;
-import static android.view.Gravity.TOP;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
 
 /**
  * Contains functionality for handling keyboard shortcuts.
  */
 public class KeyboardShortcuts {
-    private static final char SYSTEM_HOME_BASE_CHARACTER = '\u2386';
-    private static final char SYSTEM_BACK_BASE_CHARACTER = '\u007F';
-    private static final char SYSTEM_RECENTS_BASE_CHARACTER = '\u0009';
+    private static final String TAG = KeyboardShortcuts.class.getSimpleName();
+
+    private static final SparseArray<String> SPECIAL_CHARACTER_NAMES = new SparseArray<>();
+    private static final SparseArray<String> MODIFIER_NAMES = new SparseArray<>();
+
+    private static void loadSpecialCharacterNames(Context context) {
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_PERIOD, ".");
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
+                context.getString(R.string.keyboard_key_media_play_pause));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+                context.getString(R.string.keyboard_key_media_previous));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_REWIND,
+                context.getString(R.string.keyboard_key_media_rewind));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+                context.getString(R.string.keyboard_key_media_fast_forward));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_A,
+                context.getString(R.string.keyboard_key_button_template, "A"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_B,
+                context.getString(R.string.keyboard_key_button_template, "B"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_C,
+                context.getString(R.string.keyboard_key_button_template, "C"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_X,
+                context.getString(R.string.keyboard_key_button_template, "X"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_Y,
+                context.getString(R.string.keyboard_key_button_template, "Y"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_Z,
+                context.getString(R.string.keyboard_key_button_template, "Z"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_L1,
+                context.getString(R.string.keyboard_key_button_template, "L1"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_R1,
+                context.getString(R.string.keyboard_key_button_template, "R1"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_L2,
+                context.getString(R.string.keyboard_key_button_template, "L2"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_R2,
+                context.getString(R.string.keyboard_key_button_template, "R2"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_START,
+                context.getString(R.string.keyboard_key_button_template, "Start"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_SELECT,
+                context.getString(R.string.keyboard_key_button_template, "Select"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_MODE,
+                context.getString(R.string.keyboard_key_button_template, "Mode"));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BREAK, "Break");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F1, "F1");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F2, "F2");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F3, "F3");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F4, "F4");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F5, "F5");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F6, "F6");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F7, "F7");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F8, "F8");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F9, "F9");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F10, "F10");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F11, "F11");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F12, "F12");
+        SPECIAL_CHARACTER_NAMES.put(
+                KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_0,
+                context.getString(R.string.keyboard_key_numpad_template, "0"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_1,
+                context.getString(R.string.keyboard_key_numpad_template, "1"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_2,
+                context.getString(R.string.keyboard_key_numpad_template, "2"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_3,
+                context.getString(R.string.keyboard_key_numpad_template, "3"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_4,
+                context.getString(R.string.keyboard_key_numpad_template, "4"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_5,
+                context.getString(R.string.keyboard_key_numpad_template, "5"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_6,
+                context.getString(R.string.keyboard_key_numpad_template, "6"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_7,
+                context.getString(R.string.keyboard_key_numpad_template, "7"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_8,
+                context.getString(R.string.keyboard_key_numpad_template, "8"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_9,
+                context.getString(R.string.keyboard_key_numpad_template, "9"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
+                context.getString(R.string.keyboard_key_numpad_template, "/"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
+                context.getString(R.string.keyboard_key_numpad_template, "*"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
+                context.getString(R.string.keyboard_key_numpad_template, "-"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_ADD,
+                context.getString(R.string.keyboard_key_numpad_template, "+"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_DOT,
+                context.getString(R.string.keyboard_key_numpad_template, "."));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
+                context.getString(R.string.keyboard_key_numpad_template, ","));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
+                context.getString(R.string.keyboard_key_numpad_template,
+                        context.getString(R.string.keyboard_key_enter)));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
+                context.getString(R.string.keyboard_key_numpad_template, "="));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
+                context.getString(R.string.keyboard_key_numpad_template, "("));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
+                context.getString(R.string.keyboard_key_numpad_template, ")"));
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_EISU, "英数");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_HENKAN, "変換");
+        SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+
+        MODIFIER_NAMES.put(KeyEvent.META_META_ON, "Meta");
+        MODIFIER_NAMES.put(KeyEvent.META_CTRL_ON, "Ctrl");
+        MODIFIER_NAMES.put(KeyEvent.META_ALT_ON, "Alt");
+        MODIFIER_NAMES.put(KeyEvent.META_SHIFT_ON, "Shift");
+        MODIFIER_NAMES.put(KeyEvent.META_SYM_ON, "Sym");
+        MODIFIER_NAMES.put(KeyEvent.META_FUNCTION_ON, "Fn");
+    }
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Context mContext;
@@ -62,12 +214,20 @@
     };
 
     private Dialog mKeyboardShortcutsDialog;
+    private KeyCharacterMap mKeyCharacterMap;
 
     public KeyboardShortcuts(Context context) {
         this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_Material_Light);
+        if (SPECIAL_CHARACTER_NAMES.size() == 0) {
+            loadSpecialCharacterNames(context);
+        }
     }
 
-    public void toggleKeyboardShortcuts() {
+    public void toggleKeyboardShortcuts(int deviceId) {
+        InputDevice inputDevice = InputManager.getInstance().getInputDevice(deviceId);
+        if (inputDevice != null) {
+            mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+        }
         if (mKeyboardShortcutsDialog == null) {
             Recents.getSystemServices().requestKeyboardShortcuts(mContext,
                 new KeyboardShortcutsReceiver() {
@@ -75,20 +235,20 @@
                     public void onKeyboardShortcutsReceived(
                             final List<KeyboardShortcutGroup> result) {
                         KeyboardShortcutGroup systemGroup = new KeyboardShortcutGroup(
-                            mContext.getString(R.string.keyboard_shortcut_group_system), true);
+                                mContext.getString(R.string.keyboard_shortcut_group_system), true);
                         systemGroup.addItem(new KeyboardShortcutInfo(
-                            mContext.getString(R.string.keyboard_shortcut_group_system_home),
-                            SYSTEM_HOME_BASE_CHARACTER, KeyEvent.META_META_ON));
+                                mContext.getString(R.string.keyboard_shortcut_group_system_home),
+                                KeyEvent.KEYCODE_ENTER, KeyEvent.META_META_ON));
                         systemGroup.addItem(new KeyboardShortcutInfo(
-                            mContext.getString(R.string.keyboard_shortcut_group_system_back),
-                            SYSTEM_BACK_BASE_CHARACTER, KeyEvent.META_META_ON));
+                                mContext.getString(R.string.keyboard_shortcut_group_system_back),
+                                KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON));
                         systemGroup.addItem(new KeyboardShortcutInfo(
-                            mContext.getString(R.string.keyboard_shortcut_group_system_recents),
-                            SYSTEM_RECENTS_BASE_CHARACTER, KeyEvent.META_ALT_ON));
+                                mContext.getString(R.string.keyboard_shortcut_group_system_recents),
+                                KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON));
                         result.add(systemGroup);
                         showKeyboardShortcutsDialog(result);
                     }
-                });
+                }, deviceId);
         } else {
             dismissKeyboardShortcutsDialog();
         }
@@ -148,6 +308,18 @@
             final int itemsSize = group.getItems().size();
             for (int j = 0; j < itemsSize; j++) {
                 KeyboardShortcutInfo info = group.getItems().get(j);
+                if (info.getKeycode() != KeyEvent.KEYCODE_UNKNOWN
+                        && !KeyCharacterMap.deviceHasKey(info.getKeycode())) {
+                    // The user can't achieve this shortcut, so skipping.
+                    Log.w(TAG, "Keyboard Shortcut contains key not on device, skipping.");
+                    continue;
+                }
+                List<String> shortcutKeys = getHumanReadableShortcutKeys(info);
+                if (shortcutKeys == null) {
+                    // Ignore shortcuts we can't display keys for.
+                    Log.w(TAG, "Keyboard Shortcut contains unsupported keys, skipping.");
+                    continue;
+                }
                 View shortcutView = inflater.inflate(R.layout.keyboard_shortcut_app_item,
                         shortcutContainer, false);
                 TextView textView = (TextView) shortcutView
@@ -156,7 +328,6 @@
 
                 ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView
                         .findViewById(R.id.keyboard_shortcuts_item_container);
-                List<String> shortcutKeys = getHumanReadableShortcutKeys(info);
                 final int shortcutKeysSize = shortcutKeys.size();
                 for (int k = 0; k < shortcutKeysSize; k++) {
                     String shortcutKey = shortcutKeys.get(k);
@@ -178,11 +349,46 @@
     }
 
     private List<String> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
-        // TODO: fix the shortcuts. Find or build an util which can produce human readable
-        // names of the baseCharacter and the modifiers.
-        List<String> shortcutKeys = new ArrayList<>();
-        shortcutKeys.add(KeyEvent.metaStateToString(info.getModifiers()).toUpperCase());
-        shortcutKeys.add(Character.getName(info.getBaseCharacter()).toUpperCase());
+        List<String> shortcutKeys = getHumanReadableModifiers(info);
+        if (shortcutKeys == null) {
+            return null;
+        }
+        String displayLabelString;
+        if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
+            displayLabelString = String.valueOf(info.getBaseCharacter());
+        } else if (SPECIAL_CHARACTER_NAMES.get(info.getKeycode()) != null) {
+            displayLabelString = SPECIAL_CHARACTER_NAMES.get(info.getKeycode());
+        } else {
+            // TODO: Have a generic map for when we don't have the device's.
+            char displayLabel = mKeyCharacterMap == null
+                    ? 0 : mKeyCharacterMap.getDisplayLabel(info.getKeycode());
+            if (displayLabel != 0) {
+                displayLabelString = String.valueOf(displayLabel);
+            } else {
+                return null;
+            }
+        }
+        shortcutKeys.add(displayLabelString.toUpperCase());
+        return shortcutKeys;
+    }
+
+    private List<String> getHumanReadableModifiers(KeyboardShortcutInfo info) {
+        final List<String> shortcutKeys = new ArrayList<>();
+        int modifiers = info.getModifiers();
+        if (modifiers == 0) {
+            return shortcutKeys;
+        }
+        for(int i = 0; i < MODIFIER_NAMES.size(); ++i) {
+            final int supportedModifier = MODIFIER_NAMES.keyAt(i);
+            if ((modifiers & supportedModifier) != 0) {
+                shortcutKeys.add(MODIFIER_NAMES.get(supportedModifier).toUpperCase());
+                modifiers &= ~supportedModifier;
+            }
+        }
+        if (modifiers != 0) {
+            // Remaining unsupported modifiers, don't show anything.
+            return null;
+        }
         return shortcutKeys;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 0a41e42..f4fb0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -29,6 +29,7 @@
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
+import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridGroupManager;
@@ -787,13 +788,16 @@
                         ViewGroup.LayoutParams.MATCH_PARENT,
                         ViewGroup.LayoutParams.MATCH_PARENT)
                 );
+                existing = riv;
+            }
+            if (hasRemoteInput) {
                 int color = entry.notification.getNotification().color;
                 if (color == Notification.COLOR_DEFAULT) {
                     color = mContext.getColor(R.color.default_remote_input_background);
                 }
-                riv.setBackgroundColor(color);
-
-                return riv;
+                existing.setBackgroundColor(NotificationColorUtil.ensureTextBackgroundColor(color,
+                        mContext.getColor(R.color.remote_input_text),
+                        mContext.getColor(R.color.remote_input_hint)));
             }
             return existing;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index c32ef0e..726aed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.car;
 
 import android.app.ActivityManager;
-import android.app.ITaskStackListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -33,6 +32,7 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
@@ -40,9 +40,7 @@
  * A status bar (and navigation bar) tailored for the automotive use case.
  */
 public class CarStatusBar extends PhoneStatusBar {
-    private SystemServicesProxy mSystemServicesProxy;
     private TaskStackListenerImpl mTaskStackListener;
-    private Handler mHandler;
 
     private CarNavigationBarView mCarNavigationBar;
     private CarNavigationBarController mController;
@@ -51,10 +49,8 @@
     @Override
     public void start() {
         super.start();
-        mHandler = new Handler();
-        mTaskStackListener = new TaskStackListenerImpl(mHandler);
-        mSystemServicesProxy = new SystemServicesProxy(mContext);
-        mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+        mTaskStackListener = new TaskStackListenerImpl();
+        SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
         registerPackageChangeReceivers();
     }
 
@@ -114,47 +110,16 @@
     }
 
     /**
-     * An implementation of ITaskStackListener, that listens for changes in the system task
+     * An implementation of TaskStackListener, that listens for changes in the system task
      * stack and notifies the navigation bar.
      */
-    private class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
-        private Handler mHandler;
-
-        public TaskStackListenerImpl(Handler handler) {
-            this.mHandler = handler;
-        }
-
-        @Override
-        public void onActivityPinned() {
-        }
-
-        @Override
-        public void onPinnedActivityRestartAttempt() {
-        }
-
-        @Override
-        public void onPinnedStackAnimationEnded() {
-        }
-
+    private class TaskStackListenerImpl extends TaskStackListener {
         @Override
         public void onTaskStackChanged() {
-            mHandler.removeCallbacks(this);
-            mHandler.post(this);
-        }
-
-        @Override
-        public void run() {
-            ensureMainThread();
             SystemServicesProxy ssp = Recents.getSystemServices();
             ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
             mController.taskChanged(runningTaskInfo.baseActivity.getPackageName());
         }
-
-        private void ensureMainThread() {
-            if (!Looper.getMainLooper().isCurrentThread()) {
-                throw new RuntimeException("Must be called on the UI thread");
-            }
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
index 50ead3d..225751a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -27,14 +28,17 @@
     private static final String TAG = "IconMerger";
     private static final boolean DEBUG = false;
 
-    private int mIconSize;
+    private final int mIconSize;
+    private final int mIconHPadding;
+
     private View mMoreView;
 
     public IconMerger(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mIconSize = context.getResources().getDimensionPixelSize(
-                R.dimen.status_bar_icon_size);
+        Resources res = context.getResources();
+        mIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
 
         if (DEBUG) {
             setBackgroundColor(0x800099FF);
@@ -45,12 +49,16 @@
         mMoreView = v;
     }
 
+    private int getFullIconWidth() {
+        return mIconSize + 2 * mIconHPadding;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         // we need to constrain this to an integral multiple of our children
         int width = getMeasuredWidth();
-        setMeasuredDimension(width - (width % mIconSize), getMeasuredHeight());
+        setMeasuredDimension(width - (width % getFullIconWidth()), getMeasuredHeight());
     }
 
     @Override
@@ -70,7 +78,7 @@
         final boolean overflowShown = (mMoreView.getVisibility() == View.VISIBLE);
         // let's assume we have one more slot if the more icon is already showing
         if (overflowShown) visibleChildren --;
-        final boolean moreRequired = visibleChildren * mIconSize > width;
+        final boolean moreRequired = visibleChildren * getFullIconWidth() > width;
         if (moreRequired != overflowShown) {
             post(new Runnable() {
                 @Override
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 8225dab..54af684 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -130,9 +130,8 @@
             Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
                     getContext(), v, ContactsContract.Profile.CONTENT_URI,
                     ContactsContract.QuickContact.MODE_LARGE, null);
-            getContext().startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
             if (mQsPanel != null) {
-                mQsPanel.getHost().collapsePanels();
+                mQsPanel.getHost().startActivityDismissingKeyguard(intent);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index a605a81..c4917a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -17,6 +17,7 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A controller for the space in the status bar to the left of the system icons. This area is
@@ -42,6 +43,10 @@
         initializeNotificationAreaViews(context);
     }
 
+    protected View inflateIconArea(LayoutInflater inflater) {
+        return inflater.inflate(R.layout.notification_icon_area, null);
+    }
+
     /**
      * Initializes the views that will represent the notification area.
      */
@@ -51,14 +56,16 @@
         mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
 
         LayoutInflater layoutInflater = LayoutInflater.from(context);
-        mNotificationIconArea = layoutInflater.inflate(R.layout.notification_icon_area, null);
-
-        mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
-        mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+        mNotificationIconArea = inflateIconArea(layoutInflater);
 
         mNotificationIcons =
                 (IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons);
-        mNotificationIcons.setOverflowIndicator(mMoreIcon);
+
+        mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
+        if (mMoreIcon != null) {
+            mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+            mNotificationIcons.setOverflowIndicator(mMoreIcon);
+        }
     }
 
     /**
@@ -88,16 +95,38 @@
      */
     public void setIconTint(int iconTint) {
         mIconTint = iconTint;
-        mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+        if (mMoreIcon != null) {
+            mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+        }
         applyNotificationIconsTint();
     }
 
+    protected int getHeight() {
+        return mPhoneStatusBar.getStatusBarHeight();
+    }
+
+    protected boolean shouldShowNotification(NotificationData.Entry entry,
+            NotificationData notificationData) {
+        if (notificationData.isAmbient(entry.key)
+                && !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
+            return false;
+        }
+        if (!PhoneStatusBar.isTopLevelChild(entry)) {
+            return false;
+        }
+        if (entry.row.getVisibility() == View.GONE) {
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Updates the notifications with the given list of notifications to display.
      */
     public void updateNotificationIcons(NotificationData notificationData) {
         final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
-                mIconSize + 2 * mIconHPadding, mPhoneStatusBar.getStatusBarHeight());
+                mIconSize + 2 * mIconHPadding, getHeight());
 
         ArrayList<NotificationData.Entry> activeNotifications =
                 notificationData.getActiveNotifications();
@@ -107,17 +136,9 @@
         // Filter out ambient notifications and notification children.
         for (int i = 0; i < size; i++) {
             NotificationData.Entry ent = activeNotifications.get(i);
-            if (notificationData.isAmbient(ent.key)
-                    && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
-                continue;
+            if (shouldShowNotification(ent, notificationData)) {
+                toShow.add(ent.icon);
             }
-            if (!PhoneStatusBar.isTopLevelChild(ent)) {
-                continue;
-            }
-            if (ent.row.getVisibility() == View.GONE) {
-                continue;
-            }
-            toShow.add(ent.icon);
         }
 
         ArrayList<View> toRemove = new ArrayList<>();
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 e5e3caf..d9a0f43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -202,6 +202,7 @@
             notifyBarPanelExpansionChanged();
         }
     };
+    private NotificationGroupManager mGroupManager;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -415,6 +416,11 @@
             if (!(child instanceof ExpandableNotificationRow)) {
                 continue;
             }
+            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+                    ((ExpandableNotificationRow) child).getStatusBarNotification());
+            if (suppressedSummary) {
+                continue;
+            }
             availableSpace -= child.getMinHeight() + notificationPadding;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
@@ -1731,7 +1737,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mNavigationBarBottomHeight = insets.getSystemWindowInsetBottom();
+        mNavigationBarBottomHeight = insets.getStableInsetBottom();
         updateMaxHeadsUpTranslation();
         return insets;
     }
@@ -2298,4 +2304,8 @@
         List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
         return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
     }
+
+    public void setGroupManager(NotificationGroupManager groupManager) {
+        mGroupManager = groupManager;
+    }
 }
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 f0df706..960515b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -75,7 +75,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
+        setPadding(0, 0, 0, insets.getStableInsetBottom());
         return insets;
     }
 
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 e84d8fc..9b1f338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -48,7 +48,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
@@ -132,7 +131,6 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
@@ -305,7 +303,7 @@
     Point mCurrentDisplaySize = new Point();
 
     protected StatusBarWindowView mStatusBarWindow;
-    PhoneStatusBarView mStatusBarView;
+    protected PhoneStatusBarView mStatusBarView;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected StatusBarWindowManager mStatusBarWindowManager;
     private UnlockMethodCache mUnlockMethodCache;
@@ -317,7 +315,7 @@
     int mPixelFormat;
     Object mQueueLock = new Object();
 
-    StatusBarIconController mIconController;
+    protected StatusBarIconController mIconController;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -329,7 +327,7 @@
 
     // top bar
     BaseStatusBarHeader mHeader;
-    KeyguardStatusBarView mKeyguardStatusBar;
+    protected KeyguardStatusBarView mKeyguardStatusBar;
     View mKeyguardStatusView;
     KeyguardBottomAreaView mKeyguardBottomArea;
     boolean mLeaveOpenOnKeyguardHide;
@@ -678,6 +676,11 @@
         mFalsingManager = FalsingManager.getInstance(mContext);
     }
 
+    protected void createIconController() {
+        mIconController = new StatusBarIconController(
+                mContext, mStatusBarView, mKeyguardStatusBar, this);
+    }
+
     // ================================================================================
     // Constructing the view
     // ================================================================================
@@ -705,6 +708,7 @@
         mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
                 R.id.notification_panel);
         mNotificationPanel.setStatusBar(this);
+        mNotificationPanel.setGroupManager(mGroupManager);
 
         mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
         mStatusBarView.setBar(this);
@@ -811,8 +815,7 @@
         // set the initial view visibility
         setAreThereNotifications();
 
-        mIconController = new StatusBarIconController(
-                mContext, mStatusBarView, mKeyguardStatusBar, this);
+        createIconController();
 
         // Background thread for any controllers that need it.
         mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
@@ -1989,7 +1992,7 @@
         }
     }
 
-    private int adjustDisableFlags(int state) {
+    protected int adjustDisableFlags(int state) {
         if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway
                 && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
             state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index f61f31e..a40aa83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -42,6 +42,7 @@
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -116,8 +117,8 @@
         mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
         mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
 
-        mNotificationIconAreaController =
-                new NotificationIconAreaController(context, phoneStatusBar);
+        mNotificationIconAreaController = SystemUIFactory.getInstance()
+                .createNotificationIconAreaController(context, phoneStatusBar);
         mNotificationIconAreaInner =
                 mNotificationIconAreaController.getNotificationInnerAreaView();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index ada7450..b271380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -138,11 +138,7 @@
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         }
 
-        if (state.remoteInputActive) {
-            mLpChanged.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
-        } else {
-            mLpChanged.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-        }
+        mLpChanged.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
     }
 
     private void applyHeight(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 3a0336b..1f4ef4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -46,7 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.stack.LongPressCancelable;
+import com.android.systemui.statusbar.stack.ScrollContainer;
 
 /**
  * Host for the remote input.
@@ -67,7 +67,9 @@
     private RemoteInputController mController;
 
     private NotificationData.Entry mEntry;
-    private LongPressCancelable mLongPressCancelable;
+
+    private ScrollContainer mScrollContainer;
+    private View mScrollContainerChild;
 
     public RemoteInputView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -106,7 +108,7 @@
         mEditText.setOnClickListener(this);
         mEditText.addTextChangedListener(this);
         mEditText.setInnerFocusable(false);
-        mEditText.mDefocusListener = this;
+        mEditText.mRemoteInputView = this;
     }
 
     private void sendRemoteInput() {
@@ -237,23 +239,34 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if (mLongPressCancelable == null) {
-                ViewParent p = getParent();
-                while (p != null) {
-                    if (p instanceof LongPressCancelable) {
-                        mLongPressCancelable = (LongPressCancelable) p;
-                        break;
-                    }
-                    p = p.getParent();
-                }
-            }
-            if (mLongPressCancelable != null) {
-                mLongPressCancelable.requestDisallowLongPress();
+            findScrollContainer();
+            if (mScrollContainer != null) {
+                mScrollContainer.requestDisallowLongPress();
             }
         }
         return super.onInterceptTouchEvent(ev);
     }
 
+    public boolean requestScrollTo() {
+        findScrollContainer();
+        mScrollContainer.scrollTo(mScrollContainerChild);
+        return true;
+    }
+
+    private void findScrollContainer() {
+        if (mScrollContainer == null) {
+            ViewParent p = this;
+            while (p != null) {
+                if (p.getParent() instanceof ScrollContainer) {
+                    mScrollContainer = (ScrollContainer) p.getParent();
+                    mScrollContainerChild = (View) p;
+                    break;
+                }
+                p = p.getParent();
+            }
+        }
+    }
+
     /**
      * An EditText that changes appearance based on whether it's focusable and becomes
      * un-focusable whenever the user navigates away from it or it becomes invisible.
@@ -261,7 +274,7 @@
     public static class RemoteEditText extends EditText {
 
         private final Drawable mBackground;
-        private RemoteInputView mDefocusListener;
+        private RemoteInputView mRemoteInputView;
         boolean mShowImeOnInputConnection;
 
         public RemoteEditText(Context context, AttributeSet attrs) {
@@ -270,13 +283,13 @@
         }
 
         private void defocusIfNeeded() {
-            if (mDefocusListener.mEntry.row.isChangingPosition()) {
+            if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()) {
                 return;
             }
             if (isFocusable() && isEnabled()) {
                 setInnerFocusable(false);
-                if (mDefocusListener != null) {
-                    mDefocusListener.onDefocus();
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
                 }
                 mShowImeOnInputConnection = false;
             }
@@ -300,13 +313,6 @@
         }
 
         @Override
-        public boolean requestRectangleOnScreen(Rect r) {
-            r.top = mScrollY;
-            r.bottom = mScrollY + (mBottom - mTop);
-            return super.requestRectangleOnScreen(r);
-        }
-
-        @Override
         public void getFocusedRect(Rect r) {
             super.getFocusedRect(r);
             r.top = mScrollY;
@@ -314,6 +320,11 @@
         }
 
         @Override
+        public boolean requestRectangleOnScreen(Rect rectangle) {
+            return mRemoteInputView.requestScrollTo();
+        }
+
+        @Override
         public boolean onKeyPreIme(int keyCode, KeyEvent event) {
             if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
                 defocusIfNeeded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index bc276b7..b5030e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -43,6 +43,7 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.OverScroller;
@@ -83,7 +84,7 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        SettingsIconRowListener, LongPressCancelable {
+        SettingsIconRowListener, ScrollContainer {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -134,7 +135,7 @@
     private int mPaddingBetweenElements;
     private int mIncreasedPaddingBetweenElements;
     private int mTopPadding;
-    private int mCollapseSecondCardPadding;
+    private int mBottomInset = 0;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -206,6 +207,7 @@
     private float mStackTranslation;
     private float mTopPaddingOverflow;
     private boolean mDontReportNextOverScroll;
+    private boolean mDontClampNextScroll;
     private boolean mRequestViewResizeAnimationOnLayout;
     private boolean mNeedViewResizeAnimation;
     private View mExpandedGroupView;
@@ -919,6 +921,45 @@
         mScrollingEnabled = enable;
     }
 
+    public void scrollTo(View v) {
+        ExpandableView expandableView = (ExpandableView) v;
+        int positionInLinearLayout = getPositionInLinearLayout(v);
+
+        int targetScroll = positionInLinearLayout + expandableView.getActualHeight() +
+                mBottomInset - getHeight() + getTopPadding();
+        if (mOwnScrollY < targetScroll) {
+            mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+            mDontReportNextOverScroll = true;
+            postInvalidateOnAnimation();
+        }
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mBottomInset = Math.max(0, insets.getSystemWindowInsetBottom()
+                - (getRootView().getHeight() - getHeight()));
+
+        int range = getScrollRange();
+        if (mOwnScrollY > range) {
+            // HACK: We're repeatedly getting staggered insets here while the IME is
+            // animating away. To work around that we'll wait until things have settled.
+            removeCallbacks(mReclamp);
+            postDelayed(mReclamp, 50);
+        }
+        return insets;
+    }
+
+    private Runnable mReclamp = new Runnable() {
+        @Override
+        public void run() {
+            int range = getScrollRange();
+            mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+            mDontReportNextOverScroll = true;
+            mDontClampNextScroll = true;
+            postInvalidateOnAnimation();
+        }
+    };
+
     public void setExpandingEnabled(boolean enable) {
         mExpandHelper.setEnabled(enable);
     }
@@ -1256,7 +1297,7 @@
             int y = mScroller.getCurrY();
 
             if (oldX != x || oldY != y) {
-                final int range = getScrollRange();
+                int range = getScrollRange();
                 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
                     float currVelocity = mScroller.getCurrVelocity();
                     if (currVelocity >= mMinimumVelocity) {
@@ -1264,6 +1305,9 @@
                     }
                 }
 
+                if (mDontClampNextScroll) {
+                    range = Math.max(range, oldY);
+                }
                 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
                         0, (int) (mMaxOverScroll), false);
                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
@@ -1271,6 +1315,8 @@
 
             // Keep on drawing until the animation has finished.
             postInvalidateOnAnimation();
+        } else {
+            mDontClampNextScroll = false;
         }
     }
 
@@ -1474,7 +1520,9 @@
                         - firstChild.getMinHeight());
             }
         }
-        return scrollRange;
+        int imeOverlap = Math.max(0,
+                getContentHeight() - (getHeight() - mBottomInset));
+        return scrollRange + imeOverlap;
     }
 
     /**
@@ -2015,6 +2063,14 @@
         }
     }
 
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+        if (disallowIntercept) {
+            mSwipeHelper.removeLongPressCallback();
+        }
+    }
+
     private void onViewRemovedInternal(View child) {
         if (mChangePositionInProgress) {
             // This is only a position change, don't do anything special
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/LongPressCancelable.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/statusbar/stack/LongPressCancelable.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
index 05f0c07..a35465e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/LongPressCancelable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
@@ -16,13 +16,21 @@
 
 package com.android.systemui.statusbar.stack;
 
+import android.view.View;
+
 /**
- * Interface for container layouts that listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic.
+ * Interface for container layouts that scroll and listen for long presses. A child that
+ * wants to handle long press can use this to cancel the parents long press logic or request
+ * to be made visible by scrolling to it.
  */
-public interface LongPressCancelable {
+public interface ScrollContainer {
     /**
      * Request that the view does not perform long press for the current touch.
      */
     void requestDisallowLongPress();
+
+    /**
+     * Request that the view is made visible by scrolling to it.
+     */
+    void scrollTo(View v);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
new file mode 100644
index 0000000..0e64dcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 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.tv.pip;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.PlaybackState;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+import android.util.AttributeSet;
+
+import com.android.systemui.R;
+
+import static android.media.session.PlaybackState.ACTION_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PLAY;
+
+import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PLAYING;
+import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PAUSED;
+import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE;
+
+
+/**
+ * A view containing PIP controls including fullscreen, close, and media controls.
+ */
+public class PipControlsView extends LinearLayout implements PipManager.Listener {
+    /**
+     * An interface to listen user action.
+     */
+    public interface Listener {
+        /**
+         * Called when an user clicks close PIP button.
+         */
+        void onClosed();
+    }
+
+    private final PipManager mPipManager = PipManager.getInstance();
+    private MediaController mMediaController;
+    private Listener mListener;
+
+    private View mFullButtonView;
+    private View mFullDescriptionView;
+    private View mPlayPauseView;
+    private ImageView mPlayPauseButtonImageView;
+    private TextView mPlayPauseDescriptionTextView;
+    private View mCloseButtonView;
+    private View mCloseDescriptionView;
+
+    private boolean mHasFocus;
+    private OnFocusChangeListener mOnChildFocusChangeListener;
+
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            updatePlayPauseView();
+        }
+    };
+
+    public PipControlsView(Context context) {
+        this(context, null, 0, 0);
+    }
+
+    public PipControlsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0, 0);
+    }
+
+    public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+
+        mFullButtonView = findViewById(R.id.full_button);
+        mFullDescriptionView = findViewById(R.id.full_desc);
+        mFullButtonView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPipManager.movePipToFullscreen();
+            }
+        });
+        mFullButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                mFullDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
+                onChildViewFocusChanged();
+            }
+        });
+
+        mPlayPauseView = findViewById(R.id.play_pause);
+        mPlayPauseButtonImageView = (ImageView) findViewById(R.id.play_pause_button);
+        mPlayPauseDescriptionTextView = (TextView) findViewById(R.id.play_pause_desc);
+        mPlayPauseButtonImageView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mMediaController == null || mMediaController.getPlaybackState() == null) {
+                    return;
+                }
+                long actions = mMediaController.getPlaybackState().getActions();
+                int state = mMediaController.getPlaybackState().getState();
+                if (mPipManager.getPlaybackState() == PLAYBACK_STATE_PAUSED) {
+                    mMediaController.getTransportControls().play();
+                } else if (mPipManager.getPlaybackState() == PLAYBACK_STATE_PLAYING) {
+                    mMediaController.getTransportControls().pause();
+                }
+                // View will be updated later in {@link mMediaControllerCallback}
+            }
+        });
+        mPlayPauseButtonImageView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                mPlayPauseDescriptionTextView.setVisibility(
+                        hasFocus ? View.VISIBLE : View.INVISIBLE);
+                onChildViewFocusChanged();
+            }
+        });
+
+        mCloseButtonView = findViewById(R.id.close_button);
+        mCloseDescriptionView = findViewById(R.id.close_desc);
+        mCloseButtonView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPipManager.closePip();
+                mListener.onClosed();
+            }
+        });
+        mCloseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                mCloseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
+                onChildViewFocusChanged();
+            }
+        });
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateMediaController();
+        mPipManager.addListener(this);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mPipManager.removeListener(this);
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mMediaControllerCallback);
+        }
+    }
+
+    private void updateMediaController() {
+        MediaController newController = mPipManager.getMediaController();
+        if (mMediaController == newController) {
+            return;
+        }
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mMediaControllerCallback);
+        }
+        mMediaController = newController;
+        if (mMediaController != null) {
+            mMediaController.registerCallback(mMediaControllerCallback);
+        }
+        updatePlayPauseView();
+    }
+
+    private void updatePlayPauseView() {
+        int state = mPipManager.getPlaybackState();
+        if (state == PLAYBACK_STATE_UNAVAILABLE) {
+            mPlayPauseView.setVisibility(View.GONE);
+        } else {
+            mPlayPauseView.setVisibility(View.VISIBLE);
+            if (state == PLAYBACK_STATE_PLAYING) {
+                mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_pause_button);
+                mPlayPauseDescriptionTextView.setText(R.string.pip_pause);
+            } else {
+                mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_play_button);
+                mPlayPauseDescriptionTextView.setText(R.string.pip_play);
+            }
+        }
+    }
+
+    /**
+     * Sets a listener to be invoked when {@link android.view.View.hasFocus()} is changed.
+     */
+    public void setOnChildFocusChangeListener(OnFocusChangeListener listener) {
+        mOnChildFocusChangeListener = listener;
+    }
+
+    private void onChildViewFocusChanged() {
+        // At this moment, hasFocus() returns true although there's no focused child.
+        boolean hasFocus = (mFullButtonView != null && mFullButtonView.isFocused())
+                || (mPlayPauseButtonImageView != null && mPlayPauseButtonImageView.isFocused())
+                || (mCloseButtonView != null && mCloseButtonView.isFocused());
+        if (mHasFocus != hasFocus) {
+            mHasFocus = hasFocus;
+            if (mOnChildFocusChangeListener != null) {
+                mOnChildFocusChangeListener.onFocusChange(getFocusedChild(), mHasFocus);
+            }
+        }
+    }
+
+    /**
+     * Sets the {@link Listener} to listen user actions.
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onPipEntered() { }
+
+    @Override
+    public void onPipActivityClosed() { }
+
+    @Override
+    public void onShowPipMenu() { }
+
+    @Override
+    public void onMoveToFullscreen() { }
+
+    @Override
+    public void onMediaControllerChanged() {
+        updateMediaController();
+    }
+
+    @Override
+    public void onPipResizeAboutToStart() { }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 95cee4c..68e0883 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -21,7 +21,6 @@
 import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
 import android.app.IActivityManager;
-import android.app.ITaskStackListener;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,6 +30,7 @@
 import android.graphics.Rect;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -38,6 +38,9 @@
 import android.util.Log;
 
 import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -69,12 +72,23 @@
     public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
     public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
 
+    /**
+     * PIPed activity is playing a media and it can be paused.
+     */
+    static final int PLAYBACK_STATE_PLAYING = 0;
+    /**
+     * PIPed activity has a paused media and it can be played.
+     */
+    static final int PLAYBACK_STATE_PAUSED = 1;
+    /**
+     * Users are unable to control PIPed activity's media playback.
+     */
+    static final int PLAYBACK_STATE_UNAVAILABLE = 2;
+
     private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
 
     private int mSuspendPipResizingReason;
 
-    private static final float SCALE_FACTOR = 1.1f;
-
     private Context mContext;
     private IActivityManager mActivityManager;
     private MediaSessionManager mMediaSessionManager;
@@ -86,6 +100,7 @@
     private Rect mMenuModePipBounds;
     private Rect mRecentsPipBounds;
     private Rect mRecentsFocusedPipBounds;
+    private int mRecentsFocusChangedAnimationDurationMs;
     private boolean mInitialized;
     private int mPipTaskId = TASK_ID_NO_PIP;
     private ComponentName mPipComponentName;
@@ -95,89 +110,6 @@
     private boolean mIsRecentsShown;
     private boolean mIsPipFocusedInRecent;
 
-    private final Runnable mOnActivityPinnedRunnable = new Runnable() {
-        @Override
-        public void run() {
-            StackInfo stackInfo = null;
-            try {
-                stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
-                if (stackInfo == null) {
-                    Log.w(TAG, "Cannot find pinned stack");
-                    return;
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "getStackInfo failed", e);
-                return;
-            }
-            if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
-            mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
-            mPipComponentName = ComponentName.unflattenFromString(
-                    stackInfo.taskNames[stackInfo.taskNames.length - 1]);
-            // Set state to overlay so we show it when the pinned stack animation ends.
-            mState = STATE_PIP_OVERLAY;
-            mCurrentPipBounds = mPipBounds;
-            launchPipOnboardingActivityIfNeeded();
-            mMediaSessionManager.addOnActiveSessionsChangedListener(
-                    mActiveMediaSessionListener, null);
-            updateMediaController(mMediaSessionManager.getActiveSessions(null));
-            if (mIsRecentsShown) {
-                // If an activity becomes PIPed again after the fullscreen, the Recents is shown
-                // behind so we need to resize the pinned stack and show the correct overlay.
-                resizePinnedStack(STATE_PIP_OVERLAY);
-            }
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onPipEntered();
-            }
-        }
-    };
-    private final Runnable mOnTaskStackChanged = new Runnable() {
-        @Override
-        public void run() {
-            if (mState != STATE_NO_PIP) {
-                StackInfo stackInfo = null;
-                try {
-                    stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
-                    if (stackInfo == null) {
-                        Log.w(TAG, "There is no pinned stack");
-                        closePipInternal(false);
-                        return;
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "getStackInfo failed", e);
-                    return;
-                }
-                for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
-                    if (stackInfo.taskIds[i] == mPipTaskId) {
-                        // PIP task is still alive.
-                        return;
-                    }
-                }
-                // PIP task doesn't exist anymore in PINNED_STACK.
-                closePipInternal(true);
-            }
-        }
-    };
-    private final Runnable mOnPinnedActivityRestartAttempt = new Runnable() {
-        @Override
-        public void run() {
-            // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
-            movePipToFullscreen();
-        }
-    };
-    private final Runnable mOnPinnedStackAnimationEnded = new Runnable() {
-        @Override
-        public void run() {
-            switch (mState) {
-                case STATE_PIP_OVERLAY:
-                    showPipOverlay();
-                    break;
-                case STATE_PIP_MENU:
-                    showPipMenu();
-                    break;
-            }
-        }
-    };
-
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
         public void run() {
@@ -230,24 +162,16 @@
         mPipBounds = Rect.unflattenFromString(res.getString(
                 com.android.internal.R.string.config_defaultPictureInPictureBounds));
         mMenuModePipBounds = Rect.unflattenFromString(res.getString(
-                com.android.internal.R.string.config_centeredPictureInPictureBounds));
+                R.string.pip_menu_bounds));
         mRecentsPipBounds = Rect.unflattenFromString(res.getString(
-                com.android.internal.R.string.config_pictureInPictureBoundsInRecents));
-        float scaleBy = (SCALE_FACTOR - 1.0f) / 2;
-        mRecentsFocusedPipBounds = new Rect(
-                (int) (mRecentsPipBounds.left - scaleBy * mRecentsPipBounds.width()),
-                (int) (mRecentsPipBounds.top - scaleBy * mRecentsPipBounds.height()),
-                (int) (mRecentsPipBounds.right + scaleBy * mRecentsPipBounds.width()),
-                (int) (mRecentsPipBounds.bottom + scaleBy * mRecentsPipBounds.height()));
+                R.string.pip_recents_bounds));
+        mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString(
+                R.string.pip_recents_focused_bounds));
+        mRecentsFocusChangedAnimationDurationMs = res.getInteger(
+                R.integer.recents_tv_pip_focus_anim_duration);
 
         mActivityManager = ActivityManagerNative.getDefault();
-        TaskStackListener taskStackListener = new TaskStackListener();
-        IActivityManager iam = ActivityManagerNative.getDefault();
-        try {
-            iam.registerTaskStackListener(taskStackListener);
-        } catch (RemoteException e) {
-            Log.e(TAG, "registerTaskStackListener failed", e);
-        }
+        SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
         mContext.registerReceiver(mBroadcastReceiver, intentFilter);
@@ -367,6 +291,7 @@
                             mSuspendPipResizingReason);
             return;
         }
+        int animationDurationMs = -1;
         switch (mState) {
             case STATE_NO_PIP:
                 mCurrentPipBounds = null;
@@ -376,6 +301,10 @@
                 break;
             case STATE_PIP_OVERLAY:
                 if (mIsRecentsShown) {
+                    if (mCurrentPipBounds == mRecentsFocusedPipBounds
+                            || mCurrentPipBounds == mRecentsFocusedPipBounds) {
+                        animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+                    }
                     if (mIsPipFocusedInRecent) {
                         mCurrentPipBounds = mRecentsFocusedPipBounds;
                     } else {
@@ -390,7 +319,8 @@
                 break;
         }
         try {
-            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true, -1);
+            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+                    true, true, true, animationDurationMs);
         } catch (RemoteException e) {
             Log.e(TAG, "resizeStack failed", e);
         }
@@ -452,9 +382,17 @@
     }
 
     /**
+     * Returns {@code true} if the PIP view in
+     * {@link com.android.systemui.recents.tv.RecentsTvActivity} is focused in Recents.
+     * This API is valid only when {@link isRecentsShown()} returns {@code true}.
+     */
+    boolean isPipViewFocusdInRecents() {
+        return mIsPipFocusedInRecent;
+    }
+
+    /**
      * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
-     * stack to the centered PIP bound {@link com.android.internal.R.string
-     * .config_centeredPictureInPictureBounds}.
+     * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
      */
     private void showPipMenu() {
         if (DEBUG) Log.d(TAG, "showPipMenu()");
@@ -566,33 +504,114 @@
         return mPipMediaController;
     }
 
-    private class TaskStackListener extends ITaskStackListener.Stub {
+    /**
+     * Returns the PIPed activity's playback state.
+     * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
+     * or {@link PLAYBACK_STATE_UNAVAILABLE}.
+     */
+    int getPlaybackState() {
+        if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
+            return PLAYBACK_STATE_UNAVAILABLE;
+        }
+        int state = mPipMediaController.getPlaybackState().getState();
+        boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
+                || state == PlaybackState.STATE_CONNECTING
+                || state == PlaybackState.STATE_PLAYING
+                || state == PlaybackState.STATE_FAST_FORWARDING
+                || state == PlaybackState.STATE_REWINDING
+                || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
+                || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
+        long actions = mPipMediaController.getPlaybackState().getActions();
+        if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
+            return PLAYBACK_STATE_PAUSED;
+        } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
+            return PLAYBACK_STATE_PLAYING;
+        }
+        return PLAYBACK_STATE_UNAVAILABLE;
+    }
+
+    TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
-        public void onTaskStackChanged() throws RemoteException {
-            // Post the message back to the UI thread.
-            mHandler.post(mOnTaskStackChanged);
+        public void onTaskStackChanged() {
+            if (mState != STATE_NO_PIP) {
+                StackInfo stackInfo = null;
+                try {
+                    stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                    if (stackInfo == null) {
+                        Log.w(TAG, "There is no pinned stack");
+                        closePipInternal(false);
+                        return;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "getStackInfo failed", e);
+                    return;
+                }
+                for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
+                    if (stackInfo.taskIds[i] == mPipTaskId) {
+                        // PIP task is still alive.
+                        return;
+                    }
+                }
+                // PIP task doesn't exist anymore in PINNED_STACK.
+                closePipInternal(true);
+            }
         }
 
         @Override
-        public void onActivityPinned()  throws RemoteException {
-            // Post the message back to the UI thread.
+        public void onActivityPinned() {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
-            mHandler.post(mOnActivityPinnedRunnable);
+            StackInfo stackInfo = null;
+            try {
+                stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                if (stackInfo == null) {
+                    Log.w(TAG, "Cannot find pinned stack");
+                    return;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "getStackInfo failed", e);
+                return;
+            }
+            if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+            mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
+            mPipComponentName = ComponentName.unflattenFromString(
+                    stackInfo.taskNames[stackInfo.taskNames.length - 1]);
+            // Set state to overlay so we show it when the pinned stack animation ends.
+            mState = STATE_PIP_OVERLAY;
+            mCurrentPipBounds = mPipBounds;
+            launchPipOnboardingActivityIfNeeded();
+            mMediaSessionManager.addOnActiveSessionsChangedListener(
+                    mActiveMediaSessionListener, null);
+            updateMediaController(mMediaSessionManager.getActiveSessions(null));
+            if (mIsRecentsShown) {
+                // If an activity becomes PIPed again after the fullscreen, the Recents is shown
+                // behind so we need to resize the pinned stack and show the correct overlay.
+                resizePinnedStack(STATE_PIP_OVERLAY);
+            }
+            for (int i = mListeners.size() - 1; i >= 0; i--) {
+                mListeners.get(i).onPipEntered();
+            }
         }
 
         @Override
         public void onPinnedActivityRestartAttempt() {
-            // Post the message back to the UI thread.
             if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-            mHandler.post(mOnPinnedActivityRestartAttempt);
+            // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
+            movePipToFullscreen();
         }
 
         @Override
         public void onPinnedStackAnimationEnded() {
             if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
-            mHandler.post(mOnPinnedStackAnimationEnded);
+            switch (mState) {
+                case STATE_PIP_OVERLAY:
+                    showPipOverlay();
+                    break;
+                case STATE_PIP_MENU:
+                    showPipMenu();
+                    break;
+            }
         }
-    }
+    };
 
     /**
      * A listener interface to receive notification on changes in PIP.
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index b8b837a..ea9275f 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -17,12 +17,7 @@
 package com.android.systemui.tv.pip;
 
 import android.app.Activity;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
 import android.os.Bundle;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -31,8 +26,6 @@
 
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-import static android.media.session.PlaybackState.ACTION_PAUSE;
-import static android.media.session.PlaybackState.ACTION_PLAY;
 
 /**
  * Activity to show the PIP menu to control PIP.
@@ -41,131 +34,17 @@
     private static final String TAG = "PipMenuActivity";
 
     private final PipManager mPipManager = PipManager.getInstance();
-    private MediaController mMediaController;
 
-    private View mFullButtonView;
-    private View mFullDescriptionView;
-    private View mPlayPauseView;
-    private ImageView mPlayPauseButtonImageView;
-    private TextView mPlayPauseDescriptionTextView;
-    private View mCloseButtonView;
-    private View mCloseDescriptionView;
+    private PipControlsView mPipControlsView;
     private boolean mPipMovedToFullscreen;
 
-    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            updatePlayPauseView(state);
-        }
-    };
-
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
         setContentView(R.layout.tv_pip_menu);
         mPipManager.addListener(this);
-        mFullButtonView = findViewById(R.id.full_button);
-        mFullDescriptionView = findViewById(R.id.full_desc);
-        mFullButtonView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mPipManager.movePipToFullscreen();
-                mPipMovedToFullscreen = true;
-                finish();
-            }
-        });
-        mFullButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                mFullDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
-            }
-        });
 
-        mPlayPauseView = findViewById(R.id.play_pause);
-        mPlayPauseButtonImageView = (ImageView) findViewById(R.id.play_pause_button);
-        mPlayPauseDescriptionTextView = (TextView) findViewById(R.id.play_pause_desc);
-        mPlayPauseButtonImageView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (mMediaController == null || mMediaController.getPlaybackState() == null) {
-                    return;
-                }
-                long actions = mMediaController.getPlaybackState().getActions();
-                int state = mMediaController.getPlaybackState().getState();
-                if (((actions & ACTION_PLAY) != 0) && !isPlaying(state)) {
-                    mMediaController.getTransportControls().play();
-                } else if ((actions & ACTION_PAUSE) != 0 && isPlaying(state)) {
-                    mMediaController.getTransportControls().pause();
-                }
-                // View will be updated later in {@link mMediaControllerCallback}
-            }
-        });
-        mPlayPauseButtonImageView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                mPlayPauseDescriptionTextView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
-            }
-        });
-
-        mCloseButtonView = findViewById(R.id.close_button);
-        mCloseDescriptionView = findViewById(R.id.close_desc);
-        mCloseButtonView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mPipManager.closePip();
-                finish();
-            }
-        });
-        mCloseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                mCloseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
-            }
-        });
-        updateMediaController();
-    }
-
-    private void updateMediaController() {
-        MediaController newController = mPipManager.getMediaController();
-        if (mMediaController == newController) {
-            return;
-        }
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaControllerCallback);
-        }
-        mMediaController = newController;
-        if (mMediaController != null) {
-            mMediaController.registerCallback(mMediaControllerCallback);
-            updatePlayPauseView(mMediaController.getPlaybackState());
-        } else {
-            updatePlayPauseView(null);
-        }
-    }
-
-    private void updatePlayPauseView(PlaybackState playbackState) {
-        if (playbackState != null
-                && (playbackState.getActions() & (ACTION_PLAY | ACTION_PAUSE)) != 0) {
-            mPlayPauseView.setVisibility(View.VISIBLE);
-            if (isPlaying(playbackState.getState())) {
-                mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_pause_button);
-                mPlayPauseDescriptionTextView.setText(R.string.pip_pause);
-            } else {
-                mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_play_button);
-                mPlayPauseDescriptionTextView.setText(R.string.pip_play);
-            }
-        } else {
-            mPlayPauseView.setVisibility(View.GONE);
-        }
-    }
-
-    private boolean isPlaying(int state) {
-        return state == PlaybackState.STATE_BUFFERING
-                || state == PlaybackState.STATE_CONNECTING
-                || state == PlaybackState.STATE_PLAYING
-                || state == PlaybackState.STATE_FAST_FORWARDING
-                || state == PlaybackState.STATE_REWINDING
-                || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
-                || state == PlaybackState.STATE_SKIPPING_TO_NEXT;
+        mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls);
     }
 
     private void restorePipAndFinish() {
@@ -184,9 +63,6 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaControllerCallback);
-        }
         mPipManager.removeListener(this);
         mPipManager.resumePipResizing(
                 PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
@@ -210,13 +86,12 @@
 
     @Override
     public void onMoveToFullscreen() {
+        mPipMovedToFullscreen = true;
         finish();
     }
 
     @Override
-    public void onMediaControllerChanged() {
-        updateMediaController();
-    }
+    public void onMediaControllerChanged() { }
 
     @Override
     public void onPipResizeAboutToStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index 1de321d..12cb4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.View;
+import android.widget.ImageView;
 
 import com.android.systemui.R;
 
@@ -46,9 +47,10 @@
     private final Handler mHandler = new Handler();
     private View mGuideOverlayView;
     private View mGuideButtonsView;
+    private ImageView mGuideButtonPlayPauseImageView;
     private final Runnable mHideGuideOverlayRunnable = new Runnable() {
         public void run() {
-            mGuideOverlayView.setVisibility(View.INVISIBLE);
+            mGuideOverlayView.setVisibility(View.GONE);
         }
     };
 
@@ -71,6 +73,7 @@
         setContentView(R.layout.tv_pip_overlay);
         mGuideOverlayView = findViewById(R.id.guide_overlay);
         mGuideButtonsView = findViewById(R.id.guide_buttons);
+        mGuideButtonPlayPauseImageView = (ImageView) findViewById(R.id.guide_button_play_pause);
         mPipManager.addListener(this);
 
         sPipOverlayActivity = this;
@@ -80,12 +83,17 @@
     protected void onResume() {
         super.onResume();
         // TODO: Implement animation for this
-        if (!mPipManager.isRecentsShown()) {
-            mGuideOverlayView.setVisibility(View.VISIBLE);
-            mGuideButtonsView.setVisibility(View.INVISIBLE);
+        if (mPipManager.isRecentsShown()) {
+            mGuideOverlayView.setVisibility(View.GONE);
+            if (mPipManager.isPipViewFocusdInRecents()) {
+                mGuideButtonsView.setVisibility(View.GONE);
+            } else {
+                mGuideButtonsView.setVisibility(View.VISIBLE);
+                updateGuideButtonsView();
+            }
         } else {
-            mGuideOverlayView.setVisibility(View.INVISIBLE);
-            mGuideButtonsView.setVisibility(View.VISIBLE);
+            mGuideOverlayView.setVisibility(View.VISIBLE);
+            mGuideButtonsView.setVisibility(View.GONE);
         }
         mHandler.removeCallbacks(mHideGuideOverlayRunnable);
         mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
@@ -134,11 +142,30 @@
     }
 
     @Override
-    public void onMediaControllerChanged() { }
+    public void onMediaControllerChanged() {
+        updateGuideButtonsView();
+    }
 
     @Override
     public void finish() {
         sPipOverlayActivity = null;
         super.finish();
     }
+
+    private void updateGuideButtonsView() {
+        switch (mPipManager.getPlaybackState()) {
+            case PipManager.PLAYBACK_STATE_PLAYING:
+                mGuideButtonPlayPauseImageView.setVisibility(View.VISIBLE);
+                mGuideButtonPlayPauseImageView.setImageResource(R.drawable.ic_pause_white_24dp);
+                break;
+            case PipManager.PLAYBACK_STATE_PAUSED:
+                mGuideButtonPlayPauseImageView.setVisibility(View.VISIBLE);
+                mGuideButtonPlayPauseImageView.setImageResource(
+                        R.drawable.ic_play_arrow_white_24dp);
+                break;
+            case PipManager.PLAYBACK_STATE_UNAVAILABLE:
+                mGuideButtonPlayPauseImageView.setVisibility(View.GONE);
+                break;
+        }
+    }
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d929519..d6f1499 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -1922,10 +1922,12 @@
 
     // User granted access to the request folder; action takes an integer
     // representing the folder's index on Environment.STANDARD_DIRECTORIES
+    // (or -2 for root access, or -1 or unknown directory).
     ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER = 326;
 
     // User denied access to the request folder; action takes an integer
     // representing the folder's index on Environment.STANDARD_DIRECTORIES
+    // (or -2 for root access, or -1 or unknown directory).
     ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER = 327;
 
     // User granted access to the request folder; action pass package name
@@ -1939,6 +1941,7 @@
     // App requested access to a directory it has already been granted
     // access before; action takes an integer representing the folder's
     // index on Environment.STANDARD_DIRECTORIES
+    // (or -2 for root access, or -1 or unknown directory).
     ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER = 330;
 
     // App requested access to a directory it has already been granted
@@ -1998,6 +2001,7 @@
 
     // User already denied access to the request folder; action takes an integer
     // representing the folder's index on Environment.STANDARD_DIRECTORIES
+    // (or -2 for root access, or -1 or unknown directory).
     ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER = 353;
 
     // User already denied access to the request folder; action pass package name
@@ -2006,6 +2010,7 @@
 
     // User denied access to the request folder and checked 'Do not ask again';
     // action takes an integer representing the folder's index on Environment.STANDARD_DIRECTORIES
+    // (or -2 for root access, or -1 or unknown directory).
     ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER = 355;
 
     // User denied access to the request folder and checked 'Do not ask again';
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e710ded..e3dac28 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1812,6 +1812,87 @@
         return mKeyEventDispatcher;
     }
 
+    /**
+     * Enables accessibility service specified by {@param componentName} for the {@param userId}.
+     */
+    public void enableAccessibilityService(ComponentName componentName, int userId) {
+        synchronized(mLock) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("only SYSTEM can call enableAccessibilityService.");
+            }
+
+            SettingsStringHelper settingsHelper = new SettingsStringHelper(
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
+            settingsHelper.addService(componentName);
+            settingsHelper.writeToSettings();
+
+            UserState userState = getUserStateLocked(userId);
+            if (userState.mEnabledServices.add(componentName)) {
+                onUserStateChangedLocked(userState);
+            }
+        }
+    }
+
+    /**
+     * Disables accessibility service specified by {@param componentName} for the {@param userId}.
+     */
+    public void disableAccessibilityService(ComponentName componentName, int userId) {
+        synchronized(mLock) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("only SYSTEM can call disableAccessibility");
+            }
+
+            SettingsStringHelper settingsHelper = new SettingsStringHelper(
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
+            settingsHelper.deleteService(componentName);
+            settingsHelper.writeToSettings();
+
+            UserState userState = getUserStateLocked(userId);
+            if (userState.mEnabledServices.remove(componentName)) {
+                onUserStateChangedLocked(userState);
+            }
+        }
+    }
+
+    private class SettingsStringHelper {
+        private static final String SETTINGS_DELIMITER = ":";
+        private ContentResolver mContentResolver;
+        private final String mSettingsName;
+        private Set<String> mServices;
+        private final int mUserId;
+
+        public SettingsStringHelper(String name, int userId) {
+            mUserId = userId;
+            mSettingsName = name;
+            mContentResolver = mContext.getContentResolver();
+            String servicesString = Settings.Secure.getStringForUser(
+                    mContentResolver, mSettingsName, userId);
+            mServices = new HashSet();
+            if (!TextUtils.isEmpty(servicesString)) {
+                final TextUtils.SimpleStringSplitter colonSplitter =
+                        new TextUtils.SimpleStringSplitter(SETTINGS_DELIMITER.charAt(0));
+                colonSplitter.setString(servicesString);
+                while (colonSplitter.hasNext()) {
+                    final String serviceName = colonSplitter.next();
+                    mServices.add(serviceName);
+                }
+            }
+        }
+
+        public void addService(ComponentName component) {
+            mServices.add(component.flattenToString());
+        }
+
+        public void deleteService(ComponentName component) {
+            mServices.remove(component.flattenToString());
+        }
+
+        public void writeToSettings() {
+            Settings.Secure.putStringForUser(mContentResolver, mSettingsName,
+                    TextUtils.join(SETTINGS_DELIMITER, mServices), mUserId);
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5b01062..95d3cc3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -71,6 +71,7 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.Uri;
+import android.net.metrics.ConnectivityServiceChangeEvent;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -125,7 +126,6 @@
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.PacManager;
 import com.android.server.connectivity.PermissionMonitor;
-import com.android.server.connectivity.ApfFilter;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
@@ -354,13 +354,6 @@
      */
     private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
 
-    /**
-     * used to push APF program to NetworkAgent
-     * replyTo = NetworkAgent message handler
-     * obj = byte[] of APF program
-     */
-    private static final int EVENT_PUSH_APF_PROGRAM_TO_NETWORK = 32;
-
     /** Handler thread used for both of the handlers below. */
     @VisibleForTesting
     protected final HandlerThread mHandlerThread;
@@ -630,7 +623,7 @@
 
         mDefaultRequest = createInternetRequestForTransport(-1);
         NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest,
-                new Binder(), NetworkRequestInfo.REQUEST);
+                new Binder(), NetworkRequestType.REQUEST);
         mNetworkRequests.put(mDefaultRequest, defaultNRI);
         mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
 
@@ -806,7 +799,7 @@
 
         if (enable) {
             handleRegisterNetworkRequest(new NetworkRequestInfo(
-                    null, mDefaultMobileDataRequest, new Binder(), NetworkRequestInfo.REQUEST));
+                    null, mDefaultMobileDataRequest, new Binder(), NetworkRequestType.REQUEST));
         } else {
             handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID);
         }
@@ -1802,20 +1795,6 @@
         }
     }
 
-    private void dumpApf(IndentingPrintWriter pw) {
-        pw.println("APF filters:");
-        pw.increaseIndent();
-        for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-            if (nai.apfFilter != null) {
-                pw.println(nai.name() + ":");
-                pw.increaseIndent();
-                nai.apfFilter.dump(pw);
-                pw.decreaseIndent();
-            }
-        }
-        pw.decreaseIndent();
-    }
-
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1833,11 +1812,6 @@
             return;
         }
 
-        if (argsContain(args, "apf")) {
-            dumpApf(pw);
-            return;
-        }
-
         pw.print("NetworkFactories for:");
         for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
             pw.print(" " + nfi.name);
@@ -1901,7 +1875,6 @@
         mKeepaliveTracker.dump(pw);
 
         pw.println();
-        dumpApf(pw);
 
         if (mInetLog != null && mInetLog.size() > 0) {
             pw.println();
@@ -1945,7 +1918,7 @@
     }
 
     private boolean isRequest(NetworkRequest request) {
-        return mNetworkRequests.get(request).isRequest;
+        return mNetworkRequests.get(request).isRequest();
     }
 
     // must be stateless - things change under us.
@@ -2164,7 +2137,7 @@
                 if (VDBG) log("NetworkFactory connected");
                 // A network factory has connected.  Send it all current NetworkRequests.
                 for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                    if (nri.isRequest == false) continue;
+                    if (!nri.isRequest()) continue;
                     NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
                     ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
                             (nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
@@ -2223,7 +2196,6 @@
             mKeepaliveTracker.handleStopAllKeepalives(nai,
                     ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
-            if (nai.apfFilter != null) nai.apfFilter.shutdown();
             mNetworkAgentInfos.remove(msg.replyTo);
             updateClat(null, nai.linkProperties, nai);
             synchronized (mNetworkForNetId) {
@@ -2301,7 +2273,7 @@
     private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
         mNetworkRequests.put(nri.request, nri);
         mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        if (!nri.isRequest) {
+        if (!nri.isRequest()) {
             for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
                 if (nri.request.networkCapabilities.hasSignalStrength() &&
                         network.satisfiesImmutableCapabilitiesOf(nri.request)) {
@@ -2310,7 +2282,7 @@
             }
         }
         rematchAllNetworksAndRequests(null, 0);
-        if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) {
+        if (nri.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
             sendUpdatedScoreToFactories(nri.request, 0);
         }
     }
@@ -2331,7 +2303,7 @@
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             // If this Network is already the highest scoring Network for a request, or if
             // there is hope for it to become one if it validated, then it is needed.
-            if (nri.isRequest && nai.satisfies(nri.request) &&
+            if (nri.isRequest() && nai.satisfies(nri.request) &&
                     (nai.networkRequests.get(nri.request.requestId) != null ||
                     // Note that this catches two important cases:
                     // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
@@ -2359,7 +2331,7 @@
             nri.unlinkDeathRecipient();
             mNetworkRequests.remove(request);
             mNetworkRequestInfoLogs.log("RELEASE " + nri);
-            if (nri.isRequest) {
+            if (nri.isRequest()) {
                 // Find all networks that are satisfying this request and remove the request
                 // from their request lists.
                 // TODO - it's my understanding that for a request there is only a single
@@ -2438,13 +2410,6 @@
                 accept ? 1 : 0, always ? 1: 0, network));
     }
 
-    public void pushApfProgramToNetwork(NetworkAgentInfo nai, byte[] program) {
-        enforceConnectivityInternalPermission();
-        Message msg = mHandler.obtainMessage(EVENT_PUSH_APF_PROGRAM_TO_NETWORK, program);
-        msg.replyTo = nai.messenger;
-        mHandler.sendMessage(msg);
-    }
-
     private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
         if (DBG) log("handleSetAcceptUnvalidated network=" + network +
                 " accept=" + accept + " always=" + always);
@@ -2594,16 +2559,6 @@
                     handleMobileDataAlwaysOn();
                     break;
                 }
-                case EVENT_PUSH_APF_PROGRAM_TO_NETWORK: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_PUSH_APF_PROGRAM_TO_NETWORK from unknown NetworkAgent");
-                    } else {
-                         nai.asyncChannel.sendMessage(NetworkAgent.CMD_PUSH_APF_PROGRAM,
-                                 (byte[]) msg.obj);
-                    }
-                    break;
-                }
                 // Sent by KeepaliveTracker to process an app request on the state machine thread.
                 case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
                     mKeepaliveTracker.handleStartKeepalive(msg);
@@ -3736,13 +3691,36 @@
     }
 
     /**
+     * A NetworkRequest as registered by an application can be one of three
+     * types:
+     *
+     *     - "listen", for which the framework will issue callbacks about any
+     *       and all networks that match the specified NetworkCapabilities,
+     *
+     *     - "request", capable of causing a specific network to be created
+     *       first (e.g. a telephony DUN request), the framework will issue
+     *       callbacks about the single, highest scoring current network
+     *       (if any) that matches the specified NetworkCapabilities, or
+     *
+     *     - "track the default network", a hybrid of the two designed such
+     *       that the framework will issue callbacks for the single, highest
+     *       scoring current network (if any) that matches the capabilities of
+     *       the default Internet request (mDefaultRequest), but which cannot
+     *       cause the framework to either create or retain the existence of
+     *       any specific network.
+     *
+     */
+    private static enum NetworkRequestType {
+        LISTEN,
+        TRACK_DEFAULT,
+        REQUEST
+    };
+
+    /**
      * Tracks info about the requester.
      * Also used to notice when the calling process dies so we can self-expire
      */
     private class NetworkRequestInfo implements IBinder.DeathRecipient {
-        static final boolean REQUEST = true;
-        static final boolean LISTEN = false;
-
         final NetworkRequest request;
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
@@ -3750,26 +3728,26 @@
         final int mPid;
         final int mUid;
         final Messenger messenger;
-        final boolean isRequest;
+        private final NetworkRequestType mType;
 
-        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) {
+        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, NetworkRequestType type) {
             request = r;
             mPendingIntent = pi;
             messenger = null;
             mBinder = null;
             mPid = getCallingPid();
             mUid = getCallingUid();
-            this.isRequest = isRequest;
+            mType = type;
         }
 
-        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
+        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, NetworkRequestType type) {
             super();
             messenger = m;
             request = r;
             mBinder = binder;
             mPid = getCallingPid();
             mUid = getCallingUid();
-            this.isRequest = isRequest;
+            mType = type;
             mPendingIntent = null;
 
             try {
@@ -3779,6 +3757,16 @@
             }
         }
 
+        private String typeString() {
+            switch (mType) {
+                case LISTEN: return "Listen";
+                case REQUEST: return "Request";
+                case TRACK_DEFAULT: return "Track default";
+                default:
+                    return "unknown type";
+            }
+        }
+
         void unlinkDeathRecipient() {
             if (mBinder != null) {
                 mBinder.unlinkToDeath(this, 0);
@@ -3791,8 +3779,27 @@
             releaseNetworkRequest(request);
         }
 
+        /**
+         * Returns true iff. the contained NetworkRequest is one that:
+         *
+         *     - should be associated with at most one satisfying network
+         *       at a time;
+         *
+         *     - should cause a network to be kept up if it is the only network
+         *       which can satisfy the NetworkReqeust.
+         *
+         * For full detail of how isRequest() is used for pairing Networks with
+         * NetworkRequests read rematchNetworkAndRequests().
+         *
+         * TODO: Rename to something more properly descriptive.
+         */
+        public boolean isRequest() {
+            return (mType == NetworkRequestType.TRACK_DEFAULT) ||
+                   (mType == NetworkRequestType.REQUEST);
+        }
+
         public String toString() {
-            return (isRequest ? "Request" : "Listen") +
+            return typeString() +
                     " from uid/pid:" + mUid + "/" + mPid +
                     " for " + request +
                     (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
@@ -3845,10 +3852,13 @@
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
+        final NetworkRequestType type = (networkCapabilities == null)
+                ? NetworkRequestType.TRACK_DEFAULT
+                : NetworkRequestType.REQUEST;
         // If the requested networkCapabilities is null, take them instead from
         // the default network request. This allows callers to keep track of
         // the system default network.
-        if (networkCapabilities == null) {
+        if (type == NetworkRequestType.TRACK_DEFAULT) {
             networkCapabilities = new NetworkCapabilities(mDefaultRequest.networkCapabilities);
             enforceAccessPermission();
         } else {
@@ -3870,8 +3880,7 @@
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId());
-        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
-                NetworkRequestInfo.REQUEST);
+        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, type);
         if (DBG) log("requestNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
@@ -3936,7 +3945,7 @@
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId());
         NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
-                NetworkRequestInfo.REQUEST);
+                NetworkRequestType.REQUEST);
         if (DBG) log("pendingRequest for " + nri);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
                 nri));
@@ -3988,7 +3997,7 @@
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
         NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
-                NetworkRequestInfo.LISTEN);
+                NetworkRequestType.LISTEN);
         if (DBG) log("listenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -4006,7 +4015,7 @@
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
         NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
-                NetworkRequestInfo.LISTEN);
+                NetworkRequestType.LISTEN);
         if (DBG) log("pendingListenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -4131,9 +4140,6 @@
         if (networkAgent.clatd != null) {
             networkAgent.clatd.fixupLinkProperties(oldLp);
         }
-        if (networkAgent.apfFilter != null) {
-            networkAgent.apfFilter.updateFilter();
-        }
 
         updateInterfaces(newLp, oldLp, netId);
         updateMtu(newLp, oldLp);
@@ -4457,6 +4463,7 @@
 
     private void makeDefault(NetworkAgentInfo newNetwork) {
         if (DBG) log("Switching to new default network: " + newNetwork);
+        ConnectivityServiceChangeEvent.logEvent(newNetwork.network.netId);
         setupDataActivityTracking(newNetwork);
         try {
             mNetd.setDefaultNetId(newNetwork.network.netId);
@@ -4523,7 +4530,7 @@
             // check if it satisfies the NetworkCapabilities
             if (VDBG) log("  checking if request is satisfied: " + nri.request);
             if (satisfies) {
-                if (!nri.isRequest) {
+                if (!nri.isRequest()) {
                     // This is not a request, it's a callback listener.
                     // Add it to newNetwork regardless of score.
                     if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
@@ -4583,7 +4590,7 @@
                     mNetworkForRequestId.remove(nri.request.requestId);
                     sendUpdatedScoreToFactories(nri.request, 0);
                 } else {
-                    if (nri.isRequest == true) {
+                    if (nri.isRequest()) {
                         Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
                                 newNetwork.name() +
                                 " without updating mNetworkForRequestId or factories!");
@@ -5087,5 +5094,4 @@
             NetworkAgentInfo nai, NetworkRequest defaultRequest) {
         return new NetworkMonitor(context, handler, nai, defaultRequest);
     }
-
 }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index c8763b1..4749417 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -198,7 +198,6 @@
 
     private int mActiveIdleOpCount;
     private IBinder mDownloadServiceActive;
-    private boolean mSyncActive;
     private boolean mJobsActive;
     private boolean mAlarmsActive;
     private boolean mReportedMaintenanceActivity;
@@ -685,13 +684,18 @@
         public long SMS_TEMP_APP_WHITELIST_DURATION;
 
         private final ContentResolver mResolver;
+        private final boolean mHasWatch;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         public Constants(Handler handler, ContentResolver resolver) {
             super(handler);
             mResolver = resolver;
-            mResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS), false, this);
+            mHasWatch = getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_WATCH);
+            mResolver.registerContentObserver(Settings.Global.getUriFor(
+                    mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
+                              : Settings.Global.DEVICE_IDLE_CONSTANTS),
+                    false, this);
             updateConstants();
         }
 
@@ -704,7 +708,8 @@
             synchronized (DeviceIdleController.this) {
                 try {
                     mParser.setString(Settings.Global.getString(mResolver,
-                            Settings.Global.DEVICE_IDLE_CONSTANTS));
+                            mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
+                                      : Settings.Global.DEVICE_IDLE_CONSTANTS));
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
@@ -725,8 +730,9 @@
                 MIN_DEEP_MAINTENANCE_TIME = mParser.getLong(
                         KEY_MIN_DEEP_MAINTENANCE_TIME,
                         !COMPRESS_TIME ? 30 * 1000L : 5 * 1000L);
+                long inactiveTimeoutDefault = (mHasWatch ? 15 : 30) * 60 * 1000L;
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
-                        !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
+                        !COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
                         !DEBUG ? 4 * 60 * 1000L : 60 * 1000L);
                 LOCATING_TIMEOUT = mParser.getLong(KEY_LOCATING_TIMEOUT,
@@ -734,8 +740,10 @@
                 LOCATION_ACCURACY = mParser.getFloat(KEY_LOCATION_ACCURACY, 20);
                 MOTION_INACTIVE_TIMEOUT = mParser.getLong(KEY_MOTION_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
+                long idleAfterInactiveTimeout = (mHasWatch ? 15 : 30) * 60 * 1000L;
                 IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
-                        !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
+                        !COMPRESS_TIME ? idleAfterInactiveTimeout
+                                       : (idleAfterInactiveTimeout / 10));
                 IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_IDLE_PENDING_TIMEOUT,
                         !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L);
                 MAX_IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_MAX_IDLE_PENDING_TIMEOUT,
@@ -944,7 +952,7 @@
                                 null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
                     // Always start with one active op for the message being sent here.
-                    // Now we we done!
+                    // Now we are done!
                     decActiveIdleOps();
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
@@ -1145,10 +1153,6 @@
             setNetworkPolicyTempWhitelistCallbackInternal(callback);
         }
 
-        public void setSyncActive(boolean active) {
-            DeviceIdleController.this.setSyncActive(active);
-        }
-
         public void setJobsActive(boolean active) {
             DeviceIdleController.this.setJobsActive(active);
         }
@@ -1157,6 +1161,16 @@
         public void setAlarmsActive(boolean active) {
             DeviceIdleController.this.setAlarmsActive(active);
         }
+
+        /**
+         * Returns the array of app ids whitelisted by user. Take care not to
+         * modify this, as it is a reference to the original copy. But the reference
+         * can change when the list changes, so it needs to be re-acquired when
+         * {@link PowerManager#ACTION_POWER_SAVE_WHITELIST_CHANGED} is sent.
+         */
+        public int[] getPowerSaveWhitelistUserAppIds() {
+            return DeviceIdleController.this.getPowerSaveWhitelistUserAppIds();
+        }
     }
 
     public DeviceIdleController(Context context) {
@@ -1165,6 +1179,12 @@
         mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
     }
 
+    int[] getPowerSaveWhitelistUserAppIds() {
+        synchronized (this) {
+            return mPowerSaveWhitelistUserAppIdArray;
+        }
+    }
+
     private static File getSystemDir() {
         return new File(Environment.getDataDirectory(), "system");
     }
@@ -1288,7 +1308,6 @@
 
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
                 mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
-
                 mDisplayManager.registerDisplayListener(mDisplayListener, null);
                 updateDisplayLocked();
             }
@@ -1877,16 +1896,6 @@
         }
     }
 
-    void setSyncActive(boolean active) {
-        synchronized (this) {
-            mSyncActive = active;
-            reportMaintenanceActivityIfNeededLocked();
-            if (!active) {
-                exitMaintenanceEarlyIfNeededLocked();
-            }
-        }
-    }
-
     void setJobsActive(boolean active) {
         synchronized (this) {
             mJobsActive = active;
@@ -1920,7 +1929,7 @@
     }
 
     void reportMaintenanceActivityIfNeededLocked() {
-        boolean active = mJobsActive | mSyncActive | (mDownloadServiceActive != null);
+        boolean active = mJobsActive | (mDownloadServiceActive != null);
         if (active == mReportedMaintenanceActivity) {
             return;
         }
@@ -1933,7 +1942,7 @@
     void exitMaintenanceEarlyIfNeededLocked() {
         if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
             if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
-                    && !mSyncActive && !mJobsActive && !mAlarmsActive) {
+                    && !mJobsActive && !mAlarmsActive) {
                 final long now = SystemClock.elapsedRealtime();
                 if (DEBUG) {
                     StringBuilder sb = new StringBuilder();
@@ -2741,9 +2750,6 @@
                 TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
-            if (mSyncActive) {
-                pw.print("  mSyncActive="); pw.println(mSyncActive);
-            }
             if (mJobsActive) {
                 pw.print("  mJobsActive="); pw.println(mJobsActive);
             }
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 044bb04..e29515f 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -51,17 +51,15 @@
  * 2) ASHMEM_SIZE (for scratch space used during dumping)
  * 3) ASHMEM_SIZE * HISTORY_SIZE
  *
- * Currently ASHMEM_SIZE is 256 bytes and HISTORY_SIZE is 20. Assuming
- * the system then also has 10 active rendering processes in the worst case
- * this would end up using under 14KiB (12KiB for the buffers, plus some overhead
- * for userId, pid, package name, and a couple other objects)
+ * This is currently under 16KiB total memory in the worst case of
+ * 20 processes in history + 10 unique active processes.
  *
  *  @hide */
 public class GraphicsStatsService extends IGraphicsStats.Stub {
     public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
 
     private static final String TAG = "GraphicsStatsService";
-    private static final int ASHMEM_SIZE = 256;
+    private static final int ASHMEM_SIZE = 296;
     private static final int HISTORY_SIZE = 20;
 
     private final Context mContext;
diff --git a/services/core/java/com/android/server/HardwarePropertiesManagerService.java b/services/core/java/com/android/server/HardwarePropertiesManagerService.java
index 575d99e..23cf64a 100644
--- a/services/core/java/com/android/server/HardwarePropertiesManagerService.java
+++ b/services/core/java/com/android/server/HardwarePropertiesManagerService.java
@@ -23,6 +23,8 @@
 import android.os.CpuUsageInfo;
 import android.os.IHardwarePropertiesManager;
 import android.os.Process;
+import android.os.UserHandle;
+import com.android.server.vr.VrManagerInternal;
 
 import java.util.Arrays;
 
@@ -78,14 +80,15 @@
      *
      * @param callingPackage The calling package name.
      *
-     * @throws SecurityException if a non profile or device owner or system tries to retrieve
-     * information provided by the service.
+     * @throws SecurityException if something other than the profile or device owner, or the
+     *        current VR service tries to retrieve information provided by this service.
      */
     private void enforceHardwarePropertiesRetrievalAllowed(String callingPackage)
             throws SecurityException {
         final PackageManager pm = mContext.getPackageManager();
+        int uid = 0;
         try {
-            final int uid = pm.getPackageUid(callingPackage, 0);
+            uid = pm.getPackageUid(callingPackage, 0);
             if (Binder.getCallingUid() != uid) {
                 throw new SecurityException("The caller has faked the package name.");
             }
@@ -93,10 +96,13 @@
             throw new SecurityException("The caller has faked the package name.");
         }
 
+        final int userId = UserHandle.getUserId(uid);
+        final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
         final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
         if (!dpm.isDeviceOwnerApp(callingPackage) && !dpm.isProfileOwnerApp(callingPackage)
-                && Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("The caller is not a device or profile owner or system.");
+                && !vrService.isCurrentVrListener(callingPackage, userId)) {
+            throw new SecurityException("The caller is not a device or profile owner or bound "
+                + "VrListenerService.");
         }
     }
 }
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 898d5b73..e042483 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -215,7 +215,7 @@
     // Ongoing notification
     private NotificationManager mNotificationManager;
     private KeyguardManager mKeyguardManager;
-    private StatusBarManagerService mStatusBar;
+    private @Nullable StatusBarManagerService mStatusBar;
     private Notification.Builder mImeSwitcherNotification;
     private PendingIntent mImeSwitchPendingIntent;
     private boolean mShowOngoingImeSwitcherForPhones;
@@ -1070,7 +1070,9 @@
                 mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
                 mNotificationManager = mContext.getSystemService(NotificationManager.class);
                 mStatusBar = statusBar;
-                statusBar.setIconVisibility(mSlotIme, false);
+                if (mStatusBar != null) {
+                    mStatusBar.setIconVisibility(mSlotIme, false);
+                }
                 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 43d10c7..3fdcceb 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -779,11 +779,11 @@
             }
         }
 
-        if (hasNonDefaults) {
+        if (debug && hasNonDefaults) {
             if (dest.size() == 0) {
-                Slog.w(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT");
+                Slog.v(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT");
             } else if (dest.size() > 1) {
-                Slog.w(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT");
+                Slog.v(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT");
             }
         }
     }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 548b662..c1a352c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -513,6 +513,28 @@
         }
     }
 
+    // Sync the state of the given chain with the native daemon.
+    private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) {
+        int size = uidFirewallRules.size();
+        if (size > 0) {
+            // Make a copy of the current rules, and then clear them. This is because
+            // setFirewallUidRuleInternal only pushes down rules to the native daemon if they are
+            // different from the current rules stored in the mUidFirewall*Rules array for the
+            // specified chain. If we don't clear the rules, setFirewallUidRuleInternal will do
+            // nothing.
+            final SparseIntArray rules = uidFirewallRules.clone();
+            uidFirewallRules.clear();
+
+            // Now push the rules. setFirewallUidRuleInternal will push each of these down to the
+            // native daemon, and also add them to the mUidFirewall*Rules array for the specified
+            // chain.
+            if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall " + name + "UID rules");
+            for (int i = 0; i < rules.size(); i++) {
+                setFirewallUidRuleInternal(chain, rules.keyAt(i), rules.valueAt(i));
+            }
+        }
+    }
+
     /**
      * Prepare native daemon once connected, enabling modules and pushing any
      * existing in-memory rules.
@@ -603,55 +625,18 @@
 
             setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
 
-            size = mUidFirewallRules.size();
-            if (size > 0) {
-                if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall UID rules");
-                final SparseIntArray uidFirewallRules = mUidFirewallRules;
-                mUidFirewallRules = new SparseIntArray();
-                for (int i = 0; i < uidFirewallRules.size(); i++) {
-                    setFirewallUidRuleInternal(FIREWALL_CHAIN_NONE, uidFirewallRules.keyAt(i),
-                            uidFirewallRules.valueAt(i));
-                }
-            }
+            syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
+            syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mUidFirewallDozableRules, "dozable ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, mUidFirewallPowerSaveRules,
+                    "powersave ");
 
-            size = mUidFirewallStandbyRules.size();
-            if (size > 0) {
-                if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall standby UID rules");
-                final SparseIntArray uidFirewallRules = mUidFirewallStandbyRules;
-                mUidFirewallStandbyRules = new SparseIntArray();
-                for (int i = 0; i < uidFirewallRules.size(); i++) {
-                    setFirewallUidRuleInternal(FIREWALL_CHAIN_STANDBY, uidFirewallRules.keyAt(i),
-                            uidFirewallRules.valueAt(i));
-                }
-            }
             if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)) {
                 setFirewallChainEnabled(FIREWALL_CHAIN_STANDBY, true);
             }
-
-            size = mUidFirewallDozableRules.size();
-            if (size > 0) {
-                if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall dozable UID rules");
-                final SparseIntArray uidFirewallRules = mUidFirewallDozableRules;
-                mUidFirewallDozableRules = new SparseIntArray();
-                for (int i = 0; i < uidFirewallRules.size(); i++) {
-                    setFirewallUidRuleInternal(FIREWALL_CHAIN_DOZABLE, uidFirewallRules.keyAt(i),
-                            uidFirewallRules.valueAt(i));
-                }
-            }
             if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)) {
                 setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true);
             }
-
-            size = mUidFirewallPowerSaveRules.size();
-            if (size > 0) {
-                Slog.d(TAG, "Pushing " + size + " active firewall powersave UID rules");
-                final SparseIntArray uidFirewallRules = mUidFirewallPowerSaveRules;
-                mUidFirewallPowerSaveRules = new SparseIntArray();
-                for (int i = 0; i < uidFirewallRules.size(); i++) {
-                    setFirewallUidRuleInternal(FIREWALL_CHAIN_POWERSAVE, uidFirewallRules.keyAt(i),
-                            uidFirewallRules.valueAt(i));
-                }
-            }
             if (mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE)) {
                 setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE, true);
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c1ec71c..1abb5ff 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -526,6 +526,7 @@
 
     // Determines whether to take full screen screenshots
     static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
+    public static final float FULLSCREEN_SCREENSHOT_SCALE = 0.6f;
 
     private static native int nativeMigrateToBoost();
     private static native int nativeMigrateFromBoost();
@@ -1491,6 +1492,7 @@
     /** The dimensions of the thumbnails in the Recents UI. */
     int mThumbnailWidth;
     int mThumbnailHeight;
+    float mFullscreenThumbnailScale;
 
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
@@ -3013,7 +3015,7 @@
             if (!app.killed) {
                 Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
                 Process.killProcessQuiet(app.pid);
-                killProcessGroup(app.info.uid, app.pid);
+                killProcessGroup(app.uid, app.pid);
             }
             if (lrui <= mLruProcessActivityStart) {
                 mLruProcessActivityStart--;
@@ -3386,7 +3388,7 @@
             // clean it up now.
             if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_PROCESSES, "App died: " + app);
             checkTime(startTime, "startProcess: bad proc running, killing");
-            killProcessGroup(app.info.uid, app.pid);
+            killProcessGroup(app.uid, app.pid);
             handleAppDiedLocked(app, true, true);
             checkTime(startTime, "startProcess: done killing old proc");
         }
@@ -5021,7 +5023,7 @@
             if (!fromBinderDied) {
                 Process.killProcessQuiet(pid);
             }
-            killProcessGroup(app.info.uid, pid);
+            killProcessGroup(app.uid, pid);
             app.killed = true;
         }
 
@@ -11422,18 +11424,16 @@
                 try {
                     final int currentUserId = mUserController.getCurrentUserIdLocked();
                     // Get the focused task before launching launcher.
-                    final int taskId = (mFocusedActivity == null)
-                            ? -1 : mFocusedActivity.task.taskId;
-                    startHomeActivityLocked(currentUserId, "notifyLockedProfile");
+
                     if (mUserController.isLockScreenDisabled(currentUserId)) {
-                        // If there is no device lock, we first go to launcher and then resume the
-                        // original task. Work challenge will be shown because we intercepted
-                        // startActivityFromRecentsInner and the reason why we switch to home stack
-                        // first is to prevent pressing back button brings user back to the work
-                        // app.
-                        if (taskId != -1) {
-                            startActivityFromRecentsInner(taskId, null);
+                        // If there is no device lock, we will show the profile's credential page.
+                        // startActivityFromRecentsInner is intercepted and will forward user to it.
+                        if (mFocusedActivity != null) {
+                            startActivityFromRecentsInner(mFocusedActivity.task.taskId, null);
                         }
+                    } else {
+                        // Showing launcher to avoid user entering credential twice.
+                        startHomeActivityLocked(currentUserId, "notifyLockedProfile");
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -12592,6 +12592,8 @@
                     com.android.internal.R.dimen.thumbnail_width);
             mThumbnailHeight = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.thumbnail_height);
+            mFullscreenThumbnailScale = res.getFraction(
+                    com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
             mDefaultPinnedStackBounds = Rect.unflattenFromString(res.getString(
                     com.android.internal.R.string.config_defaultPictureInPictureBounds));
             mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 26eaa8a..5cb04c8 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -929,7 +929,7 @@
             // use within SystemUI while keeping memory usage low.
             if (ActivityManagerService.TAKE_FULLSCREEN_SCREENSHOTS) {
                 w = h = -1;
-                scale = 0.5f;
+                scale = mService.mFullscreenThumbnailScale;
             }
             return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
                     w, h, scale);
@@ -1980,7 +1980,9 @@
         if (next == null) {
             // There are no more activities!
             final String reason = "noMoreActivities";
-            if (!mFullscreen && adjustFocusToNextFocusableStackLocked(reason)) {
+            final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack()
+                    ? HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
+            if (!mFullscreen && adjustFocusToNextFocusableStackLocked(returnTaskType, reason)) {
                 // Try to move focus to the next visible stack with a running activity if this
                 // stack is not covering the entire screen.
                 return mStackSupervisor.resumeFocusedStackTopActivityLocked(
@@ -1993,8 +1995,6 @@
                     "resumeTopActivityLocked: No more activities go home");
             if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
             // Only resume home if on home display
-            final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
-                    HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
             return isOnHomeDisplay() &&
                     mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason);
         }
@@ -3004,16 +3004,18 @@
             } else {
                 final TaskRecord task = r.task;
                 if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
+                    final int taskToReturnTo = task.getTaskToReturnTo();
+
                     // For non-fullscreen stack, we want to move the focus to the next visible
                     // stack to prevent the home screen from moving to the top and obscuring
                     // other visible stacks.
-                    if (!mFullscreen && adjustFocusToNextFocusableStackLocked(myReason)) {
+                    if (!mFullscreen
+                            && adjustFocusToNextFocusableStackLocked(taskToReturnTo, myReason)) {
                         return;
                     }
                     // Move the home stack to the top if this stack is fullscreen or there is no
                     // other visible stack.
-                    if (mStackSupervisor.moveHomeStackTaskToTop(
-                            task.getTaskToReturnTo(), myReason)) {
+                    if (mStackSupervisor.moveHomeStackTaskToTop(taskToReturnTo, myReason)) {
                         // Activity focus was already adjusted. Nothing else to do...
                         return;
                     }
@@ -3024,12 +3026,16 @@
         mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked(), myReason);
     }
 
-    private boolean adjustFocusToNextFocusableStackLocked(String reason) {
+    private boolean adjustFocusToNextFocusableStackLocked(int taskToReturnTo, String reason) {
         final ActivityStack stack = getNextFocusableStackLocked();
         final String myReason = reason + " adjustFocusToNextFocusableStack";
         if (stack == null) {
             return false;
         }
+
+        if (stack.isHomeStack()) {
+            return mStackSupervisor.moveHomeStackTaskToTop(taskToReturnTo, reason);
+        }
         return mService.setFocusedActivityLocked(stack.topRunningActivityLocked(), myReason);
     }
 
@@ -4843,7 +4849,9 @@
             // We only need to adjust focused stack if this stack is in focus.
             if (isOnHomeDisplay() && mStackSupervisor.isFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
-                if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) {
+                if (mFullscreen
+                        || !adjustFocusToNextFocusableStackLocked(
+                        task.getTaskToReturnTo(), myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index 13d90e3..76dfd01 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -26,6 +26,7 @@
 import static android.content.Intent.EXTRA_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import android.app.KeyguardManager;
@@ -192,14 +193,14 @@
                 Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
                 new String[]{ resolvedType },
                 FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE, null);
-        final int flags = intent.getFlags();
         final KeyguardManager km = (KeyguardManager) mService.mContext
                 .getSystemService(KEYGUARD_SERVICE);
         final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
         if (newIntent == null) {
             return null;
         }
-        newIntent.setFlags(flags | FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
+                FLAG_ACTIVITY_TASK_ON_HOME);
         newIntent.putExtra(EXTRA_PACKAGE_NAME, aInfo.packageName);
         newIntent.putExtra(EXTRA_INTENT, new IntentSender(target));
         return newIntent;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 71a1f97..ffa3b5b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1170,7 +1170,8 @@
         }
 
         if (useCheckinFormat) {
-            List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
+            List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_ALL);
             if (isRealCheckin) {
                 // For a real checkin, first we want to prefer to use the last complete checkin
                 // file if there is one.
diff --git a/services/core/java/com/android/server/am/HealthStatsBatteryStatsWriter.java b/services/core/java/com/android/server/am/HealthStatsBatteryStatsWriter.java
index 39c6ce6..9fb51c1 100644
--- a/services/core/java/com/android/server/am/HealthStatsBatteryStatsWriter.java
+++ b/services/core/java/com/android/server/am/HealthStatsBatteryStatsWriter.java
@@ -122,7 +122,8 @@
             // Battery Stats stores the GPS sensors with a bogus key in this API. Pull it out
             // as a separate metric here so as to not expose that in the API.
             if (sensorId == BatteryStats.Uid.Sensor.GPS) {
-                addTimer(uidWriter, UidHealthStats.TIMER_GPS_SENSOR, sensors.valueAt(i).getSensorTime());
+                addTimer(uidWriter, UidHealthStats.TIMER_GPS_SENSOR,
+                        sensors.valueAt(i).getSensorTime());
             } else {
                 addTimers(uidWriter, UidHealthStats.TIMERS_SENSORS, Integer.toString(sensorId),
                         sensors.valueAt(i).getSensorTime());
@@ -131,7 +132,7 @@
 
         // STATS_PIDS
         pids = uid.getPidStats();
-        N = sensors.size();
+        N = pids.size();
         for (int i=0; i<N; i++) {
             final HealthStatsWriter writer = new HealthStatsWriter(PidHealthStats.CONSTANTS);
             writePid(writer, pids.valueAt(i));
@@ -241,7 +242,8 @@
         addTimer(uidWriter, UidHealthStats.TIMER_CAMERA, uid.getCameraTurnedOnTimer());
 
         // TIMER_FOREGROUND_ACTIVITY
-        addTimer(uidWriter, UidHealthStats.TIMER_FOREGROUND_ACTIVITY, uid.getForegroundActivityTimer());
+        addTimer(uidWriter, UidHealthStats.TIMER_FOREGROUND_ACTIVITY,
+                uid.getForegroundActivityTimer());
 
         // TIMER_BLUETOOTH_SCAN
         addTimer(uidWriter, UidHealthStats.TIMER_BLUETOOTH_SCAN, uid.getBluetoothScanTimer());
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b4aa4cf..5407d28 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -558,7 +558,7 @@
             }
             EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
             Process.killProcessQuiet(pid);
-            Process.killProcessGroup(info.uid, pid);
+            Process.killProcessGroup(uid, pid);
             if (!persistent) {
                 killed = true;
                 killedByAm = true;
diff --git a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
index f6dc9b9..7cac227 100644
--- a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
+++ b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
@@ -60,7 +60,7 @@
     private long mLastSentEventTimeMillis = System.currentTimeMillis();
 
     private final void enforceConnectivityInternalPermission() {
-        getContext().enforceCallingPermission(
+        getContext().enforceCallingOrSelfPermission(
                 android.Manifest.permission.CONNECTIVITY_INTERNAL,
                 "MetricsLoggerService");
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b4c71c1..c5d38cb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -32,7 +32,6 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.server.ConnectivityService;
 import com.android.server.connectivity.NetworkMonitor;
-import com.android.server.connectivity.ApfFilter;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -164,8 +163,6 @@
     // Used by ConnectivityService to keep track of 464xlat.
     public Nat464Xlat clatd;
 
-    public ApfFilter apfFilter;
-
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
             NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
@@ -178,7 +175,6 @@
         currentScore = score;
         networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
-        apfFilter.maybeInstall(connService, this);
     }
 
     /**
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index bce7733..bbb162e 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -34,6 +34,8 @@
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
+import android.net.metrics.CaptivePortalCheckResultEvent;
+import android.net.metrics.CaptivePortalStateChangeEvent;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
@@ -297,9 +299,13 @@
                     transitionTo(mLingeringState);
                     return HANDLED;
                 case CMD_NETWORK_CONNECTED:
+                    CaptivePortalStateChangeEvent.logEvent(
+                            CaptivePortalStateChangeEvent.NETWORK_MONITOR_CONNECTED);
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_NETWORK_DISCONNECTED:
+                    CaptivePortalStateChangeEvent.logEvent(
+                            CaptivePortalStateChangeEvent.NETWORK_MONITOR_DISCONNECTED);
                     if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
                         mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
                         mLaunchCaptivePortalAppBroadcastReceiver = null;
@@ -349,6 +355,8 @@
     private class ValidatedState extends State {
         @Override
         public void enter() {
+            CaptivePortalStateChangeEvent.logEvent(
+                   CaptivePortalStateChangeEvent.NETWORK_MONITOR_VALIDATED);
             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
                     NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo));
         }
@@ -457,6 +465,8 @@
                     // will be unresponsive. isCaptivePortal() could be executed on another Thread
                     // if this is found to cause problems.
                     int httpResponseCode = isCaptivePortal();
+                    CaptivePortalCheckResultEvent.logEvent(mNetworkAgentInfo.network.netId,
+                            httpResponseCode);
                     if (httpResponseCode == 204) {
                         transitionTo(mValidatedState);
                     } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 4eecc81..da9c48a 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -255,6 +255,9 @@
                     // ignore usb0 down after enabling RNDIS
                     // we will handle disconnect in interfaceRemoved instead
                     if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
+                } else if (isWifi(iface)) {
+                    // handle disconnect in interfaceRemoved
+                    if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
                 } else if (sm != null) {
                     sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
                     mIfaces.remove(iface);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 4527f1f..715d2d8 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -17,12 +17,12 @@
 package com.android.server.display;
 
 import android.content.res.Resources;
-import android.os.Build;
 import com.android.server.LocalServices;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 
 import android.content.Context;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -31,7 +31,6 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.DisplayEventReceiver;
 import android.view.Surface;
@@ -388,7 +387,7 @@
                     mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
                             | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
                     if (res.getBoolean(com.android.internal.R.bool.config_mainBuiltInDisplayIsRound)
-                            || (Build.HARDWARE.contains("goldfish")
+                            || (Build.IS_EMULATOR
                             && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
                         mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
                     }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index e34fb9b..6b00f5f 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -67,6 +67,7 @@
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.ContentObserverController;
+import com.android.server.job.controllers.DeviceIdleJobsController;
 import com.android.server.job.controllers.IdleController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
@@ -164,11 +165,6 @@
     boolean mReadyToRock;
 
     /**
-     * True when in device idle mode, so we don't want to schedule any jobs.
-     */
-    boolean mDeviceIdleMode;
-
-    /**
      * What we last reported to DeviceIdleController about whether we are active.
      */
     boolean mReportedActive;
@@ -228,12 +224,6 @@
                     Slog.d(TAG, "Removing jobs for user: " + userId);
                 }
                 cancelJobsForUser(userId);
-            } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
-                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
-                updateIdleMode(mPowerManager != null
-                        ? (mPowerManager.isDeviceIdleMode()
-                        || mPowerManager.isLightDeviceIdleMode())
-                        : false);
             }
         }
     };
@@ -418,44 +408,29 @@
         }
     }
 
-    void updateIdleMode(boolean enabled) {
-        boolean changed = false;
-        boolean rocking;
+    @Override
+    public void onDeviceIdleStateChanged(boolean deviceIdle) {
         synchronized (mLock) {
-            if (mDeviceIdleMode != enabled) {
-                changed = true;
-            }
-            rocking = mReadyToRock;
-        }
-        if (changed) {
-            if (rocking) {
-                for (int i=0; i<mControllers.size(); i++) {
-                    mControllers.get(i).deviceIdleModeChanged(enabled);
+            if (deviceIdle) {
+                // When becoming idle, make sure no jobs are actively running.
+                for (int i=0; i<mActiveServices.size(); i++) {
+                    JobServiceContext jsc = mActiveServices.get(i);
+                    final JobStatus executing = jsc.getRunningJob();
+                    if (executing != null) {
+                        jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
+                    }
                 }
-            }
-            synchronized (mLock) {
-                mDeviceIdleMode = enabled;
-                if (enabled) {
-                    // When becoming idle, make sure no jobs are actively running.
-                    for (int i=0; i<mActiveServices.size(); i++) {
-                        JobServiceContext jsc = mActiveServices.get(i);
-                        final JobStatus executing = jsc.getRunningJob();
-                        if (executing != null) {
-                            jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
+            } else {
+                // When coming out of idle, allow thing to start back up.
+                if (mReadyToRock) {
+                    if (mLocalDeviceIdleController != null) {
+                        if (!mReportedActive) {
+                            mReportedActive = true;
+                            mLocalDeviceIdleController.setJobsActive(true);
                         }
                     }
-                } else {
-                    // When coming out of idle, allow thing to start back up.
-                    if (rocking) {
-                        if (mLocalDeviceIdleController != null) {
-                            if (!mReportedActive) {
-                                mReportedActive = true;
-                                mLocalDeviceIdleController.setJobsActive(true);
-                            }
-                        }
-                    }
-                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
                 }
+                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
             }
         }
     }
@@ -500,6 +475,7 @@
         mControllers.add(BatteryController.get(this));
         mControllers.add(AppIdleController.get(this));
         mControllers.add(ContentObserverController.get(this));
+        mControllers.add(DeviceIdleJobsController.get(this));
 
         mHandler = new JobHandler(context.getMainLooper());
         mJobSchedulerStub = new JobSchedulerStub();
@@ -521,8 +497,6 @@
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, filter, null, null);
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
-            userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
             mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
@@ -553,7 +527,6 @@
                     public void process(JobStatus job) {
                         for (int controller = 0; controller < mControllers.size(); controller++) {
                             final StateController sc = mControllers.get(controller);
-                            sc.deviceIdleModeChanged(mDeviceIdleMode);
                             sc.maybeStartTrackingJobLocked(job, null);
                         }
                     }
@@ -1015,10 +988,6 @@
          */
         private void maybeRunPendingJobsH() {
             synchronized (mLock) {
-                if (mDeviceIdleMode) {
-                    // If device is idle, we will not schedule jobs to run.
-                    return;
-                }
                 if (DEBUG) {
                     Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
                 }
@@ -1188,6 +1157,7 @@
          * Returns a list of all pending jobs. A running job is not considered pending. Periodic
          * jobs are always considered pending.
          */
+        @Override
         public List<JobInfo> getSystemScheduledPendingJobs() {
             synchronized (mLock) {
                 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
@@ -1509,7 +1479,6 @@
             }
             pw.println();
             pw.print("mReadyToRock="); pw.println(mReadyToRock);
-            pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
             pw.print("mReportedActive="); pw.println(mReportedActive);
             pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
         }
diff --git a/services/core/java/com/android/server/job/StateChangedListener.java b/services/core/java/com/android/server/job/StateChangedListener.java
index 97dfad3..87bfc27 100644
--- a/services/core/java/com/android/server/job/StateChangedListener.java
+++ b/services/core/java/com/android/server/job/StateChangedListener.java
@@ -37,4 +37,6 @@
      *                  indicates to the scheduler that any ready jobs should be flushed.</strong>
      */
     public void onRunJobNow(JobStatus jobStatus);
+
+    public void onDeviceIdleStateChanged(boolean deviceIdle);
 }
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index f0c579f..2114fc3 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -87,7 +87,7 @@
         pw.println("Parole On: " + mAppIdleParoleOn);
         for (JobStatus task : mTrackedTasks) {
             pw.print(task.getSourcePackageName());
-            pw.print(":idle="
+            pw.print(":runnable="
                     + ((task.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0));
             pw.print(", ");
         }
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
new file mode 100644
index 0000000..d2ef6a2
--- /dev/null
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 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.job.controllers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.StateChangedListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
+ * When device is not dozing, set constraint for all jobs as satisfied.
+ */
+public class DeviceIdleJobsController extends StateController {
+
+    private static final String LOG_TAG = "DeviceIdleJobsController";
+    private static final boolean LOG_DEBUG = false;
+
+    // Singleton factory
+    private static Object sCreationLock = new Object();
+    final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
+    private static DeviceIdleJobsController sController;
+
+    private final PowerManager mPowerManager;
+    private final DeviceIdleController.LocalService mLocalDeviceIdleController;
+
+    /**
+     * True when in device idle mode, so we don't want to schedule any jobs.
+     */
+    private boolean mDeviceIdleMode;
+    private int[] mDeviceIdleWhitelistAppIds;
+
+    /**
+     * Returns a singleton for the DeviceIdleJobsController
+     */
+    public static DeviceIdleJobsController get(JobSchedulerService service) {
+        synchronized (sCreationLock) {
+            if (sController == null) {
+                sController = new DeviceIdleJobsController(service, service.getContext(),
+                        service.getLock());
+            }
+            return sController;
+        }
+    }
+
+    // onReceive
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
+                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                updateIdleMode(mPowerManager != null
+                        ? (mPowerManager.isDeviceIdleMode()
+                                || mPowerManager.isLightDeviceIdleMode())
+                        : false);
+            } else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) {
+                updateWhitelist();
+            }
+        }
+    };
+
+    private DeviceIdleJobsController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
+        super(stateChangedListener, context, lock);
+
+        // Register for device idle mode changes
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mLocalDeviceIdleController =
+                LocalServices.getService(DeviceIdleController.LocalService.class);
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+        mContext.registerReceiverAsUser(
+                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+    }
+
+    void updateIdleMode(boolean enabled) {
+        boolean changed = false;
+        // Need the whitelist to be ready when going into idle
+        if (mDeviceIdleWhitelistAppIds == null) {
+            updateWhitelist();
+        }
+        synchronized (mLock) {
+            if (mDeviceIdleMode != enabled) {
+                changed = true;
+            }
+            mDeviceIdleMode = enabled;
+            if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
+            for (JobStatus task : mTrackedTasks) {
+                updateTaskStateLocked(task);
+            }
+        }
+        // Inform the job scheduler service about idle mode changes
+        if (changed) {
+            mStateChangedListener.onDeviceIdleStateChanged(enabled);
+        }
+    }
+
+    /**
+     * Fetches the latest whitelist from the device idle controller.
+     */
+    void updateWhitelist() {
+        synchronized (mLock) {
+            if (mLocalDeviceIdleController != null) {
+                mDeviceIdleWhitelistAppIds =
+                        mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
+                if (LOG_DEBUG) {
+                    Slog.d(LOG_TAG, "Got whitelist " + Arrays.toString(mDeviceIdleWhitelistAppIds));
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if the given job's scheduling app id exists in the device idle user whitelist.
+     */
+    boolean isWhitelistedLocked(JobStatus job) {
+        if (mDeviceIdleWhitelistAppIds != null
+                && ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
+                        UserHandle.getAppId(job.getSourceUid()))) {
+            return true;
+        }
+        return false;
+    }
+
+    private void updateTaskStateLocked(JobStatus task) {
+        boolean enableTask = !mDeviceIdleMode || isWhitelistedLocked(task);
+        task.setDeviceNotDozingConstraintSatisfied(enableTask);
+    }
+
+    @Override
+    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        synchronized (mLock) {
+            mTrackedTasks.add(jobStatus);
+            updateTaskStateLocked(jobStatus);
+        }
+    }
+
+    @Override
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+        mTrackedTasks.remove(jobStatus);
+    }
+
+    @Override
+    public void dumpControllerStateLocked(PrintWriter pw) {
+        pw.println("DeviceIdleJobsController");
+        for (JobStatus task : mTrackedTasks) {
+            pw.print(task.getSourcePackageName());
+            pw.print(":runnable="
+                    + ((task.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0));
+            pw.print(", ");
+        }
+        pw.println();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 4a2c88c..39905d8 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -54,6 +54,7 @@
     static final int CONSTRAINT_CONNECTIVITY = 1<<5;
     static final int CONSTRAINT_APP_NOT_IDLE = 1<<6;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<7;
+    static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<8;
 
     // Soft override: ignore constraints like time that don't affect API availability
     public static final int OVERRIDE_SOFT = 1;
@@ -363,6 +364,10 @@
         return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
     }
 
+    boolean setDeviceNotDozingConstraintSatisfied(boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
+    }
+
     boolean setConstraintSatisfied(int constraint, boolean state) {
         boolean old = (satisfiedConstraints&constraint) != 0;
         if (old == state) {
@@ -380,11 +385,14 @@
         // Deadline constraint trumps other constraints (except for periodic jobs where deadline
         // is an implementation detail. A periodic job should only run if its constraints are
         // satisfied).
-        // AppNotIdle implicit constraint trumps all!
+        // AppNotIdle implicit constraint must be satisfied
+        // DeviceNotDozing implicit constraint must be satisfied
         return (isConstraintsSatisfied()
                 || (!job.isPeriodic()
-                && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0))
-                && (satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0;
+                && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0)
+                )
+                && (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0
+                && (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0;
     }
 
     static final int CONSTRAINTS_OF_INTEREST =
@@ -433,6 +441,7 @@
                 + ",U=" + (job.getTriggerContentUris() != null)
                 + ",F=" + numFailures + ",P=" + job.isPersisted()
                 + ",ANI=" + ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0)
+                + ",DND=" + ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0)
                 + (isReady() ? "(READY)" : "")
                 + "]";
     }
@@ -492,6 +501,9 @@
         if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
             pw.print(" CONTENT_TRIGGER");
         }
+        if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
+            pw.print(" DEVICE_NOT_DOZING");
+        }
     }
 
     // Dumpsys infrastructure
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index 7882bc4d..ac7f4c3 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -33,7 +33,6 @@
     protected final Context mContext;
     protected final Object mLock;
     protected final StateChangedListener mStateChangedListener;
-    protected boolean mDeviceIdleMode;
 
     public StateController(StateChangedListener stateChangedListener, Context context,
             Object lock) {
@@ -42,10 +41,6 @@
         mLock = lock;
     }
 
-    public void deviceIdleModeChanged(boolean enabled) {
-        mDeviceIdleMode = enabled;
-    }
-
     /**
      * Implement the logic here to decide whether a job should be tracked by this controller.
      * This logic is put here so the JobManager can be completely agnostic of Controller logic.
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 5b1cedc..fc412e3 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -20,6 +20,7 @@
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.provider.Settings.ACTION_VPN_SETTINGS;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -66,9 +67,6 @@
 
     private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
 
-    private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
-    private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
-
     private static final int ROOT_UID = 0;
 
     private final Context mContext;
@@ -101,7 +99,6 @@
         mProfile = Preconditions.checkNotNull(profile);
 
         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
-        configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
         mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
 
         final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/services/core/java/com/android/server/net/NetworkStatsObservers.java
index 2f55562..6f781b3 100644
--- a/services/core/java/com/android/server/net/NetworkStatsObservers.java
+++ b/services/core/java/com/android/server/net/NetworkStatsObservers.java
@@ -52,7 +52,7 @@
  */
 class NetworkStatsObservers {
     private static final String TAG = "NetworkStatsObservers";
-    private static final boolean LOGV = true;
+    private static final boolean LOGV = false;
 
     private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
 
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 0eacd13..eae2eaa 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -24,7 +24,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -51,8 +50,6 @@
 
     final AtomicBoolean mIdleTime = new AtomicBoolean(false);
 
-    private boolean useJitProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
-
     public static void schedule(Context context) {
         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         JobInfo job = new JobInfo.Builder(BACKGROUND_DEXOPT_JOB, sDexoptServiceName)
@@ -93,8 +90,8 @@
                         // skip previously failing package
                         continue;
                     }
-                    if (!pm.performDexOpt(pkg, /* instruction set */ null, useJitProfiles,
-                            /* extractOnly */ false, /* force */ false)) {
+                    if (!pm.performDexOpt(pkg, /* instruction set */ null, /* checkProfiles */ true,
+                            PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)) {
                         // there was a problem running dexopt,
                         // remember this so we do not keep retrying.
                         sFailedPackageNames.add(pkg);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 206a143..7e25632 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.Build;
-import android.os.storage.StorageManager;
 import android.util.Slog;
 
 import com.android.internal.os.InstallerConnection;
@@ -37,17 +36,17 @@
      * frameworks/native/cmds/installd/installd.h
      * **************************************************************************/
     /** Application should be visible to everyone */
-    public static final int DEXOPT_PUBLIC       = 1 << 1;
+    public static final int DEXOPT_PUBLIC         = 1 << 1;
     /** Application wants to run in VM safe mode */
-    public static final int DEXOPT_SAFEMODE     = 1 << 2;
+    public static final int DEXOPT_SAFEMODE       = 1 << 2;
     /** Application wants to allow debugging of its code */
-    public static final int DEXOPT_DEBUGGABLE   = 1 << 3;
+    public static final int DEXOPT_DEBUGGABLE     = 1 << 3;
     /** The system boot has finished */
-    public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
-    /** Do not compile, only extract bytecode into an OAT file */
-    public static final int DEXOPT_EXTRACTONLY  = 1 << 5;
+    public static final int DEXOPT_BOOTCOMPLETE   = 1 << 4;
+    /** Hint that the dexopt type is profile-guided. */
+    public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
     /** This is an OTA update dexopt */
-    public static final int DEXOPT_OTA          = 1 << 6;
+    public static final int DEXOPT_OTA            = 1 << 6;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
@@ -137,19 +136,23 @@
     }
 
     public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
-            int dexFlags, String volumeUuid, boolean useProfiles) throws InstallerException {
+            int dexFlags, String compilerFilter, String volumeUuid) throws InstallerException {
         assertValidInstructionSet(instructionSet);
         mInstaller.dexopt(apkPath, uid, instructionSet, dexoptNeeded, dexFlags,
-                volumeUuid, useProfiles);
+                compilerFilter, volumeUuid);
     }
 
     public void dexopt(String apkPath, int uid, String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
-            String volumeUuid, boolean useProfiles)
+            String compilerFilter, String volumeUuid)
                     throws InstallerException {
         assertValidInstructionSet(instructionSet);
         mInstaller.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded,
-                outputPath, dexFlags, volumeUuid, useProfiles);
+                outputPath, dexFlags, compilerFilter, volumeUuid);
+    }
+
+    public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
+        return mInstaller.mergeProfiles(uid, pkgName);
     }
 
     public void idmap(String targetApkPath, String overlayApkPath, int uid)
@@ -166,8 +169,12 @@
         mInstaller.execute("rmpackagedir", packageDir);
     }
 
-    public void rmProfiles(String pkgName) throws InstallerException {
-        mInstaller.execute("rmprofiles", pkgName);
+    public void clearAppProfiles(String pkgName) throws InstallerException {
+        mInstaller.execute("clear_app_profiles", pkgName);
+    }
+
+    public void destroyAppProfiles(String pkgName) throws InstallerException {
+        mInstaller.execute("destroy_app_profiles", pkgName);
     }
 
     public void createUserConfig(int userid) throws InstallerException {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 67aeed1..c3a9226 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -19,11 +19,11 @@
 import static com.android.server.pm.Installer.DEXOPT_OTA;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
 import android.content.Context;
 import android.content.pm.IOtaDexopt;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.Package;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -130,6 +130,7 @@
         // TODO: If apps are not installed in the internal /data partition, we should compare
         //       against that storage's free capacity.
         File dataDir = Environment.getDataDirectory();
+        @SuppressWarnings("deprecation")
         long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
         if (lowThreshold == 0) {
             throw new IllegalStateException("Invalid low memory threshold");
@@ -142,7 +143,7 @@
         }
 
         mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */,
-                false /* extractOnly */);
+                getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA));
     }
 
     private void moveAbArtifacts(Installer installer) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 561682c..5ceb65f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,13 +20,10 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.Package;
 import android.os.Environment;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.WorkSource;
-import android.os.storage.StorageManager;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
@@ -34,18 +31,18 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
 import dalvik.system.DexFile;
 
 import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
 import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
+import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
 import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
 import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
-import static com.android.server.pm.Installer.DEXOPT_EXTRACTONLY;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getFullCompilerFilter;
 
 /**
  * Helper class for running dexopt command on packages.
@@ -59,8 +56,6 @@
     static final int DEX_OPT_DEFERRED = 2;
     static final int DEX_OPT_FAILED = -1;
 
-    private static final boolean DEBUG_DEXOPT = PackageManagerService.DEBUG_DEXOPT;
-
     private final Installer mInstaller;
     private final Object mInstallLock;
 
@@ -94,8 +89,8 @@
      * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
      * synchronized on {@link #mInstallLock}.
      */
-    int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean useProfiles,
-            boolean extractOnly) {
+    int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean checkProfiles,
+            String targetCompilationFilter) {
         synchronized (mInstallLock) {
             final boolean useLock = mSystemReady;
             if (useLock) {
@@ -103,7 +98,8 @@
                 mDexoptWakeLock.acquire();
             }
             try {
-                return performDexOptLI(pkg, instructionSets, useProfiles, extractOnly);
+                return performDexOptLI(pkg, instructionSets, checkProfiles,
+                        targetCompilationFilter);
             } finally {
                 if (useLock) {
                     mDexoptWakeLock.release();
@@ -128,7 +124,7 @@
     }
 
     private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
-            boolean useProfiles, boolean extractOnly) {
+            boolean checkProfiles, String targetCompilerFilter) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
 
@@ -136,36 +132,51 @@
             return DEX_OPT_SKIPPED;
         }
 
+        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
+        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+
+        boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
+        // If any part of the app is used by other apps, we cannot use profile-guided
+        // compilation.
+        // TODO: This needs to be refactored to be also checked when the target mode is
+        //       profile-guided.
+        if (isProfileGuidedFilter) {
+            for (String path : paths) {
+                if (isUsedByOtherApps(path)) {
+                    checkProfiles = false;
+
+                    // TODO: Should we only upgrade to the non-profile-guided version? That is,
+                    //       given verify-profile, should we move to interpret-only?
+                    targetCompilerFilter = getFullCompilerFilter();
+                    isProfileGuidedFilter = false;
+
+                    break;
+                }
+            }
+        }
+
+        // If we're asked to take profile updates into account, check now.
+        boolean newProfile = false;
+        if (checkProfiles && isProfileGuidedFilter) {
+            // Merge profiles, see if we need to do anything.
+            try {
+                newProfile = mInstaller.mergeProfiles(sharedGid, pkg.packageName);
+            } catch (InstallerException e) {
+                Slog.w(TAG, "Failed to merge profiles", e);
+            }
+        }
+
         final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
 
-        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
         boolean performedDexOpt = false;
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         for (String dexCodeInstructionSet : dexCodeInstructionSets) {
             for (String path : paths) {
-                if (useProfiles && isUsedByOtherApps(path)) {
-                    // We cannot use profile guided compilation if the apk was used by another app.
-                    useProfiles = false;
-                }
                 int dexoptNeeded;
-
                 try {
-                    int compilationTypeMask = 0;
-                    if (extractOnly) {
-                        // For extract only, any type of compilation is good.
-                        compilationTypeMask = DexFile.COMPILATION_TYPE_FULL
-                            | DexFile.COMPILATION_TYPE_PROFILE_GUIDE
-                            | DexFile.COMPILATION_TYPE_EXTRACT_ONLY;
-                    } else {
-                        // Branch taken for profile guide and full compilation.
-                        // Profile guide compilation should only recompile a previous
-                        // profile compiled/extract only file and should not be attempted if the
-                        // apk is already fully compiled. So test against a full compilation type.
-                        compilationTypeMask = DexFile.COMPILATION_TYPE_FULL;
-                    }
                     dexoptNeeded = DexFile.getDexOptNeeded(path,
-                            dexCodeInstructionSet, compilationTypeMask);
+                            dexCodeInstructionSet, targetCompilerFilter, newProfile);
                 } catch (IOException ioe) {
                     Slog.w(TAG, "IOException reading apk: " + path, ioe);
                     return DEX_OPT_FAILED;
@@ -194,20 +205,20 @@
                 Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
                         + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
                         + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
-                        + " extractOnly=" + extractOnly + " oatDir = " + oatDir);
-                final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+                        + " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir);
                 // Profile guide compiled oat files should not be public.
-                final boolean isPublic = !pkg.isForwardLocked() && !useProfiles;
+                final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
+                final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
                 final int dexFlags = adjustDexoptFlags(
                         ( isPublic ? DEXOPT_PUBLIC : 0)
                         | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                         | (debuggable ? DEXOPT_DEBUGGABLE : 0)
-                        | (extractOnly ? DEXOPT_EXTRACTONLY : 0)
+                        | profileFlag
                         | DEXOPT_BOOTCOMPLETE);
 
                 try {
                     mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
-                            dexoptNeeded, oatDir, dexFlags, pkg.volumeUuid, useProfiles);
+                            dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid);
                     performedDexOpt = true;
                 } catch (InstallerException e) {
                     Slog.w(TAG, "Failed to dexopt", e);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9335116..64f8c98 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -92,6 +92,8 @@
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getFullCompilerFilter;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
@@ -441,6 +443,17 @@
         sBrowserIntent.setData(Uri.parse("http:"));
     }
 
+    // Compilation reasons.
+    public static final int REASON_BOOT = 0;
+    public static final int REASON_INSTALL = 1;
+    public static final int REASON_BACKGROUND_DEXOPT = 2;
+    public static final int REASON_AB_OTA = 3;
+    public static final int REASON_NON_SYSTEM_LIBRARY = 4;
+    public static final int REASON_SHARED_APK = 5;
+    public static final int REASON_FORCED_DEXOPT = 6;
+
+    public static final int REASON_LAST = REASON_FORCED_DEXOPT;
+
     final ServiceThread mHandlerThread;
 
     final PackageHandler mHandler;
@@ -1956,6 +1969,9 @@
 
     public static PackageManagerService main(Context context, Installer installer,
             boolean factoryTest, boolean onlyCore) {
+        // Self-check for initial settings.
+        PackageManagerServiceCompilerMapping.checkProperties();
+
         PackageManagerService m = new PackageManagerService(context, installer,
                 factoryTest, onlyCore);
         m.enableSystemUserPackages();
@@ -2168,12 +2184,13 @@
                             // AOT compilation (if needed).
                             int dexoptNeeded = DexFile.getDexOptNeeded(
                                     lib, dexCodeInstructionSet,
-                                    DexFile.COMPILATION_TYPE_FULL);
+                                    getCompilerFilterForReason(REASON_SHARED_APK),
+                                    false /* newProfile */);
                             if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                                 mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,
                                         dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/,
-                                        StorageManager.UUID_PRIVATE_INTERNAL,
-                                        false /*useProfiles*/);
+                                        getCompilerFilterForReason(REASON_SHARED_APK),
+                                        StorageManager.UUID_PRIVATE_INTERNAL);
                             }
                         } catch (FileNotFoundException e) {
                             Slog.w(TAG, "Library not found: " + lib);
@@ -6928,7 +6945,7 @@
                 // and would have to be patched (would be SELF_PATCHOAT, which is deprecated).
                 // Instead, force the extraction in this case.
                 performDexOpt(pkg.packageName, null /* instructionSet */,
-                         false /* useProfiles */, true /* extractOnly */, prunedCache);
+                         false /* checkProfiles */, REASON_BOOT, prunedCache);
             }
         }
     }
@@ -6947,29 +6964,37 @@
     // TODO: this is not used nor needed. Delete it.
     @Override
     public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
-        return performDexOptTraced(packageName, instructionSet, false /* useProfiles */,
-                false /* extractOnly */, false /* force */);
+        return performDexOptTraced(packageName, instructionSet, false /* checkProfiles */,
+                getFullCompilerFilter(), false /* force */);
     }
 
     @Override
-    public boolean performDexOpt(String packageName, String instructionSet, boolean useProfiles,
-            boolean extractOnly, boolean force) {
-        return performDexOptTraced(packageName, instructionSet, useProfiles, extractOnly, force);
+    public boolean performDexOpt(String packageName, String instructionSet,
+            boolean checkProfiles, int compileReason, boolean force) {
+        return performDexOptTraced(packageName, instructionSet, checkProfiles,
+                getCompilerFilterForReason(compileReason), force);
+    }
+
+    @Override
+    public boolean performDexOptMode(String packageName, String instructionSet,
+            boolean checkProfiles, String targetCompilerFilter, boolean force) {
+        return performDexOptTraced(packageName, instructionSet, checkProfiles,
+                targetCompilerFilter, force);
     }
 
     private boolean performDexOptTraced(String packageName, String instructionSet,
-                boolean useProfiles, boolean extractOnly, boolean force) {
+                boolean checkProfiles, String targetCompilerFilter, boolean force) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
         try {
-            return performDexOptInternal(packageName, instructionSet, useProfiles, extractOnly,
-                    force);
+            return performDexOptInternal(packageName, instructionSet, checkProfiles,
+                    targetCompilerFilter, force);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
     private boolean performDexOptInternal(String packageName, String instructionSet,
-                boolean useProfiles, boolean extractOnly, boolean force) {
+                boolean checkProfiles, String targetCompilerFilter, boolean force) {
         PackageParser.Package p;
         final String targetInstructionSet;
         synchronized (mPackages) {
@@ -6987,7 +7012,7 @@
             synchronized (mInstallLock) {
                 final String[] instructionSets = new String[] { targetInstructionSet };
                 int result = performDexOptInternalWithDependenciesLI(p, instructionSets,
-                        useProfiles, extractOnly, force);
+                        checkProfiles, targetCompilerFilter, force);
                 return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
             }
         } finally {
@@ -7008,7 +7033,8 @@
     }
 
     private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
-            String instructionSets[], boolean useProfiles, boolean extractOnly, boolean force) {
+            String instructionSets[], boolean checkProfiles, String targetCompilerFilter,
+            boolean force) {
         // Select the dex optimizer based on the force parameter.
         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
         //       allocate an object here.
@@ -7022,13 +7048,13 @@
         if (!deps.isEmpty()) {
             for (PackageParser.Package depPackage : deps) {
                 // TODO: Analyze and investigate if we (should) profile libraries.
-                // Currently this will do a full compilation of the library.
-                pdo.performDexOpt(depPackage, instructionSets, false /* useProfiles */,
-                        false /* extractOnly */);
+                // Currently this will do a full compilation of the library by default.
+                pdo.performDexOpt(depPackage, instructionSets, false /* checkProfiles */,
+                        getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY));
             }
         }
 
-        return pdo.performDexOpt(p, instructionSets, useProfiles, extractOnly);
+        return pdo.performDexOpt(p, instructionSets, checkProfiles, targetCompilerFilter);
     }
 
     Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -7106,7 +7132,8 @@
             // Whoever is calling forceDexOpt wants a fully compiled package.
             // Don't use profiles since that may cause compilation to be skipped.
             final int res = performDexOptInternalWithDependenciesLI(pkg, instructionSets,
-                    false /* useProfiles */, false /* extractOnly */, true /* force */);
+                    false /* checkProfiles */, getCompilerFilterForReason(REASON_FORCED_DEXOPT),
+                    true /* force */);
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -7176,6 +7203,30 @@
         }
     }
 
+    private void deleteProfilesLI(String packageName, boolean destroy) {
+        final PackageParser.Package pkg;
+        synchronized (mPackages) {
+            pkg = mPackages.get(packageName);
+        }
+        if (pkg == null) {
+            Slog.w(TAG, "Failed to delete profiles. No package: " + packageName);
+            return;
+        }
+        deleteProfilesLI(pkg, destroy);
+    }
+
+    private void deleteProfilesLI(PackageParser.Package pkg, boolean destroy) {
+        try {
+            if (destroy) {
+                mInstaller.clearAppProfiles(pkg.packageName);
+            } else {
+                mInstaller.destroyAppProfiles(pkg.packageName);
+            }
+        } catch (InstallerException ex) {
+            Log.e(TAG, "Could not delete profiles for package " + pkg.packageName);
+        }
+    }
+
     private void deleteCodeCacheDirsLI(String volumeUuid, String packageName) {
         final PackageParser.Package pkg;
         synchronized (mPackages) {
@@ -13208,6 +13259,7 @@
             }
 
             deleteCodeCacheDirsLI(pkg);
+            deleteProfilesLI(pkg, /*destroy*/ false);
 
             try {
                 final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
@@ -13342,6 +13394,7 @@
 
         // Successfully disabled the old package. Now proceed with re-installation
         deleteCodeCacheDirsLI(pkg);
+        deleteProfilesLI(pkg, /*destroy*/ false);
 
         res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
         pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP,
@@ -13973,7 +14026,7 @@
             // Do not run PackageDexOptimizer through the local performDexOpt
             // method because `pkg` is not in `mPackages` yet.
             int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
-                    false /* useProfiles */, true /* extractOnly */);
+                    false /* checkProfiles */, getCompilerFilterForReason(REASON_INSTALL));
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
                 String msg = "Extracking package failed for " + pkgName;
@@ -14414,6 +14467,7 @@
             if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
             res = deletePackageLI(packageName, removeForUser, true, allUsers,
                     flags | REMOVE_CHATTY, info, true, null);
+            deleteProfilesLI(packageName, /*destroy*/ true);
             synchronized (mPackages) {
                 if (res) {
                     mEphemeralApplicationRegistry.onPackageUninstalledLPw(uninstalledPs.pkg);
@@ -15181,7 +15235,7 @@
     public void clearApplicationProfileData(String packageName) {
         enforceSystemOrRoot("Only the system can clear all profile data");
         try {
-            mInstaller.rmProfiles(packageName);
+            mInstaller.clearAppProfiles(packageName);
         } catch (InstallerException ex) {
             Log.e(TAG, "Could not clear profile data of package " + packageName);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
new file mode 100644
index 0000000..238e410
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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.pm;
+
+import android.os.SystemProperties;
+
+import dalvik.system.DexFile;
+
+/**
+ * Manage (retrieve) mappings from compilation reason to compilation filter.
+ */
+class PackageManagerServiceCompilerMapping {
+    // Names for compilation reasons.
+    static final String REASON_STRINGS[] = {
+            "boot", "install", "bg-dexopt", "ab-ota", "nsys-library", "shared-apk", "forced-dexopt"
+    };
+
+    // Static block to ensure the strings array is of the right length.
+    static {
+        if (PackageManagerService.REASON_LAST + 1 != REASON_STRINGS.length) {
+            throw new IllegalStateException("REASON_STRINGS not correct");
+        }
+    }
+
+    private static String getSystemPropertyName(int reason) {
+        if (reason < 0 || reason >= REASON_STRINGS.length) {
+            throw new IllegalArgumentException("reason " + reason + " invalid");
+        }
+
+        return "pm.dexopt." + REASON_STRINGS[reason];
+    }
+
+    // Load the property for the given reason and check for validity. This will throw an
+    // exception in case the reason or value are invalid.
+    private static String getAndCheckValidity(int reason) {
+        String sysPropValue = SystemProperties.get(getSystemPropertyName(reason));
+        if (sysPropValue == null || sysPropValue.isEmpty() ||
+                !DexFile.isValidCompilerFilter(sysPropValue)) {
+            throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
+                    + "(reason " + REASON_STRINGS[reason] + ")");
+        }
+
+        // Ensure that some reasons are not mapped to profile-guided filters.
+        switch (reason) {
+            case PackageManagerService.REASON_SHARED_APK:
+            case PackageManagerService.REASON_FORCED_DEXOPT:
+                if (DexFile.isProfileGuidedCompilerFilter(sysPropValue)) {
+                    throw new IllegalStateException("\"" + sysPropValue + "\" is profile-guided, "
+                            + "but not allowed for " + REASON_STRINGS[reason]);
+                }
+                break;
+        }
+
+        return sysPropValue;
+    }
+
+    // Check that the properties are set and valid.
+    // Note: this is done in a separate method so this class can be statically initialized.
+    static void checkProperties() {
+        // We're gonna check all properties and collect the exceptions, so we can give a general
+        // overview. Store the exceptions here.
+        RuntimeException toThrow = null;
+
+        for (int reason = 0; reason <= PackageManagerService.REASON_LAST; reason++) {
+            try {
+                // Check that the system property name is legal.
+                String sysPropName = getSystemPropertyName(reason);
+                if (sysPropName == null ||
+                        sysPropName.isEmpty() ||
+                        sysPropName.length() > SystemProperties.PROP_NAME_MAX) {
+                    throw new IllegalStateException("Reason system property name \"" +
+                            sysPropName +"\" for reason " + REASON_STRINGS[reason]);
+                }
+
+                // Check validity, ignore result.
+                getAndCheckValidity(reason);
+            } catch (Exception exc) {
+                if (toThrow == null) {
+                    toThrow = new IllegalStateException("PMS compiler filter settings are bad.");
+                }
+                toThrow.addSuppressed(exc);
+            }
+        }
+
+        if (toThrow != null) {
+            throw toThrow;
+        }
+    }
+
+    public static String getCompilerFilterForReason(int reason) {
+        return getAndCheckValidity(reason);
+    }
+
+    /**
+     * Return the compiler filter for "full" compilation.
+     *
+     * We derive that from the traditional "dalvik.vm.dex2oat-filter" property and just make
+     * sure this isn't profile-guided. Returns "speed" in case of invalid (or missing) values.
+     */
+    public static String getFullCompilerFilter() {
+        String value = SystemProperties.get("dalvik.vm.dex2oat-filter");
+        if (value == null || value.isEmpty()) {
+            return "speed";
+        }
+
+        if (!DexFile.isValidCompilerFilter(value) ||
+                DexFile.isProfileGuidedCompilerFilter(value)) {
+            return "speed";
+        }
+
+        return value;
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 319fc37..bf44b0f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -44,12 +44,13 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ShellCommand;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.PrintWriterPrinter;
 import com.android.internal.util.SizedInputStream;
 
+import dalvik.system.DexFile;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -249,11 +250,38 @@
     private int runCompile() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         boolean useJitProfiles = false;
-        boolean extractOnly = false;
         boolean forceCompilation = false;
         boolean allPackages = false;
         boolean clearProfileData = false;
-        String compilationMode = "default";
+        String compilerFilter = null;
+        String compilationReason = null;
+
+        if (peekNextArg() == null) {
+            // No arguments, show help.
+            pw.println("Usage: cmd package compile [-c] [-f] [--reset] [-m mode] " +
+                    "[-r reason] [-a|pkg]");
+            pw.println();
+            pw.println("  -c         Clear profile data");
+            pw.println("  -f         Force compilation");
+            pw.println("  --reset    Reset package");
+            pw.println("  -m mode    Compilation mode, one of the dex2oat compiler filters");
+            pw.println("               verify-none");
+            pw.println("               verify-at-runtime");
+            pw.println("               verify-profile");
+            pw.println("               interpret-only");
+            pw.println("               space-profile");
+            pw.println("               space");
+            pw.println("               speed-profile");
+            pw.println("               speed");
+            pw.println("               everything");
+            pw.println("  -r reason  Compiler reason, one of the package manager reasons");
+            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
+                pw.println("               " +
+                        PackageManagerServiceCompilerMapping.REASON_STRINGS[i]);
+            }
+            pw.println("  -a         Apply to all packages");
+            return 1;
+        }
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -268,12 +296,15 @@
                     forceCompilation = true;
                     break;
                 case "-m":
-                    compilationMode = getNextArgRequired();
+                    compilerFilter = getNextArgRequired();
+                    break;
+                case "-r":
+                    compilationReason = getNextArgRequired();
                     break;
                 case "--reset":
                     forceCompilation = true;
                     clearProfileData = true;
-                    compilationMode = "extract";
+                    compilerFilter = "reset";
                     break;
                 default:
                     pw.println("Error: Unknown option: " + opt);
@@ -281,27 +312,55 @@
             }
         }
 
-        switch (compilationMode) {
-            case "default":
-                useJitProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
-                extractOnly = false;
-                break;
-            case "full":
-                useJitProfiles = false;
-                extractOnly = false;
-                break;
-            case "profile":
-                useJitProfiles = true;
-                extractOnly = false;
-                break;
-            case "extract":
-                useJitProfiles = false;
-                extractOnly = true;
-                break;
-            default:
-                pw.println("Error: Unknown compilation mode: " + compilationMode);
-                return 1;
+        if (compilerFilter != null && compilationReason != null) {
+            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
+                    "at the same time");
+            return 1;
         }
+        if (compilerFilter == null && compilationReason == null) {
+            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
+                    "reason (\"-r\") at the same time");
+            return 1;
+        }
+
+        String targetCompilerFilter;
+        if (compilerFilter != null) {
+            // Specially recognize default and reset. Otherwise, only accept valid modes.
+            if ("default".equals(compilerFilter)) {
+                // Use the default mode for background dexopt.
+                targetCompilerFilter =
+                        PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                                PackageManagerService.REASON_BACKGROUND_DEXOPT);
+            } else if ("reset".equals(compilerFilter)) {
+                // Use the default mode for install.
+                targetCompilerFilter =
+                        PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                                PackageManagerService.REASON_INSTALL);
+            } else {
+                if (!DexFile.isValidCompilerFilter(compilerFilter)) {
+                    pw.println("Error: \"" + compilerFilter +
+                            "\" is not a valid compilation filter.");
+                    return 1;
+                }
+                targetCompilerFilter = compilerFilter;
+            }
+        } else {
+            int reason = -1;
+            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
+                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
+                        compilationReason)) {
+                    reason = i;
+                    break;
+                }
+            }
+            if (reason == -1) {
+                pw.println("Error: Unknown compilation reason: " + compilationReason);
+                return 1;
+            }
+            targetCompilerFilter =
+                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
+        }
+
 
         List<String> packageNames = null;
         if (allPackages) {
@@ -321,8 +380,8 @@
                 mInterface.clearApplicationProfileData(packageName);
             }
 
-            boolean result = mInterface.performDexOpt(packageName, null /* instructionSet */,
-                        useJitProfiles, extractOnly, forceCompilation);
+            boolean result = mInterface.performDexOptMode(packageName, null /* instructionSet */,
+                    useJitProfiles, targetCompilerFilter, forceCompilation);
             if (!result) {
                 failedPackages.add(packageName);
             }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e66ec3c..fa0eb46 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4456,7 +4456,8 @@
             }
         }
 
-        if ((permissionNames != null || dumpAll) && ps.pkg.requestedPermissions != null
+        if ((permissionNames != null || dumpAll) && ps.pkg != null
+                && ps.pkg.requestedPermissions != null
                 && ps.pkg.requestedPermissions.size() > 0) {
             final ArrayList<String> perms = ps.pkg.requestedPermissions;
             pw.print(prefix); pw.println("  requested permissions:");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 715f1e5..ac19e24 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -458,7 +458,7 @@
                     continue;
                 }
                 if (!excludeDying || !mRemovingUserIds.get(ui.id)) {
-                    users.add(ui);
+                    users.add(userWithName(ui));
                 }
             }
             return users;
@@ -500,7 +500,7 @@
             if (mRemovingUserIds.get(profile.id)) {
                 continue;
             }
-            users.add(profile);
+            users.add(userWithName(profile));
         }
         return users;
     }
@@ -653,7 +653,21 @@
     public UserInfo getUserInfo(int userId) {
         checkManageUsersPermission("query user");
         synchronized (mUsersLock) {
-            return getUserInfoLU(userId);
+            return userWithName(getUserInfoLU(userId));
+        }
+    }
+
+    /**
+     * Returns a UserInfo object with the name filled in, for Owner, or the original
+     * if the name is already set.
+     */
+    private UserInfo userWithName(UserInfo orig) {
+        if (orig != null && orig.name == null && orig.id == UserHandle.USER_SYSTEM) {
+            UserInfo withName = new UserInfo(orig);
+            withName.name = getOwnerName();
+            return withName;
+        } else {
+            return orig;
         }
     }
 
@@ -1459,9 +1473,7 @@
             flags |= UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY;
         }
         // Create the system user
-        UserInfo system = new UserInfo(UserHandle.USER_SYSTEM,
-                mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
-                flags);
+        UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags);
         UserData userData = new UserData();
         userData.info = system;
         synchronized (mUsersLock) {
@@ -1482,6 +1494,10 @@
         writeUserLP(userData);
     }
 
+    private String getOwnerName() {
+        return mContext.getResources().getString(com.android.internal.R.string.owner_name);
+    }
+
     private void scheduleWriteUser(UserData UserData) {
         if (DBG) {
             debug("scheduleWriteUser");
@@ -1551,9 +1567,11 @@
                     serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE, userData.seedAccountType);
                 }
             }
-            serializer.startTag(null, TAG_NAME);
-            serializer.text(userInfo.name);
-            serializer.endTag(null, TAG_NAME);
+            if (userInfo.name != null) {
+                serializer.startTag(null, TAG_NAME);
+                serializer.text(userInfo.name);
+                serializer.endTag(null, TAG_NAME);
+            }
             synchronized (mRestrictionsLock) {
                 UserRestrictionsUtils.writeRestrictions(serializer,
                         mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
diff --git a/services/core/java/com/android/server/policy/EnableAccessibilityController.java b/services/core/java/com/android/server/policy/EnableAccessibilityController.java
index da9c001..6b203a9 100644
--- a/services/core/java/com/android/server/policy/EnableAccessibilityController.java
+++ b/services/core/java/com/android/server/policy/EnableAccessibilityController.java
@@ -16,7 +16,9 @@
 
 package com.android.server.policy;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -32,19 +34,25 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.speech.tts.TextToSpeech;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.IWindowManager;
 import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 
 import com.android.internal.R;
+import com.android.server.LocalServices;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
 public class EnableAccessibilityController {
+    private static final String TAG = "EnableAccessibilityController";
 
     private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
     private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
@@ -75,9 +83,6 @@
         }
     };
 
-    private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
-            ServiceManager.getService("window"));
-
     private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
             .Stub.asInterface(ServiceManager.getService("accessibility"));
 
@@ -132,7 +137,7 @@
                 && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
     }
 
-    private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
+    public static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
             Context context) {
         List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
         services.addAll(AccessibilityManager.getInstance(context)
@@ -213,71 +218,74 @@
     }
 
     private void enableAccessibility() {
-        List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
-                mContext);
-        if (services.isEmpty()) {
+        if (enableAccessibility(mContext)) {
+            mOnAccessibilityEnabledCallback.run();
+        }
+    }
+
+    public static boolean enableAccessibility(Context context) {
+        final IAccessibilityManager accessibilityManager = IAccessibilityManager
+                .Stub.asInterface(ServiceManager.getService("accessibility"));
+        final WindowManagerInternal windowManager = LocalServices.getService(
+                WindowManagerInternal.class);
+        final UserManager userManager = (UserManager) context.getSystemService(
+                Context.USER_SERVICE);
+        ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
+        if (componentName == null) {
+            return false;
+        }
+
+        boolean keyguardLocked = windowManager.isKeyguardLocked();
+        final boolean hasMoreThanOneUser = userManager.getUsers().size() > 1;
+        try {
+            if (!keyguardLocked || !hasMoreThanOneUser) {
+                final int userId = ActivityManager.getCurrentUser();
+                accessibilityManager.enableAccessibilityService(componentName, userId);
+            } else if (keyguardLocked) {
+                accessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
+                        componentName, true /* enableTouchExploration */);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "cannot enable accessibilty: " + e);
+        }
+
+        return true;
+    }
+
+    public static void disableAccessibility(Context context) {
+        final IAccessibilityManager accessibilityManager = IAccessibilityManager
+                .Stub.asInterface(ServiceManager.getService("accessibility"));
+        ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
+        if (componentName == null) {
             return;
         }
-        boolean keyguardLocked = false;
+
+        final int userId = ActivityManager.getCurrentUser();
         try {
-            keyguardLocked = mWindowManager.isKeyguardLocked();
-        } catch (RemoteException re) {
-            /* ignore */
+            accessibilityManager.disableAccessibilityService(componentName, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "cannot disable accessibility " + e);
+        }
+    }
+
+    public static boolean isAccessibilityEnabled(Context context) {
+        final AccessibilityManager accessibilityManager =
+                context.getSystemService(AccessibilityManager.class);
+        List enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
+                AccessibilityServiceInfo.FEEDBACK_SPOKEN);
+        return enabledServices != null && !enabledServices.isEmpty();
+    }
+
+    @Nullable
+    public static ComponentName getInstalledSpeakingAccessibilityServiceComponent(
+            Context context) {
+        List<AccessibilityServiceInfo> services =
+                getInstalledSpeakingAccessibilityServices(context);
+        if (services.isEmpty()) {
+            return null;
         }
 
-        final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
-
-        AccessibilityServiceInfo service = services.get(0);
-        boolean enableTouchExploration = (service.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
-        // Try to find a service supporting explore by touch.
-        if (!enableTouchExploration) {
-            final int serviceCount = services.size();
-            for (int i = 1; i < serviceCount; i++) {
-                AccessibilityServiceInfo candidate = services.get(i);
-                if ((candidate.flags & AccessibilityServiceInfo
-                        .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
-                    enableTouchExploration = true;
-                    service = candidate;
-                    break;
-                }
-            }
-        }
-
-        ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
-        ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-        if (!keyguardLocked || !hasMoreThanOneUser) {
-            final int userId = ActivityManager.getCurrentUser();
-            String enabledServiceString = componentName.flattenToString();
-            ContentResolver resolver = mContext.getContentResolver();
-            // Enable one speaking accessibility service.
-            Settings.Secure.putStringForUser(resolver,
-                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                    enabledServiceString, userId);
-            // Allow the services we just enabled to toggle touch exploration.
-            Settings.Secure.putStringForUser(resolver,
-                    Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                    enabledServiceString, userId);
-            // Enable touch exploration.
-            if (enableTouchExploration) {
-                Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
-                        1, userId);
-            }
-            // Enable accessibility script injection (AndroidVox) for web content.
-            Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
-                    1, userId);
-            // Turn on accessibility mode last.
-            Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
-                    1, userId);
-        } else if (keyguardLocked) {
-            try {
-                mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
-                        componentName, enableTouchExploration);
-            } catch (RemoteException re) {
-                /* ignore */
-            }
-        }
-
-        mOnAccessibilityEnabledCallback.run();
+        ServiceInfo serviceInfo = services.get(0).getResolveInfo().serviceInfo;
+        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
     }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0115a08..d92d942 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3101,7 +3101,7 @@
         } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) {
             if (down) {
                 if (repeatCount == 0) {
-                    toggleKeyboardShortcutsMenu();
+                    toggleKeyboardShortcutsMenu(event.getDeviceId());
                 }
             }
         } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
@@ -3576,11 +3576,11 @@
         }
     }
 
-    private void toggleKeyboardShortcutsMenu() {
+    private void toggleKeyboardShortcutsMenu(int deviceId) {
         try {
             IStatusBarService statusbar = getStatusBarService();
             if (statusbar != null) {
-                statusbar.toggleKeyboardShortcutsMenu();
+                statusbar.toggleKeyboardShortcutsMenu(deviceId);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException when showing keyboard shortcuts menu", e);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d24e1af..403a4c6 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -564,10 +564,10 @@
     }
 
     @Override
-    public void toggleKeyboardShortcutsMenu() {
+    public void toggleKeyboardShortcutsMenu(int deviceId) {
         if (mBar != null) {
             try {
-                mBar.toggleKeyboardShortcutsMenu();
+                mBar.toggleKeyboardShortcutsMenu(deviceId);
             } catch (RemoteException ex) {}
         }
     }
diff --git a/services/core/java/com/android/server/utils/ManagedApplicationService.java b/services/core/java/com/android/server/utils/ManagedApplicationService.java
index ad8acef0..0f251fd 100644
--- a/services/core/java/com/android/server/utils/ManagedApplicationService.java
+++ b/services/core/java/com/android/server/utils/ManagedApplicationService.java
@@ -62,8 +62,6 @@
     private IInterface mBoundInterface;
     private PendingEvent mPendingEvent;
 
-
-
     private ManagedApplicationService(final Context context, final ComponentName component,
             final int userId, int clientLabel, String settingsAction,
             BinderChecker binderChecker) {
@@ -211,6 +209,7 @@
                         } else {
                             // Service connection wasn't pending, must have been disconnected
                             mContext.unbindService(this);
+                            return;
                         }
 
                         try {
@@ -242,6 +241,8 @@
                 @Override
                 public void onServiceDisconnected(ComponentName componentName) {
                     Slog.w(TAG, "Service disconnected: " + intent);
+                    mConnection = null;
+                    mBoundInterface = null;
                 }
             };
 
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
index 8316efa..93bb9d7 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -38,6 +38,17 @@
     public abstract boolean isInVrMode();
 
     /**
+     * Return {@code true} if the given package is the currently bound VrListenerService for the
+     * given user.
+     *
+     * @param packageName The package name to check.
+     * @param userId the user ID to check the package name for.
+     *
+     * @return {@code true} if the given package is the currently bound VrListenerService.
+     */
+    public abstract boolean isCurrentVrListener(String packageName, int userId);
+
+    /**
      * Set the current VR mode state.
      *
      * @param enabled {@code true} to enable VR mode.
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 6bf949c..aa6f59e 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -127,6 +127,11 @@
         }
 
         @Override
+        public boolean isCurrentVrListener(String packageName, int userId) {
+            return VrManagerService.this.isCurrentVrListener(packageName, userId);
+        }
+
+        @Override
         public void registerListener(VrStateListener listener) {
             VrManagerService.this.addListener(listener);
         }
@@ -355,6 +360,16 @@
         }
     }
 
+    private boolean isCurrentVrListener(String packageName, int userId) {
+        synchronized (mLock) {
+            if (mCurrentVrService == null) {
+                return false;
+            }
+            return mCurrentVrService.getComponent().getPackageName().equals(packageName) &&
+                    userId == mCurrentVrService.getUserId();
+        }
+    }
+
     private void addListener(VrStateListener listener) {
         synchronized (mLock) {
             mListeners.add(listener);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5cb7099..2a57d8e 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -43,9 +43,12 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Debug;
@@ -943,6 +946,8 @@
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
         final int thumbHeightI = mTmpRect.height();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+        final int thumbStartX = mTmpRect.left - containingFrame.left;
+        final int thumbStartY = mTmpRect.top - containingFrame.top;
 
         // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
         float scale = 1f;
@@ -954,6 +959,9 @@
                     a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
                             containingFrame, surfaceInsets, taskId);
                 } else {
+                    AnimationSet set = new AnimationSet(true);
+
+                    // In portrait, we scale to fit the width
                     mTmpFromClipRect.set(containingFrame);
                     mTmpToClipRect.set(containingFrame);
 
@@ -964,26 +972,40 @@
 
                     // Exclude insets region from the source clip.
                     mTmpFromClipRect.inset(contentInsets);
-
-                    // We scale the width and clip to the top/left square
-                    scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
-                    scaledTopDecor = (int) (scale * contentInsets.top);
-                    int unscaledThumbHeight = (int) (thumbHeight / scale);
-                    mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
-
                     mNextAppTransitionInsets.set(contentInsets);
 
-                    Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
-                            computePivot(mTmpRect.left - containingFrame.left, scale),
-                            computePivot(mTmpRect.top - containingFrame.top, scale));
-                    Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
-                    Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
+                    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+                        // We scale the width and clip to the top/left square
+                        scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
+                        scaledTopDecor = (int) (scale * contentInsets.top);
+                        int unscaledThumbHeight = (int) (thumbHeight / scale);
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
 
-                    AnimationSet set = new AnimationSet(true);
-                    set.addAnimation(clipAnim);
-                    set.addAnimation(scaleAnim);
-                    set.addAnimation(translateAnim);
+                        Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
+                                computePivot(mTmpRect.left - containingFrame.left, scale),
+                                computePivot(mTmpRect.top - containingFrame.top, scale));
+                        Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+                        Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(scaleAnim);
+                        set.addAnimation(translateAnim);
+
+                    } else {
+                        // In landscape, we don't scale at all and only crop
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
+                        mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
+
+                        Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+                        Animation translateAnim = new TranslateAnimation(thumbStartX, 0,
+                                thumbStartY - contentInsets.top, 0);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(translateAnim);
+                    }
+
                     a = set;
+                    a.setZAdjustment(Animation.ZORDER_TOP);
                 }
                 break;
             }
@@ -1015,6 +1037,7 @@
                     a = createAspectScaledThumbnailExitFreeformAnimationLocked(
                             containingFrame, surfaceInsets, taskId);
                 } else {
+                    AnimationSet set = new AnimationSet(true);
                     mTmpFromClipRect.set(containingFrame);
                     mTmpToClipRect.set(containingFrame);
 
@@ -1025,25 +1048,37 @@
 
                     // Exclude insets region from the target clip.
                     mTmpToClipRect.inset(contentInsets);
-
-                    // We scale the width and clip to the top/left square
-                    scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
-                    scaledTopDecor = (int) (scale * contentInsets.top);
-                    int unscaledThumbHeight = (int) (thumbHeight / scale);
-                    mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
-
                     mNextAppTransitionInsets.set(contentInsets);
 
-                    Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
-                            computePivot(mTmpRect.left - containingFrame.left, scale),
-                            computePivot(mTmpRect.top - containingFrame.top, scale));
-                    Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
-                    Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
+                    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+                        // We scale the width and clip to the top/left square
+                        scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
+                        scaledTopDecor = (int) (scale * contentInsets.top);
+                        int unscaledThumbHeight = (int) (thumbHeight / scale);
+                        mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
 
-                    AnimationSet set = new AnimationSet(true);
-                    set.addAnimation(clipAnim);
-                    set.addAnimation(scaleAnim);
-                    set.addAnimation(translateAnim);
+                        Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
+                                computePivot(mTmpRect.left - containingFrame.left, scale),
+                                computePivot(mTmpRect.top - containingFrame.top, scale));
+                        Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+                        Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(scaleAnim);
+                        set.addAnimation(translateAnim);
+
+                    } else {
+                        // In landscape, we don't scale at all and only crop
+                        mTmpToClipRect.bottom = mTmpToClipRect.top + thumbHeightI;
+                        mTmpToClipRect.right = mTmpToClipRect.left + thumbWidthI;
+
+                        Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+                        Animation translateAnim = new TranslateAnimation(0, thumbStartX, 0,
+                                thumbStartY - contentInsets.top);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(translateAnim);
+                    }
 
                     a = set;
                     a.setZAdjustment(Animation.ZORDER_TOP);
@@ -1467,6 +1502,12 @@
         return a;
     }
 
+    int getAppStackClipMode() {
+        return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH
+                ? STACK_CLIP_NONE
+                : STACK_CLIP_AFTER_ANIM;
+    }
+
     void postAnimationCallback() {
         if (mNextAppTransitionCallback != null) {
             mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK,
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 3a5dec9..6225fc6 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -23,6 +23,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
 import android.graphics.Matrix;
 import android.util.Slog;
@@ -106,6 +107,7 @@
     boolean usingTransferredAnimation = false;
 
     private boolean mSkipFirstFrame = false;
+    private int mStackClip = STACK_CLIP_BEFORE_ANIM;
 
     static final Animation sDummyAnimation = new DummyAnimation();
 
@@ -115,7 +117,8 @@
         mAnimator = mService.mAnimator;
     }
 
-    public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame) {
+    public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame,
+            int stackClip) {
         if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
                 + ": " + anim + " wxh=" + width + "x" + height
                 + " isVisible=" + mAppToken.isVisible());
@@ -142,6 +145,7 @@
         transformation.clear();
         transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
         hasTransformation = true;
+        mStackClip = stackClip;
 
         this.mSkipFirstFrame = skipFirstFrame;
 
@@ -186,6 +190,7 @@
             mAppToken.allDrawn = false;
             mAppToken.deferClearAllDrawn = false;
         }
+        mStackClip = STACK_CLIP_BEFORE_ANIM;
     }
 
     public boolean isAnimating() {
@@ -201,6 +206,10 @@
         deferThumbnailDestruction = false;
     }
 
+    int getStackClip() {
+        return mStackClip;
+    }
+
     void transferCurrentAnimation(
             AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) {
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 36e8bbb..6741aba 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -96,6 +96,7 @@
     private final Interpolator mMinimizedDockInterpolator;
     private float mMaximizeMeetFraction;
     private final Rect mTouchRegion = new Rect();
+    private boolean mAdjustingForIme;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -173,6 +174,14 @@
         return mLastVisibility;
     }
 
+    void setAdjustingForIme(boolean adjusting) {
+        mAdjustingForIme = adjusting;
+    }
+
+    boolean isAdjustingForIme() {
+        return mAdjustingForIme;
+    }
+
     void positionDockedStackedDivider(Rect frame) {
         TaskStack stack = mDisplayContent.getDockedStackLocked();
         if (stack == null) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index c667767..8d67771 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -200,10 +200,11 @@
      * @param bounds The adjusted bounds.
      * @param keepInsets Whether to keep the insets from the original bounds or to calculate new
      *                   ones depending on the adjusted bounds.
+     * @return true if the adjusted bounds has changed.
      */
-    private void setAdjustedBounds(Rect bounds, boolean keepInsets) {
+    private boolean setAdjustedBounds(Rect bounds, boolean keepInsets) {
         if (mAdjustedBounds.equals(bounds)) {
-            return;
+            return false;
         }
 
         mAdjustedBounds.set(bounds);
@@ -211,6 +212,7 @@
         alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds,
                 adjusted && keepInsets ? mBounds : null);
         mDisplayContent.layoutNeeded = true;
+        return true;
     }
 
     private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
@@ -794,7 +796,9 @@
     void setAdjustedForIme(WindowState imeWin) {
         mAdjustedForIme = true;
         mImeWin = imeWin;
-        updateAdjustedBounds();
+        if (updateAdjustedBounds()) {
+            getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true);
+        }
     }
 
     /**
@@ -803,7 +807,9 @@
     void resetAdjustedForIme() {
         mAdjustedForIme = false;
         mImeWin = null;
-        updateAdjustedBounds();
+        if (updateAdjustedBounds()) {
+            getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true);
+        }
     }
 
     /**
@@ -920,7 +926,7 @@
     /**
      * Updates the adjustment depending on it's current state.
      */
-    void updateAdjustedBounds() {
+    boolean updateAdjustedBounds() {
         boolean adjust = false;
         if (mMinimizeAmount != 0f) {
             adjust = adjustForMinimizedDockedStack(mMinimizeAmount);
@@ -931,7 +937,7 @@
             mTmpAdjustedBounds.setEmpty();
             mLastContentBounds.setEmpty();
         }
-        setAdjustedBounds(mTmpAdjustedBounds, isAdjustedForMinimizedDockedStack());
+        return setAdjustedBounds(mTmpAdjustedBounds, isAdjustedForMinimizedDockedStack());
     }
 
     boolean isAdjustedForMinimizedDockedStack() {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index f243761..eae7838 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -36,6 +36,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
@@ -406,7 +408,8 @@
 
                             Animation a = mPolicy.createForceHideEnterAnimation(false,
                                     keyguardGoingAwayToShade);
-                            winAnimator.setAnimation(a, mPostKeyguardExitAnimation.getStartTime());
+                            winAnimator.setAnimation(a, mPostKeyguardExitAnimation.getStartTime(),
+                                    STACK_CLIP_BEFORE_ANIM);
                             winAnimator.mKeyguardGoingAwayAnimation = true;
                             winAnimator.mKeyguardGoingAwayWithWallpaper
                                     = keyguardGoingAwayWithWallpaper;
@@ -445,7 +448,7 @@
             }
 
             final AppWindowToken atoken = win.mAppToken;
-            if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
+            if (winAnimator.mDrawState == READY_TO_SHOW) {
                 if (atoken == null || atoken.allDrawn) {
                     if (winAnimator.performShowLocked()) {
                         setPendingLayoutChanges(displayId,
@@ -487,7 +490,7 @@
                     if (a != null) {
                         if (DEBUG_KEYGUARD) Slog.v(TAG,
                                 "Starting keyguard exit animation on window " + winAnimator.mWin);
-                        winAnimator.setAnimation(a);
+                        winAnimator.setAnimation(a, STACK_CLIP_BEFORE_ANIM);
                         winAnimator.mKeyguardGoingAwayAnimation = true;
                         winAnimator.mKeyguardGoingAwayWithWallpaper
                                 = keyguardGoingAwayWithWallpaper;
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index d843a8c..e018a4e 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -77,28 +77,22 @@
             int oldLayer = w.mLayer;
             if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) {
                 curLayer += WINDOW_LAYER_MULTIPLIER;
-                w.mLayer = curLayer;
             } else {
                 curBaseLayer = curLayer = w.mBaseLayer;
-                w.mLayer = curLayer;
             }
-            if (w.mLayer != oldLayer) {
-                layerChanged = true;
-                anyLayerChanged = true;
-            }
+            assignAnimLayer(w, curLayer);
 
-            final WindowStateAnimator winAnimator = w.mWinAnimator;
-            oldLayer = winAnimator.mAnimLayer;
-            winAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() +
-                    getSpecialWindowAnimLayerAdjustment(w);
-            if (winAnimator.mAnimLayer != oldLayer) {
+            // TODO: Preserved old behavior of code here but not sure comparing
+            // oldLayer to mAnimLayer and mLayer makes sense...though the
+            // worst case would be unintentionalp layer reassignment.
+            if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
                 layerChanged = true;
                 anyLayerChanged = true;
             }
 
             if (w.mAppToken != null) {
                 mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
-                        winAnimator.mAnimLayer);
+                        w.mWinAnimator.mAnimLayer);
             }
             collectSpecialWindows(w);
 
@@ -223,12 +217,17 @@
 
     private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
         if (win != null) {
-            win.mLayer = layer;
-            win.mWinAnimator.mAnimLayer = layer;
+            assignAnimLayer(win, layer);
             layer++;
         }
         return layer;
     }
+    
+    private void assignAnimLayer(WindowState w, int layer) {
+        w.mLayer = layer;
+        w.mWinAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() +
+                    getSpecialWindowAnimLayerAdjustment(w);
+    }
 
     void dump(PrintWriter pw, String s) {
         if (mInputMethodAnimLayerAdjustment != 0 ||
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 14291ca..be1d8067 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -236,6 +236,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
@@ -3039,7 +3040,7 @@
                 final int containingWidth = frame.width();
                 final int containingHeight = frame.height();
                 atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
-                        mAppTransition.canSkipFirstFrame());
+                        mAppTransition.canSkipFirstFrame(), mAppTransition.getAppStackClipMode());
             }
         } else {
             atoken.mAppAnimator.clearAnimation();
@@ -5735,7 +5736,7 @@
         if (mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_windowEnableCircularEmulatorDisplayOverlay)
                 && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false)
-                && Build.HARDWARE.contains("goldfish")) {
+                && Build.IS_EMULATOR) {
             mH.sendMessage(mH.obtainMessage(H.SHOW_EMULATOR_DISPLAY_OVERLAY));
         }
     }
@@ -7352,6 +7353,30 @@
         }
     }
 
+    private void adjustForImeIfNeeded(final DisplayContent displayContent) {
+        final WindowState imeWin = mInputMethodWindow;
+        final TaskStack focusedStack =
+                mCurrentFocus != null ? mCurrentFocus.getStack() : null;
+        if (imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
+                && isStackVisibleLocked(DOCKED_STACK_ID)
+                && focusedStack != null
+                && focusedStack.getDockSide() == DOCKED_BOTTOM){
+            final ArrayList<TaskStack> stacks = displayContent.getStacks();
+            for (int i = stacks.size() - 1; i >= 0; --i) {
+                final TaskStack stack = stacks.get(i);
+                if (stack.isVisibleLocked()) {
+                    stack.setAdjustedForIme(imeWin);
+                }
+            }
+        } else {
+            final ArrayList<TaskStack> stacks = displayContent.getStacks();
+            for (int i = stacks.size() - 1; i >= 0; --i) {
+                final TaskStack stack = stacks.get(i);
+                stack.resetAdjustedForIme();
+            }
+        }
+    }
+
     // -------------------------------------------------------------
     // Drag and drop
     // -------------------------------------------------------------
@@ -8209,30 +8234,8 @@
                 case UPDATE_DOCKED_STACK_DIVIDER: {
                     synchronized (mWindowMap) {
                         final DisplayContent displayContent = getDefaultDisplayContentLocked();
-
                         displayContent.getDockedDividerController().reevaluateVisibility(false);
-
-                        final WindowState imeWin = mInputMethodWindow;
-                        final TaskStack focusedStack =
-                                mCurrentFocus != null ? mCurrentFocus.getStack() : null;
-                        if (imeWin != null && imeWin.isVisibleNow()
-                                && isStackVisibleLocked(DOCKED_STACK_ID)
-                                && focusedStack != null
-                                && focusedStack.getDockSide() == DOCKED_BOTTOM){
-                            final ArrayList<TaskStack> stacks = displayContent.getStacks();
-                            for (int i = stacks.size() - 1; i >= 0; --i) {
-                                final TaskStack stack = stacks.get(i);
-                                if (stack.isVisibleLocked()) {
-                                    stack.setAdjustedForIme(imeWin);
-                                }
-                            }
-                        } else {
-                            final ArrayList<TaskStack> stacks = displayContent.getStacks();
-                            for (int i = stacks.size() - 1; i >= 0; --i) {
-                                final TaskStack stack = stacks.get(i);
-                                stack.resetAdjustedForIme();
-                            }
-                        }
+                        adjustForImeIfNeeded(displayContent);
                     }
                 }
                 break;
@@ -8971,7 +8974,7 @@
                                 + ", mDrawState=DRAW_PENDING in " + w
                                 + ", surfaceController " + winAnimator.mSurfaceController);
                     }
-                    winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                    winAnimator.mDrawState = DRAW_PENDING;
                     if (w.mAppToken != null) {
                         w.mAppToken.allDrawn = false;
                         w.mAppToken.deferClearAllDrawn = false;
@@ -10515,9 +10518,9 @@
     }
 
     @Override
-    public void requestAppKeyboardShortcuts(IResultReceiver receiver) {
+    public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
         try {
-            getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver);
+            getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver, deviceId);
         } catch (RemoteException e) {
         }
     }
@@ -10785,7 +10788,7 @@
                     final boolean isForceHiding = mPolicy.isForceHiding(win.mAttrs);
                     if (win.isVisibleLw()
                             && (win.mAppToken != null || isForceHiding)) {
-                        win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                        win.mWinAnimator.mDrawState = DRAW_PENDING;
                         // Force add to mResizingWindows.
                         win.mLastContentInsets.set(-1, -1, -1, -1);
                         mWaitingForDrawn.add(win);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1695615..38fda1b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -71,6 +71,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -655,8 +656,7 @@
         }
 
         if (mInsetFrame.isEmpty()  && (fullscreenTask
-                || (isChildWindow() && (mAttrs.privateFlags
-                        & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0))) {
+                || layoutInParentFrame())) {
             // We use the parent frame as the containing frame for fullscreen and child windows
             mContainingFrame.set(pf);
             mDisplayFrame.set(df);
@@ -1883,6 +1883,13 @@
     }
 
     private boolean shouldSaveSurface() {
+        if ((mAttrs.flags & FLAG_SECURE) != 0) {
+            // We don't save secure surfaces since their content shouldn't be shown while the app
+            // isn't on screen and content might leak through during the transition animation with
+            // saved surface.
+            return false;
+        }
+
         if (ActivityManager.isLowRamDeviceStatic()) {
             // Don't save surfaces on Svelte devices.
             return false;
@@ -2521,7 +2528,7 @@
         final int ph = mContainingFrame.height();
         final Task task = getTask();
         final boolean nonFullscreenTask = inMultiWindowMode();
-        final boolean fitToDisplay = task != null && !task.isFloating();
+        final boolean fitToDisplay = task != null && !task.isFloating() && !layoutInParentFrame();
         float x, y;
         int w,h;
 
@@ -2595,6 +2602,10 @@
         return mAttachedWindow != null;
     }
 
+    boolean layoutInParentFrame() {
+        return isChildWindow() && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
+    }
+
     void setReplacing(boolean animate) {
         if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
                 || mAttrs.type == TYPE_APPLICATION_STARTING) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 41eafe7..83b6104 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -75,6 +75,25 @@
     static final String TAG = TAG_WITH_CLASS_NAME ? "WindowStateAnimator" : TAG_WM;
     static final int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
 
+    /**
+     * Mode how the window gets clipped by the stack bounds during an animation: The clipping should
+     * be applied after applying the animation transformation, i.e. the stack bounds don't move
+     * during the animation.
+     */
+    static final int STACK_CLIP_AFTER_ANIM = 0;
+
+    /**
+     * Mode how the window gets clipped by the stack bounds: The clipping should be applied before
+     * applying the animation transformation, i.e. the stack bounds move with the window.
+     */
+    static final int STACK_CLIP_BEFORE_ANIM = 1;
+
+    /**
+     * Mode how window gets clipped by the stack bounds during an animation: Don't clip the window
+     * by the stack bounds.
+     */
+    static final int STACK_CLIP_NONE = 2;
+
     // Unchanging local convenience fields.
     final WindowManagerService mService;
     final WindowState mWin;
@@ -100,6 +119,7 @@
     int mLastLayer;
     long mAnimationStartTime;
     long mLastAnimationTime;
+    int mStackClip = STACK_CLIP_BEFORE_ANIM;
 
     /**
      * Set when we have changed the size of the surface, to know that
@@ -128,7 +148,9 @@
     boolean mHasClipRect;
     Rect mClipRect = new Rect();
     Rect mTmpClipRect = new Rect();
+    Rect mTmpFinalClipRect = new Rect();
     Rect mLastClipRect = new Rect();
+    Rect mLastFinalClipRect = new Rect();
     Rect mTmpStackBounds = new Rect();
 
     /**
@@ -226,7 +248,7 @@
         mWallpaperControllerLocked = mService.mWallpaperControllerLocked;
     }
 
-    public void setAnimation(Animation anim, long startTime) {
+    public void setAnimation(Animation anim, long startTime, int stackClip) {
         if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);
         mAnimating = false;
         mLocalAnimating = false;
@@ -238,10 +260,15 @@
         mTransformation.setAlpha(mLastHidden ? 0 : 1);
         mHasLocalTransformation = true;
         mAnimationStartTime = startTime;
+        mStackClip = stackClip;
+    }
+
+    public void setAnimation(Animation anim, int stackClip) {
+        setAnimation(anim, -1, stackClip);
     }
 
     public void setAnimation(Animation anim) {
-        setAnimation(anim, -1);
+        setAnimation(anim, -1, STACK_CLIP_AFTER_ANIM);
     }
 
     public void clearAnimation() {
@@ -252,6 +279,7 @@
             mAnimation = null;
             mKeyguardGoingAwayAnimation = false;
             mKeyguardGoingAwayWithWallpaper = false;
+            mStackClip = STACK_CLIP_BEFORE_ANIM;
         }
     }
 
@@ -397,6 +425,7 @@
         if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this + " anim layer: " + mAnimLayer);
         mHasTransformation = false;
         mHasLocalTransformation = false;
+        mStackClip = STACK_CLIP_BEFORE_ANIM;
         mWin.checkPolicyVisibilityChange();
         mTransformation.clear();
         if (mDrawState == HAS_DRAWN
@@ -994,6 +1023,17 @@
                     if (appTransformation.hasClipRect()) {
                         mClipRect.set(appTransformation.getClipRect());
                         mHasClipRect = true;
+                        // The app transformation clip will be in the coordinate space of the main
+                        // activity window, which the animation correctly assumes will be placed at
+                        // (0,0)+(insets) relative to the containing frame. This isn't necessarily
+                        // true for child windows though which can have an arbitrary frame position
+                        // relative to their containing frame. We need to offset the difference
+                        // between the containing frame as used to calculate the crop and our
+                        // bounds to compensate for this.
+                        if (mWin.isChildWindow() && mWin.layoutInParentFrame()) {
+                            mClipRect.offset( (mWin.mContainingFrame.left - mWin.mFrame.left),
+                                    mWin.mContainingFrame.top - mWin.mFrame.top );
+                        }
                     }
                 }
                 if (screenAnimation) {
@@ -1119,11 +1159,13 @@
         }
     }
 
-    Rect calculateSurfaceWindowCrop() {
+    void calculateSurfaceWindowCrop(Rect clipRect, Rect finalClipRect) {
         final WindowState w = mWin;
         final DisplayContent displayContent = w.getDisplayContent();
         if (displayContent == null) {
-            return null;
+            clipRect.setEmpty();
+            finalClipRect.setEmpty();
+            return;
         }
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Updating crop for window: " + w + ", " + "mLastCrop=" +
@@ -1159,7 +1201,6 @@
         final boolean fullscreen = w.isFrameFullscreen(displayInfo);
         final boolean isFreeformResizing =
                 w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
-        final Rect clipRect = mTmpClipRect;
 
         // We use the clip rect as provided by the tranformation for non-fullscreen windows to
         // avoid premature clipping with the system decor rect.
@@ -1190,7 +1231,8 @@
         // so we need to translate to match the actual surface coordinates.
         clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
 
-        adjustCropToStackBounds(w, clipRect, isFreeformResizing);
+        finalClipRect.setEmpty();
+        adjustCropToStackBounds(w, clipRect, finalClipRect, isFreeformResizing);
         if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Clip rect after stack adjustment=" + clipRect);
 
         w.transformFromScreenToSurfaceSpace(clipRect);
@@ -1199,35 +1241,39 @@
         if (w.hasJustMovedInStack() && mLastClipRect.isEmpty() && !clipRect.isEmpty()) {
             clipRect.setEmpty();
         }
-
-        return clipRect;
     }
 
-    void updateSurfaceWindowCrop(Rect clipRect, boolean recoveringMemory) {
+    void updateSurfaceWindowCrop(Rect clipRect, Rect finalClipRect, boolean recoveringMemory) {
         if (!clipRect.equals(mLastClipRect)) {
             mLastClipRect.set(clipRect);
             mSurfaceController.setCropInTransaction(clipRect, recoveringMemory);
         }
+        if (!finalClipRect.equals(mLastFinalClipRect)) {
+            mLastFinalClipRect.set(finalClipRect);
+            mSurfaceController.setFinalCropInTransaction(finalClipRect);
+        }
     }
 
-    private void adjustCropToStackBounds(WindowState w, Rect clipRect, boolean isFreeformResizing) {
+    private int resolveStackClip() {
+
+        // App animation overrides window animation stack clip mode.
+        if (mAppAnimator != null && mAppAnimator.animation != null) {
+            return mAppAnimator.getStackClip();
+        } else {
+            return mStackClip;
+        }
+    }
+    private void adjustCropToStackBounds(WindowState w, Rect clipRect, Rect finalClipRect,
+            boolean isFreeformResizing) {
         final Task task = w.getTask();
         if (task == null || !task.cropWindowsToStackBounds()) {
             return;
         }
 
-        // We don't apply the stack bounds crop if:
-        // 1. The window is currently animating in freeform mode, otherwise the animating window
-        // will be suddenly (docked) or for whole animation (freeform) cut off.
-        // 2. The window that is being replaced during animation, because it was living in a
-        // different stack. If we suddenly crop it to the new stack bounds, it might get cut off.
-        // We don't want it to happen, so we let it ignore the stack bounds until it gets removed.
-        // The window that will replace it will abide them.
-        // TODO: identify animations where we don't want to apply docked stack crop to the docked
-        //       task. For example, if the app is going from freeform to docked mode, we may not
-        //       want to apply the crop during the animation, since it will make the app appear
-        //       cropped prematurely.
-        if (isAnimating() && (w.mWillReplaceWindow || w.inFreeformWorkspace())) {
+        final int stackClip = resolveStackClip();
+
+        // It's animating and we don't want to clip it to stack bounds during animation - abort.
+        if (isAnimating() && stackClip == STACK_CLIP_NONE) {
             return;
         }
 
@@ -1246,16 +1292,24 @@
         final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
                 w.mFrame.top + mWin.mYOffset - w.getAttrs().surfaceInsets.top;
 
+        // If we are animating, we either apply the clip before applying all the animation
+        // transformation or after all the transformation.
+        final boolean useFinalClipRect = isAnimating() && stackClip == STACK_CLIP_AFTER_ANIM;
+
         // We need to do some acrobatics with surface position, because their clip region is
         // relative to the inside of the surface, but the stack bounds aren't.
-        clipRect.left = Math.max(0,
-                Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
-        clipRect.top = Math.max(0,
-                Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
-        clipRect.right = Math.max(0,
-                Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
-        clipRect.bottom = Math.max(0,
-                Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
+        if (useFinalClipRect) {
+            finalClipRect.set(mTmpStackBounds);
+        } else {
+            clipRect.left = Math.max(0,
+                    Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
+            clipRect.top = Math.max(0,
+                    Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
+            clipRect.right = Math.max(0,
+                    Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
+            clipRect.bottom = Math.max(0,
+                    Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
+        }
     }
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
@@ -1268,10 +1322,10 @@
         float extraHScale = (float) 1.0;
         float extraVScale = (float) 1.0;
 
-        final Rect crop = calculateSurfaceWindowCrop();
+        calculateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect);
         if (task != null && task.mStack.getForceScaleToCrop()) {
-            extraHScale = crop.width() / (float)mTmpSize.width();
-            extraVScale = crop.height() / (float)mTmpSize.height();
+            extraHScale = mTmpClipRect.width() / (float)mTmpSize.width();
+            extraVScale = mTmpClipRect.height() / (float)mTmpSize.height();
 
             // In the case of ForceScaleToCrop we scale entire tasks together,
             // and so we need to scale our offsets relative to the task bounds
@@ -1285,12 +1339,13 @@
             // Since we are scaled to fit in our previously desired crop, we can now
             // expose the whole window in buffer space, and not risk extending
             // past where the system would have cropped us
-            crop.set(0, 0, mTmpSize.width(), mTmpSize.height());
-            updateSurfaceWindowCrop(crop, recoveringMemory);
+            mTmpClipRect.set(0, 0, mTmpSize.width(), mTmpSize.height());
+            mTmpFinalClipRect.setEmpty();
+            updateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect, recoveringMemory);
         } else {
             mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top,
                     recoveringMemory);
-            updateSurfaceWindowCrop(crop, recoveringMemory);
+            updateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect, recoveringMemory);
         }
 
 
@@ -1447,7 +1502,8 @@
             SurfaceControl.openTransaction();
             mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
                     mWin.mFrame.top + top, false);
-            updateSurfaceWindowCrop(calculateSurfaceWindowCrop(), false);
+            calculateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect);
+            updateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect, false);
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error positioning surface of " + mWin
                     + " pos=(" + left + "," + top + ")", e);
@@ -1710,6 +1766,7 @@
                     pw.print(" mLocalAnimating="); pw.print(mLocalAnimating);
                     pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
                     pw.print(" mAnimation="); pw.println(mAnimation);
+                    pw.print(" mStackClip="); pw.println(mStackClip);
         }
         if (mHasTransformation || mHasLocalTransformation) {
             pw.print(prefix); pw.print("XForm: has=");
@@ -1729,6 +1786,9 @@
             if (mHasClipRect) {
                 pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw);
             }
+            if (!mLastFinalClipRect.isEmpty()) {
+                pw.print(" mLastFinalClipRect="); mLastFinalClipRect.printShortString(pw);
+            }
             pw.println();
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index be3ad3b..8799c61 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -189,6 +189,16 @@
         }
     }
 
+    void setFinalCropInTransaction(Rect clipRect) {
+        if (SHOW_TRANSACTIONS) logSurface(
+                "FINAL CROP " + clipRect.toShortString(), null);
+        try {
+            mSurfaceControl.setFinalCrop(clipRect);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error disconnecting surface in: " + this, e);
+        }
+    }
+
     void setLayer(int layer) {
         if (mSurfaceControl != null) {
             SurfaceControl.openTransaction();
@@ -460,6 +470,7 @@
         private final PointF mPosition = new PointF();
         private final Point mSize = new Point();
         private final Rect mWindowCrop = new Rect();
+        private final Rect mFinalCrop = new Rect();
         private boolean mShown = false;
         private int mLayerStack;
         private boolean mIsOpaque;
@@ -545,6 +556,19 @@
         }
 
         @Override
+        public void setFinalCrop(Rect crop) {
+            if (crop != null) {
+                if (!crop.equals(mFinalCrop)) {
+                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setFinalCrop("
+                            + crop.toShortString() + "): OLD:" + this + ". Called by "
+                            + Debug.getCallers(3));
+                    mFinalCrop.set(crop);
+                }
+            }
+            super.setFinalCrop(crop);
+        }
+
+        @Override
         public void setLayerStack(int layerStack) {
             if (layerStack != mLayerStack) {
                 if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:"
@@ -655,6 +679,7 @@
                             pw.print(" mSize="); pw.print(s.mSize.x); pw.print("x");
                             pw.println(s.mSize.y);
                     pw.print("    mCrop="); s.mWindowCrop.printShortString(pw); pw.println();
+                    pw.print("    mFinalCrop="); s.mFinalCrop.printShortString(pw); pw.println();
                     pw.print("    Transform: ("); pw.print(s.mDsdx); pw.print(", ");
                             pw.print(s.mDtdx); pw.print(", "); pw.print(s.mDsdy);
                             pw.print(", "); pw.print(s.mDtdy); pw.println(")");
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 5ad771f..909c5f2 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -9,6 +9,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
@@ -689,6 +690,9 @@
                             && !w.isDragResizing() && !adjustedForMinimizedDockedStack
                             && (task == null || !w.getTask().mStack.getFreezeMovementAnimations())) {
                         winAnimator.setMoveAnimation(left, top);
+                    } else if (w.mAttrs.type  == TYPE_DOCK_DIVIDER &&
+                            displayContent.getDockedDividerController().isAdjustingForIme()) {
+                        winAnimator.setMoveAnimation(left, top);
                     }
 
                     //TODO (multidisplay): Accessibility supported only for the default display.
@@ -805,6 +809,8 @@
                 mService.updateResizingWindows(w);
             }
 
+            displayContent.getDockedDividerController().setAdjustingForIme(false);
+
             mService.mDisplayManagerInternal.setDisplayProperties(displayId,
                     mDisplayHasContent,
                     mPreferredRefreshRate,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c362c9c..7da0a9a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7513,7 +7513,9 @@
     /**
      * Sets which packages may enter lock task mode.
      *
-     * This function can only be called by the device owner.
+     * <p>This function can only be called by the device owner or alternatively by the profile owner
+     * in case the user is affiliated.
+     *
      * @param packages The list of packages allowed to enter lock task mode.
      */
     @Override
@@ -7521,10 +7523,17 @@
             throws SecurityException {
         Preconditions.checkNotNull(who, "ComponentName is null");
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
-            int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
-            setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+            ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(
+                who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid());
+            ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(
+                who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid());
+            if (deviceOwner != null || (profileOwner != null && isAffiliatedUser())) {
+                int userHandle = mInjector.userHandleGetCallingUserId();
+                setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+            } else {
+                throw new SecurityException("Admin " + who +
+                    " is neither the device owner or affiliated user's profile owner.");
+            }
         }
     }
 
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
new file mode 100644
index 0000000..f169411
--- /dev/null
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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.net.apf;
+
+/**
+ * APF program support capabilities.
+ *
+ * @hide
+ */
+public class ApfCapabilities {
+    /**
+     * Version of APF instruction set supported for packet filtering. 0 indicates no support for
+     * packet filtering using APF programs.
+     */
+    public final int apfVersionSupported;
+
+    /**
+     * Maximum size of APF program allowed.
+     */
+    public final int maximumApfProgramSize;
+
+    /**
+     * Format of packets passed to APF filter. Should be one of ARPHRD_*
+     */
+    public final int apfPacketFormat;
+
+    ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+        this.apfVersionSupported = apfVersionSupported;
+        this.maximumApfProgramSize = maximumApfProgramSize;
+        this.apfPacketFormat = apfPacketFormat;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
similarity index 65%
rename from services/core/java/com/android/server/connectivity/ApfFilter.java
rename to services/net/java/android/net/apf/ApfFilter.java
index 824db65..ebbf991 100644
--- a/services/core/java/com/android/server/connectivity/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.connectivity;
+package android.net.apf;
 
 import static android.system.OsConstants.*;
 
@@ -22,15 +22,16 @@
 import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IpManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.PacketSocketAddress;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.ConnectivityService;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -52,6 +53,17 @@
  * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
  * filter out redundant duplicate ones.
  *
+ * Threading model:
+ * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to
+ * know what RAs to filter for, thus generating APF programs is dependent on mRas.
+ * mRas can be accessed by multiple threads:
+ * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs.
+ * - callers of:
+ *    - setMulticastFilter(), which can cause an APF program to be generated.
+ *    - dump(), which dumps mRas among other things.
+ *    - shutdown(), which clears mRas.
+ * So access to mRas is synchronized.
+ *
  * @hide
  */
 public class ApfFilter {
@@ -93,23 +105,62 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
-    private final ConnectivityService mConnectivityService;
-    private final NetworkAgentInfo mNai;
-    private ReceiveThread mReceiveThread;
-    private String mIfaceName;
-    private long mUniqueCounter;
+    private static final int ETH_HEADER_LEN = 14;
+    private static final int ETH_ETHERTYPE_OFFSET = 12;
+    private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+    // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
+    private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
+    // Endianness is not an issue for this constant because the APF interpreter always operates in
+    // network byte order.
+    private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
+    private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+    private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
 
-    private ApfFilter(ConnectivityService connectivityService, NetworkAgentInfo nai) {
-        mConnectivityService = connectivityService;
-        mNai = nai;
+    private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+    private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
+    private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+    private static final int IPV6_HEADER_LEN = 40;
+    // The IPv6 all nodes address ff02::1
+    private static final byte[] IPV6_ALL_NODES_ADDRESS =
+            new byte[]{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+    private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+
+    // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
+    private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
+    private static final int UDP_HEADER_LEN = 8;
+
+    private static final int DHCP_CLIENT_PORT = 68;
+    // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
+    private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
+
+    private final ApfCapabilities mApfCapabilities;
+    private final IpManager.Callback mIpManagerCallback;
+    private final NetworkInterface mNetworkInterface;
+    private byte[] mHardwareAddress;
+    private ReceiveThread mReceiveThread;
+    @GuardedBy("this")
+    private long mUniqueCounter;
+    @GuardedBy("this")
+    private boolean mMulticastFilter;
+
+    private ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
+            IpManager.Callback ipManagerCallback) {
+        mApfCapabilities = apfCapabilities;
+        mIpManagerCallback = ipManagerCallback;
+        mNetworkInterface = networkInterface;
+
         maybeStartFilter();
     }
 
     private void log(String s) {
-        Log.d(TAG, "(" + mNai.network.netId + "): " + s);
+        Log.d(TAG, "(" + mNetworkInterface.getName() + "): " + s);
     }
 
-    private long getUniqueNumber() {
+    @GuardedBy("this")
+    private long getUniqueNumberLocked() {
         return mUniqueCounter++;
     }
 
@@ -118,37 +169,26 @@
      * filters to ignore useless RAs.
      */
     private void maybeStartFilter() {
-        mIfaceName = mNai.linkProperties.getInterfaceName();
-        if (mIfaceName == null) return;
         FileDescriptor socket;
         try {
+            mHardwareAddress = mNetworkInterface.getHardwareAddress();
+            synchronized(this) {
+                // Install basic filters
+                installNewProgramLocked();
+            }
             socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
             PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IPV6,
-                    NetworkInterface.getByName(mIfaceName).getIndex());
+                    mNetworkInterface.getIndex());
             Os.bind(socket, addr);
-            NetworkUtils.attachRaFilter(socket, mNai.networkMisc.apfPacketFormat);
+            NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
         } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error filtering raw socket", e);
+            Log.e(TAG, "Error starting filter", e);
             return;
         }
         mReceiveThread = new ReceiveThread(socket);
         mReceiveThread.start();
     }
 
-    /**
-     * mNai's LinkProperties may have changed, take appropriate action.
-     */
-    public void updateFilter() {
-        // If we're not listening for RAs, try starting.
-        if (mReceiveThread == null) {
-            maybeStartFilter();
-        // If interface name has changed, restart.
-        } else if (!mIfaceName.equals(mNai.linkProperties.getInterfaceName())) {
-            shutdown();
-            maybeStartFilter();
-        }
-    }
-
     // Returns seconds since Unix Epoch.
     private static long curTime() {
         return System.currentTimeMillis() / 1000L;
@@ -156,12 +196,6 @@
 
     // A class to hold information about an RA.
     private class Ra {
-        private static final int ETH_HEADER_LEN = 14;
-
-        private static final int IPV6_HEADER_LEN = 40;
-        private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
-        private static final int IPV6_DST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
-
         // From RFC4861:
         private static final int ICMP6_RA_HEADER_LEN = 16;
         private static final int ICMP6_RA_CHECKSUM_OFFSET =
@@ -250,7 +284,7 @@
                 StringBuffer sb = new StringBuffer();
                 sb.append(String.format("RA %s -> %s %d ",
                         IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
-                        IPv6AddresstoString(IPV6_DST_ADDR_OFFSET),
+                        IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
                         uint16(mPacket.getShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET))));
                 for (int i: mPrefixOptionOffsets) {
                     String prefix = IPv6AddresstoString(i + 16);
@@ -302,7 +336,7 @@
                     ICMP6_RA_ROUTER_LIFETIME_OFFSET,
                     ICMP6_RA_ROUTER_LIFETIME_LEN);
 
-            // Parse ICMP6 options
+            // Parse ICMPv6 options
             mPrefixOptionOffsets = new ArrayList<>();
             mPacket.position(ICMP6_RA_OPTION_OFFSET);
             while (mPacket.hasRemaining()) {
@@ -394,13 +428,16 @@
         }
 
         boolean isExpired() {
-            return currentLifetime() < 0;
+            // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
+            // have to calculte the filter lifetime specially as a fraction of 0 is still 0.
+            return currentLifetime() <= 0;
         }
 
         // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
         // Jump to the next filter if packet doesn't match this RA.
-        long generateFilter(ApfGenerator gen) throws IllegalInstructionException {
-            String nextFilterLabel = "Ra" + getUniqueNumber();
+        @GuardedBy("ApfFilter.this")
+        long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+            String nextFilterLabel = "Ra" + getUniqueNumberLocked();
             // Skip if packet is not the right size
             gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
             gen.addJumpIfR0NotEquals(mPacket.limit(), nextFilterLabel);
@@ -447,6 +484,8 @@
 
     // Maximum number of RAs to filter for.
     private static final int MAX_RAS = 10;
+
+    @GuardedBy("this")
     private ArrayList<Ra> mRas = new ArrayList<Ra>();
 
     // There is always some marginal benefit to updating the installed APF program when an RA is
@@ -460,42 +499,167 @@
     private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
 
     // When did we last install a filter program? In seconds since Unix Epoch.
+    @GuardedBy("this")
     private long mLastTimeInstalledProgram;
     // How long should the last installed filter program live for? In seconds.
+    @GuardedBy("this")
     private long mLastInstalledProgramMinLifetime;
 
-    // For debugging only. The length in bytes of the last program.
+    // For debugging only. The last program installed.
+    @GuardedBy("this")
     private byte[] mLastInstalledProgram;
 
-    private void installNewProgram() {
-        if (mRas.size() == 0) return;
+    /**
+     * Generate filter code to process IPv4 packets. Execution of this code ends in either the
+     * DROP_LABEL or PASS_LABEL and does not fall off the end.
+     * Preconditions:
+     *  - Packet being filtered is IPv4
+     *  - R1 is initialized to 0
+     */
+    @GuardedBy("this")
+    private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+        // Here's a basic summary of what the IPv4 filter program does:
+        //
+        // if it's multicast and we're dropping multicast:
+        //   drop
+        // if it's not broadcast:
+        //   pass
+        // if it's not DHCP destined to our MAC:
+        //   drop
+        // pass
+
+        if (mMulticastFilter) {
+            // Check for multicast destination address range
+            gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
+            gen.addAnd(0xf0);
+            gen.addJumpIfR0Equals(0xe0, gen.DROP_LABEL);
+        }
+
+        // Drop all broadcasts besides DHCP addressed to us
+        // If not a broadcast packet, pass
+        // NOTE: Relies on R1 being initialized to 0 which is the offset of the ethernet
+        //       destination MAC address
+        gen.addJumpIfBytesNotEqual(Register.R1, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL);
+        // If not UDP, drop
+        gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+        gen.addJumpIfR0NotEquals(IPPROTO_UDP, gen.DROP_LABEL);
+        // If fragment, drop. This matches the BPF filter installed by the DHCP client.
+        gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+        gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, gen.DROP_LABEL);
+        // If not to DHCP client port, drop
+        gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+        gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
+        gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, gen.DROP_LABEL);
+        // If not DHCP to our MAC address, drop
+        gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
+        // NOTE: Relies on R1 containing IPv4 header offset.
+        gen.addAddR1();
+        gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, gen.DROP_LABEL);
+
+        // Otherwise, pass
+        gen.addJump(gen.PASS_LABEL);
+    }
+
+
+    /**
+     * Generate filter code to process IPv6 packets. Execution of this code ends in either the
+     * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
+     * Preconditions:
+     *  - Packet being filtered is IPv6
+     *  - R1 is initialized to 0
+     */
+    @GuardedBy("this")
+    private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+        // Here's a basic summary of what the IPv6 filter program does:
+        //
+        // if it's not ICMPv6:
+        //   pass
+        // if it's ICMPv6 NA to ff02::1:
+        //   drop
+
+        // If not ICMPv6, pass
+        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+        // TODO: Drop multicast if the multicast filter is enabled.
+        gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, gen.PASS_LABEL);
+        // Add unsolicited multicast neighbor announcements filter
+        String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
+        // If not neighbor announcements, skip unsolicited multicast NA filter
+        gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+        gen.addJumpIfR0NotEquals(ICMP6_NEIGHBOR_ANNOUNCEMENT, skipUnsolicitedMulticastNALabel);
+        // If to ff02::1, drop
+        // TODO: Drop only if they don't contain the address of on-link neighbours.
+        gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
+        gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
+                skipUnsolicitedMulticastNALabel);
+        gen.addJump(gen.DROP_LABEL);
+        gen.defineLabel(skipUnsolicitedMulticastNALabel);
+    }
+
+    /**
+     * Begin generating an APF program to:
+     * <ul>
+     * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
+     * <li>Drop IPv4 multicast packets, if mMulticastFilter,
+     * <li>Pass all other IPv4 packets,
+     * <li>Pass all non-ICMPv6 IPv6 packets,
+     * <li>Pass all non-IPv4 and non-IPv6 packets,
+     * <li>Drop IPv6 ICMPv6 NAs to ff02::1.
+     * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
+     *     insertion of RA filters here, or if there aren't any, just passes the packets.
+     * </ul>
+     */
+    @GuardedBy("this")
+    private ApfGenerator beginProgramLocked() throws IllegalInstructionException {
+        ApfGenerator gen = new ApfGenerator();
+        // This is guaranteed to return true because of the check in maybeCreate.
+        gen.setApfVersion(mApfCapabilities.apfVersionSupported);
+
+        // Here's a basic summary of what the initial program does:
+        //
+        // if it's IPv4:
+        //   insert IPv4 filter to drop or pass these appropriately
+        // if it's not IPv6:
+        //   pass
+        // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
+
+        // Add IPv4 filters:
+        String skipIPv4FiltersLabel = "skipIPv4Filters";
+        // If not IPv4, skip IPv4 filters
+        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
+        // NOTE: Relies on R1 being initialized to 0.
+        generateIPv4FilterLocked(gen);
+        gen.defineLabel(skipIPv4FiltersLabel);
+
+        // Add IPv6 filters:
+        // If not IPv6, pass
+        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
+        // execute the IPv4 filter, since that filter does not fall through, but either drops or
+        // passes.
+        gen.addJumpIfR0NotEquals(ETH_P_IPV6, gen.PASS_LABEL);
+        generateIPv6FilterLocked(gen);
+        return gen;
+    }
+
+    @GuardedBy("this")
+    private void installNewProgramLocked() {
+        purgeExpiredRasLocked();
         final byte[] program;
         long programMinLifetime = Long.MAX_VALUE;
         try {
-            ApfGenerator gen = new ApfGenerator();
-            // This is guaranteed to return true because of the check in maybeInstall.
-            gen.setApfVersion(mNai.networkMisc.apfVersionSupported);
             // Step 1: Determine how many RA filters we can fit in the program.
-            int ras = 0;
+            ApfGenerator gen = beginProgramLocked();
+            ArrayList<Ra> rasToFilter = new ArrayList<Ra>();
             for (Ra ra : mRas) {
-                if (ra.isExpired()) continue;
-                ra.generateFilter(gen);
-                if (gen.programLengthOverEstimate() > mNai.networkMisc.maximumApfProgramSize) {
-                    // We went too far.  Use prior number of RAs in "ras".
-                    break;
-                } else {
-                    // Yay! this RA filter fits, increment "ras".
-                    ras++;
-                }
+                ra.generateFilterLocked(gen);
+                // Stop if we get too big.
+                if (gen.programLengthOverEstimate() > mApfCapabilities.maximumApfProgramSize) break;
+                rasToFilter.add(ra);
             }
-            // Step 2: Generate RA filters
-            gen = new ApfGenerator();
-            // This is guaranteed to return true because of the check in maybeInstall.
-            gen.setApfVersion(mNai.networkMisc.apfVersionSupported);
-            for (Ra ra : mRas) {
-                if (ras-- == 0) break;
-                if (ra.isExpired()) continue;
-                programMinLifetime = Math.min(programMinLifetime, ra.generateFilter(gen));
+            // Step 2: Actually generate the program
+            gen = beginProgramLocked();
+            for (Ra ra : rasToFilter) {
+                programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
             }
             // Execution will reach the end of the program if no filters match, which will pass the
             // packet to the AP.
@@ -509,19 +673,18 @@
         mLastInstalledProgram = program;
         if (VDBG) {
             hexDump("Installing filter: ", program, program.length);
-        } else {
-            Log.d(TAG, "Installing filter length=" + program.length);
         }
-        mConnectivityService.pushApfProgramToNetwork(mNai, program);
+        mIpManagerCallback.installPacketFilter(program);
     }
 
     // Install a new filter program if the last installed one will die soon.
-    private void maybeInstallNewProgram() {
+    @GuardedBy("this")
+    private void maybeInstallNewProgramLocked() {
         if (mRas.size() == 0) return;
         // If the current program doesn't expire for a while, don't bother updating.
         long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
         if (expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING) {
-            installNewProgram();
+            installNewProgramLocked();
         }
     }
 
@@ -529,7 +692,19 @@
         log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
     }
 
-    private void processRa(byte[] packet, int length) {
+    @GuardedBy("this")
+    private void purgeExpiredRasLocked() {
+        for (int i = 0; i < mRas.size();) {
+            if (mRas.get(i).isExpired()) {
+                log("Expiring " + mRas.get(i));
+                mRas.remove(i);
+            } else {
+                i++;
+            }
+        }
+    }
+
+    private synchronized void processRa(byte[] packet, int length) {
         if (VDBG) hexDump("Read packet = ", packet, length);
 
         // Have we seen this RA before?
@@ -551,66 +726,70 @@
                 // Swap to front of array.
                 mRas.add(0, mRas.remove(i));
 
-                maybeInstallNewProgram();
+                maybeInstallNewProgramLocked();
                 return;
             }
         }
-        // Purge expired RAs.
-        for (int i = 0; i < mRas.size();) {
-            if (mRas.get(i).isExpired()) {
-                log("Expired RA " + mRas.get(i));
-                mRas.remove(i);
-            } else {
-                i++;
-            }
-        }
+        purgeExpiredRasLocked();
         // TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
         if (mRas.size() >= MAX_RAS) return;
+        final Ra ra;
         try {
-            Ra ra = new Ra(packet, length);
-            log("Adding " + ra);
-            mRas.add(ra);
+            ra = new Ra(packet, length);
         } catch (Exception e) {
             Log.e(TAG, "Error parsing RA: " + e);
             return;
         }
-        installNewProgram();
+        // Ignore 0 lifetime RAs.
+        if (ra.isExpired()) return;
+        log("Adding " + ra);
+        mRas.add(ra);
+        installNewProgramLocked();
     }
 
     /**
-     * Install an {@link ApfFilter} on {@code nai} if {@code nai} supports packet
+     * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
      * filtering using APF programs.
      */
-    public static void maybeInstall(ConnectivityService connectivityService, NetworkAgentInfo nai) {
-        if (nai.networkMisc == null) return;
-        if (nai.networkMisc.apfVersionSupported == 0) return;
-        if (nai.networkMisc.maximumApfProgramSize < 512) {
-            Log.e(TAG, "Unacceptably small APF limit: " + nai.networkMisc.maximumApfProgramSize);
-            return;
+    public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
+            NetworkInterface networkInterface, IpManager.Callback ipManagerCallback) {
+        if (apfCapabilities == null || networkInterface == null) return null;
+        if (apfCapabilities.apfVersionSupported == 0) return null;
+        if (apfCapabilities.maximumApfProgramSize < 512) {
+            Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize);
+            return null;
         }
         // For now only support generating programs for Ethernet frames. If this restriction is
         // lifted:
         //   1. the program generator will need its offsets adjusted.
         //   2. the packet filter attached to our packet socket will need its offset adjusted.
-        if (nai.networkMisc.apfPacketFormat != ARPHRD_ETHER) return;
-        if (!new ApfGenerator().setApfVersion(nai.networkMisc.apfVersionSupported)) {
-            Log.e(TAG, "Unsupported APF version: " + nai.networkMisc.apfVersionSupported);
-            return;
+        if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null;
+        if (!new ApfGenerator().setApfVersion(apfCapabilities.apfVersionSupported)) {
+            Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
+            return null;
         }
-        nai.apfFilter = new ApfFilter(connectivityService, nai);
+        return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback);
     }
 
-    public void shutdown() {
+    public synchronized void shutdown() {
         if (mReceiveThread != null) {
             log("shutting down");
             mReceiveThread.halt();  // Also closes socket.
             mReceiveThread = null;
         }
+        mRas.clear();
     }
 
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("APF version: " + mNai.networkMisc.apfVersionSupported);
-        pw.println("Max program size: " + mNai.networkMisc.maximumApfProgramSize);
+    public synchronized void setMulticastFilter(boolean enabled) {
+        if (mMulticastFilter != enabled) {
+            mMulticastFilter = enabled;
+            installNewProgramLocked();
+        }
+    }
+
+    public synchronized void dump(IndentingPrintWriter pw) {
+        pw.println("APF version: " + mApfCapabilities.apfVersionSupported);
+        pw.println("Max program size: " + mApfCapabilities.maximumApfProgramSize);
         pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
         if (mLastTimeInstalledProgram == 0) {
             pw.println("No program installed.");
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index e2562cd..406dd56 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -30,6 +30,8 @@
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
+import android.net.metrics.IpConnectivityEvent;
+import android.net.metrics.DhcpClientEvent;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Message;
@@ -136,7 +138,7 @@
             MessageUtils.findMessageNames(sMessageClasses);
 
     // DHCP parameters that we request.
-    private static final byte[] REQUESTED_PARAMS = new byte[] {
+    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
         DHCP_SUBNET_MASK,
         DHCP_ROUTER,
         DHCP_DNS_SERVER,
@@ -146,6 +148,7 @@
         DHCP_LEASE_TIME,
         DHCP_RENEWAL_TIME,
         DHCP_REBINDING_TIME,
+        DHCP_VENDOR_INFO,
     };
 
     // DHCP flag that means "yes, we support unicast."
@@ -355,11 +358,15 @@
                     if (!stopped) {
                         Log.e(TAG, "Read error", e);
                     }
+                    DhcpClientEvent.logEvent(IpConnectivityEvent.IPCE_DHCP_RECV_ERROR,
+                            mIfaceName, e.getMessage());
                 } catch (DhcpPacket.ParseException e) {
                     Log.e(TAG, "Can't parse packet: " + e.getMessage());
                     if (PACKET_DBG) {
                         Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
                     }
+                    DhcpClientEvent.logEvent(IpConnectivityEvent.IPCE_DHCP_PARSE_ERROR, mIfaceName,
+                            e.getMessage());
                 }
             }
             if (DBG) Log.d(TAG, "Receive thread stopped");
@@ -456,7 +463,9 @@
 
     abstract class LoggingState extends State {
         public void enter() {
-            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
+            String msg = "Entering state " + getName();
+            if (STATE_DBG) Log.d(TAG, msg);
+            DhcpClientEvent.logEvent(IpConnectivityEvent.IPCE_DHCP_STATE_CHANGE, mIfaceName, msg);
         }
 
         private String messageName(int what) {
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index c7c5015..66c7909 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -19,6 +19,8 @@
 import com.android.internal.util.MessageUtils;
 
 import android.content.Context;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
 import android.net.DhcpResults;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
@@ -38,10 +40,12 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.net.NetlinkTracker;
 
+import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -108,6 +112,9 @@
 
         // Called when the IpManager state machine terminates.
         public void onQuit() {}
+
+        // Install an APF program to filter incoming packets.
+        public void installPacketFilter(byte[] filter) {}
     }
 
     public static class WaitForProvisioningCallback extends Callback {
@@ -179,6 +186,11 @@
                 return this;
             }
 
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                mConfig.mApfCapabilities = apfCapabilities;
+                return this;
+            }
+
             public ProvisioningConfiguration build() {
                 return new ProvisioningConfiguration(mConfig);
             }
@@ -187,6 +199,7 @@
         /* package */ boolean mUsingIpReachabilityMonitor = true;
         /* package */ boolean mRequestedPreDhcpAction;
         /* package */ StaticIpConfiguration mStaticIpConfig;
+        /* package */ ApfCapabilities mApfCapabilities;
 
         public ProvisioningConfiguration() {}
 
@@ -194,6 +207,7 @@
             mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
             mRequestedPreDhcpAction = other.mRequestedPreDhcpAction;
             mStaticIpConfig = other.mStaticIpConfig;
+            mApfCapabilities = other.mApfCapabilities;
         }
     }
 
@@ -229,7 +243,7 @@
     private final INetworkManagementService mNwService;
     private final NetlinkTracker mNetlinkTracker;
 
-    private int mInterfaceIndex;
+    private NetworkInterface mNetworkInterface;
 
     /**
      * Non-final member variables accessed only from within our StateMachine.
@@ -240,6 +254,7 @@
     private DhcpResults mDhcpResults;
     private String mTcpBufferSizes;
     private ProxyInfo mHttpProxy;
+    private ApfFilter mApfFilter;
 
     /**
      * Member variables accessed both from within the StateMachine thread
@@ -325,7 +340,7 @@
     }
 
     public void startProvisioning(ProvisioningConfiguration req) {
-        getInterfaceIndex();
+        getNetworkInterface();
         sendMessage(CMD_START, new ProvisioningConfiguration(req));
     }
 
@@ -378,6 +393,19 @@
         }
     }
 
+    public void dumpApf(PrintWriter writer) {
+        writer.println("--------------------------------------------------------------------");
+        writer.println("APF dump:");
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        ApfFilter apfFilter = mApfFilter;
+        if (apfFilter != null) {
+            apfFilter.dump(new IndentingPrintWriter(writer, "  "));
+        } else {
+            writer.println("No apf support");
+        }
+        writer.println("--------------------------------------------------------------------");
+    }
+
 
     /**
      * Internals.
@@ -392,7 +420,7 @@
     protected String getLogRecString(Message msg) {
         final String logLine = String.format(
                 "iface{%s/%d} arg1{%d} arg2{%d} obj{%s}",
-                mInterfaceName, mInterfaceIndex,
+                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
                 msg.arg1, msg.arg2, Objects.toString(msg.obj));
         if (VDBG) {
             Log.d(mTag, getWhatToString(msg.what) + " " + logLine);
@@ -400,12 +428,12 @@
         return logLine;
     }
 
-    private void getInterfaceIndex() {
+    private void getNetworkInterface() {
         try {
-            mInterfaceIndex = NetworkInterface.getByName(mInterfaceName).getIndex();
+            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
         } catch (SocketException | NullPointerException e) {
             // TODO: throw new IllegalStateException.
-            Log.e(mTag, "ALERT: Failed to get interface index: ", e);
+            Log.e(mTag, "ALERT: Failed to get interface object: ", e);
         }
     }
 
@@ -732,6 +760,8 @@
     class StartedState extends State {
         @Override
         public void enter() {
+            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
+                    mCallback);
             // Set privacy extensions.
             try {
                 mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
@@ -788,6 +818,11 @@
                 mDhcpClient.doQuit();
             }
 
+            if (mApfFilter != null) {
+                mApfFilter.shutdown();
+                mApfFilter = null;
+            }
+
             resetLinkProperties();
         }
 
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 5b4fd50..af3175a 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -24,6 +24,8 @@
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.metrics.IpReachabilityMonitorMessageEvent;
+import android.net.metrics.IpReachabilityMonitorProbeEvent;
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkErrorMessage;
 import android.net.netlink.NetlinkMessage;
@@ -162,7 +164,7 @@
     private boolean mRunning;
 
     /**
-     * Make the kernel to perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
+     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
      * for the given IP address on the specified interface index.
      *
      * @return true, if the request was successfully passed to the kernel; false otherwise.
@@ -203,7 +205,8 @@
         } catch (ErrnoException | InterruptedIOException | SocketException e) {
             Log.d(TAG, "Error " + msgSnippet, e);
         }
-
+        IpReachabilityMonitorProbeEvent.logEvent("ifindex-" + ifIndex, ip.getHostAddress(),
+                returnValue);
         return returnValue;
     }
 
@@ -400,8 +403,7 @@
         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
     }
 
-
-    // TODO: simply the number of objects by making this extend Thread.
+    // TODO: simplify the number of objects by making this extend Thread.
     private final class NetlinkSocketObserver implements Runnable {
         private NetlinkSocket mSocket;
 
@@ -519,6 +521,8 @@
 
             final short msgType = neighMsg.getHeader().nlmsg_type;
             final short nudState = ndMsg.ndm_state;
+            IpReachabilityMonitorMessageEvent.logEvent(maybeGetInterfaceName(mInterfaceIndex),
+                    destination.getHostAddress(), msgType, nudState);
             final String eventMsg = "NeighborEvent{"
                     + "elapsedMs=" + whenMs + ", "
                     + destination.getHostAddress() + ", "
@@ -549,4 +553,11 @@
             }
         }
     }
+
+    private String maybeGetInterfaceName(int index) {
+        if (index == mInterfaceIndex) {
+            return mInterfaceName;
+        }
+        return "ifindex-" + index;
+    }
 }
diff --git a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java b/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java
new file mode 100644
index 0000000..163f7e40
--- /dev/null
+++ b/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class CaptivePortalCheckResultEvent extends IpConnectivityEvent implements Parcelable {
+    public static final String TAG = "CaptivePortalCheckResultEvent";
+
+    private int mNetId;
+    private int mResult;
+
+    public CaptivePortalCheckResultEvent(int netId, int result) {
+        mNetId = netId;
+        mResult = result;
+    }
+
+    public CaptivePortalCheckResultEvent(Parcel in) {
+        mNetId = in.readInt();
+        mResult = in.readInt();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mNetId);
+        out.writeInt(mResult);
+    }
+
+    public static final Parcelable.Creator<CaptivePortalCheckResultEvent> CREATOR
+        = new Parcelable.Creator<CaptivePortalCheckResultEvent>() {
+            public CaptivePortalCheckResultEvent createFromParcel(Parcel in) {
+                return new CaptivePortalCheckResultEvent(in);
+            }
+
+            public CaptivePortalCheckResultEvent[] newArray(int size) {
+                return new CaptivePortalCheckResultEvent[size];
+            }
+        };
+
+    public static void logEvent(int netId, int result) {
+        IpConnectivityEvent.logEvent(IpConnectivityEvent.IPCE_NETMON_CHECK_RESULT,
+                new CaptivePortalCheckResultEvent(netId, result));
+    }
+};
diff --git a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java b/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java
new file mode 100644
index 0000000..d0cc120
--- /dev/null
+++ b/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class CaptivePortalStateChangeEvent extends IpConnectivityEvent implements Parcelable {
+    public static final String TAG = "CaptivePortalStateChangeEvent";
+
+    public static final int NETWORK_MONITOR_CONNECTED = 0;
+    public static final int NETWORK_MONITOR_DISCONNECTED = 1;
+    public static final int NETWORK_MONITOR_VALIDATED = 2;
+    private int mState;
+
+    public CaptivePortalStateChangeEvent(int state) {
+        mState = state;
+    }
+
+    public CaptivePortalStateChangeEvent(Parcel in) {
+        mState = in.readInt();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mState);
+    }
+
+    public static final Parcelable.Creator<CaptivePortalStateChangeEvent> CREATOR
+        = new Parcelable.Creator<CaptivePortalStateChangeEvent>() {
+        public CaptivePortalStateChangeEvent createFromParcel(Parcel in) {
+            return new CaptivePortalStateChangeEvent(in);
+        }
+
+        public CaptivePortalStateChangeEvent[] newArray(int size) {
+            return new CaptivePortalStateChangeEvent[size];
+        }
+    };
+
+    public static void logEvent(int state) {
+        IpConnectivityEvent.logEvent(IpConnectivityEvent.IPCE_NETMON_STATE_CHANGE,
+                new CaptivePortalStateChangeEvent(state));
+    }
+};
diff --git a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java b/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java
new file mode 100644
index 0000000..92b376c
--- /dev/null
+++ b/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ConnectivityServiceChangeEvent extends IpConnectivityEvent implements Parcelable {
+    public static final String TAG = "ConnectivityServiceChangeEvent";
+
+    private int mNetId;
+
+    public ConnectivityServiceChangeEvent(int netId) {
+        mNetId = netId;
+    }
+
+    public ConnectivityServiceChangeEvent(Parcel in) {
+        mNetId = in.readInt();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mNetId);
+    }
+
+    public static final Parcelable.Creator<ConnectivityServiceChangeEvent> CREATOR
+        = new Parcelable.Creator<ConnectivityServiceChangeEvent>() {
+        public ConnectivityServiceChangeEvent createFromParcel(Parcel in) {
+            return new ConnectivityServiceChangeEvent(in);
+        }
+
+        public ConnectivityServiceChangeEvent[] newArray(int size) {
+            return new ConnectivityServiceChangeEvent[size];
+        }
+    };
+
+    public static void logEvent(int netId) {
+        IpConnectivityEvent.logEvent(IpConnectivityEvent.IPCE_CONSRV_DEFAULT_NET_CHANGE,
+                new ConnectivityServiceChangeEvent(netId));
+    }
+};
diff --git a/services/net/java/android/net/metrics/DhcpClientEvent.java b/services/net/java/android/net/metrics/DhcpClientEvent.java
new file mode 100644
index 0000000..2c24034
--- /dev/null
+++ b/services/net/java/android/net/metrics/DhcpClientEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class DhcpClientEvent extends IpConnectivityEvent implements Parcelable {
+    public static final String TAG = "DhcpClientEvent";
+
+    private String mIfName;
+    private String mMsg;
+
+    public DhcpClientEvent(String ifName, String msg) {
+        mIfName = ifName;
+        mMsg = msg;
+    }
+
+    public DhcpClientEvent(Parcel in) {
+        mIfName = in.readString();
+        mMsg = in.readString();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mIfName);
+        out.writeString(mMsg);
+    }
+
+    public static final Parcelable.Creator<DhcpClientEvent> CREATOR
+        = new Parcelable.Creator<DhcpClientEvent>() {
+        public DhcpClientEvent createFromParcel(Parcel in) {
+            return new DhcpClientEvent(in);
+        }
+
+        public DhcpClientEvent[] newArray(int size) {
+            return new DhcpClientEvent[size];
+        }
+    };
+
+    public static void logEvent(int eventType, String ifName, String msg) {
+        IpConnectivityEvent.logEvent(eventType, new DhcpClientEvent(ifName, msg));
+    }
+};
diff --git a/services/net/java/android/net/metrics/IpConnectivityEvent.java b/services/net/java/android/net/metrics/IpConnectivityEvent.java
new file mode 100644
index 0000000..f277bd0
--- /dev/null
+++ b/services/net/java/android/net/metrics/IpConnectivityEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.net.ConnectivityMetricsLogger;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class IpConnectivityEvent implements Parcelable {
+    // IPRM = IpReachabilityMonitor
+    // DHCP = DhcpClient
+    // NETMON = NetworkMonitorEvent
+    // CONSRV = ConnectivityServiceEvent
+    public static final String TAG = "IpConnectivityEvent";
+    public static final int IPCE_IPRM_BASE = 0*1024;
+    public static final int IPCE_DHCP_BASE = 1*1024;
+    public static final int IPCE_NETMON_BASE = 2*1024;
+    public static final int IPCE_CONSRV_BASE = 3*1024;
+
+    public static final int IPCE_IPRM_PROBE_RESULT = IPCE_IPRM_BASE + 0;
+    public static final int IPCE_IPRM_MESSAGE_RECEIVED = IPCE_IPRM_BASE + 1;
+    public static final int IPCE_DHCP_RECV_ERROR = IPCE_DHCP_BASE + 0;
+    public static final int IPCE_DHCP_PARSE_ERROR = IPCE_DHCP_BASE + 1;
+    public static final int IPCE_DHCP_TIMEOUT = IPCE_DHCP_BASE + 2;
+    public static final int IPCE_DHCP_STATE_CHANGE = IPCE_DHCP_BASE + 3;
+    public static final int IPCE_NETMON_STATE_CHANGE = IPCE_NETMON_BASE + 0;
+    public static final int IPCE_NETMON_CHECK_RESULT = IPCE_NETMON_BASE + 1;
+    public static final int IPCE_CONSRV_DEFAULT_NET_CHANGE = IPCE_CONSRV_BASE + 0;
+
+    private static ConnectivityMetricsLogger mMetricsLogger = new ConnectivityMetricsLogger();
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+    }
+
+    public static void logEvent(int tag, IpConnectivityEvent event) {
+        long timestamp = System.currentTimeMillis();
+        mMetricsLogger.logEvent(timestamp, ConnectivityMetricsLogger.COMPONENT_TAG_CONNECTIVITY,
+                tag, event);
+    }
+};
diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java b/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
new file mode 100644
index 0000000..a8c18d6
--- /dev/null
+++ b/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class IpReachabilityMonitorMessageEvent extends IpConnectivityEvent
+    implements Parcelable {
+    public static final String TAG = "IpReachabilityMonitorMessageEvent";
+
+    private String mIfName;
+    private String mDestination;
+    private int mMsgType;
+    private int mNudState;
+
+    public IpReachabilityMonitorMessageEvent(String ifName, String destination, int msgType,
+            int nudState) {
+        mIfName = ifName;
+        mDestination = destination;
+        mMsgType = msgType;
+        mNudState = nudState;
+    }
+
+    public IpReachabilityMonitorMessageEvent(Parcel in) {
+        mIfName = in.readString();
+        mDestination = in.readString();
+        mMsgType = in.readInt();
+        mNudState = in.readInt();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mIfName);
+        out.writeString(mDestination);
+        out.writeInt(mMsgType);
+        out.writeInt(mNudState);
+    }
+
+    public static final Parcelable.Creator<IpReachabilityMonitorMessageEvent> CREATOR
+        = new Parcelable.Creator<IpReachabilityMonitorMessageEvent>() {
+        public IpReachabilityMonitorMessageEvent createFromParcel(Parcel in) {
+            return new IpReachabilityMonitorMessageEvent(in);
+        }
+
+        public IpReachabilityMonitorMessageEvent[] newArray(int size) {
+            return new IpReachabilityMonitorMessageEvent[size];
+        }
+    };
+
+    public static void logEvent(String ifName, String destination, int msgType, int nudState) {
+        IpConnectivityEvent.logEvent(IpConnectivityEvent.IPCE_IPRM_MESSAGE_RECEIVED,
+                new IpReachabilityMonitorMessageEvent(ifName, destination, msgType, nudState));
+    }
+};
diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java b/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
new file mode 100644
index 0000000..172cbf8
--- /dev/null
+++ b/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class IpReachabilityMonitorProbeEvent extends IpConnectivityEvent
+    implements Parcelable {
+    public static final String TAG = "IpReachabilityMonitorProbeEvent";
+
+    private String mIfName;
+    private String mDestination;
+    private boolean mSuccess;
+
+    public IpReachabilityMonitorProbeEvent(String ifName, String destination, boolean success) {
+        mIfName = ifName;
+        mDestination = destination;
+        mSuccess = success;
+    }
+
+    public IpReachabilityMonitorProbeEvent(Parcel in) {
+        mIfName = in.readString();
+        mDestination = in.readString();
+        mSuccess = in.readByte() > 0 ? true : false;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mIfName);
+        out.writeString(mDestination);
+        out.writeByte((byte)(mSuccess ? 1 : 0));
+    }
+
+    public static final Parcelable.Creator<IpReachabilityMonitorProbeEvent> CREATOR
+        = new Parcelable.Creator<IpReachabilityMonitorProbeEvent>() {
+        public IpReachabilityMonitorProbeEvent createFromParcel(Parcel in) {
+            return new IpReachabilityMonitorProbeEvent(in);
+        }
+
+        public IpReachabilityMonitorProbeEvent[] newArray(int size) {
+            return new IpReachabilityMonitorProbeEvent[size];
+        }
+    };
+
+    public static void logEvent(String ifName, String destination, boolean success) {
+        IpConnectivityEvent.logEvent(IpConnectivityEvent.IPCE_IPRM_PROBE_RESULT,
+                new IpReachabilityMonitorProbeEvent(ifName, destination, success));
+    }
+};
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 9b99c67..9c3a852 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -19,6 +19,7 @@
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -813,6 +814,20 @@
         }
 
         @Override
+        public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
+                @NonNull CharSequence appPackageName) {
+            RemotePrintService service = mWeakService.get();
+            if (service != null) {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    service.mSpooler.setStatus(printJobId, status, appPackageName);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
         @SuppressWarnings({"rawtypes", "unchecked"})
         public void onPrintersAdded(ParceledListSlice printers) {
             RemotePrintService service = mWeakService.get();
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index e1d8c6c..be822c8 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -19,6 +19,7 @@
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -301,6 +302,35 @@
     }
 
     /**
+     * Set status of a print job.
+     *
+     * @param printJobId The print job to update
+     * @param status The new status as a string resource
+     * @param appPackageName The app package name the string res belongs to
+     */
+    public final void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
+            @NonNull CharSequence appPackageName) {
+        throwIfCalledOnMainThread();
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            mCanUnbind = false;
+        }
+        try {
+            getRemoteInstanceLazy().setStatusRes(printJobId, status, appPackageName);
+        } catch (RemoteException|TimeoutException re) {
+            Slog.e(LOG_TAG, "Error setting status.", re);
+        } finally {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
+            }
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    /**
      * Handle that a custom icon for a printer was loaded.
      *
      * @param printerId the id of the printer the icon belongs to
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
index c322ab8..876d95b 100644
--- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
@@ -631,18 +631,10 @@
         byte[] hwaddr = {
                 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a
         };
-        byte[] params = new byte[] {
-            DHCP_SUBNET_MASK,
-            DHCP_ROUTER,
-            DHCP_DNS_SERVER,
-            DHCP_DOMAIN_NAME,
-            DHCP_MTU,
-            DHCP_LEASE_TIME,
-        };
 
         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
                 DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr,
-                false /* do unicast */, params);
+                false /* do unicast */, DhcpClient.REQUESTED_PARAMS);
 
         byte[] headers = new byte[] {
             // Ethernet header.
@@ -650,14 +642,14 @@
             (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
             (byte) 0x08, (byte) 0x00,
             // IP header.
-            (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x52,
+            (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56,
             (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00,
-            (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x8c,
+            (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88,
             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
             // UDP header.
             (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
-            (byte) 0x01, (byte) 0x3e, (byte) 0xd8, (byte) 0xa4,
+            (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a,
             // BOOTP.
             (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00,
             (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
@@ -688,13 +680,17 @@
                     'a', 'n', 'd', 'r', 'o', 'i', 'd', '-',
                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e',
             // Requested parameter list.
-            (byte) 0x37, (byte) 0x06,
+            (byte) 0x37, (byte) 0x0a,
                 DHCP_SUBNET_MASK,
                 DHCP_ROUTER,
                 DHCP_DNS_SERVER,
                 DHCP_DOMAIN_NAME,
                 DHCP_MTU,
+                DHCP_BROADCAST_ADDRESS,
                 DHCP_LEASE_TIME,
+                DHCP_RENEWAL_TIME,
+                DHCP_REBINDING_TIME,
+                DHCP_VENDOR_INFO,
             // End options.
             (byte) 0xff,
             // Our packets are always of even length. TODO: find out why and possibly fix it.
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index afb7d93..b4c6e6a 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -250,7 +250,7 @@
          * in its manifest.
          * <p>
          * See {@link Connection#CAPABILITY_CAN_PULL_CALL} and
-         * {@link Connection#CAPABILITY_IS_EXTERNAL_CALL}.
+         * {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
          */
         public static final int CAPABILITY_CAN_PULL_CALL = 0x00800000;
 
@@ -296,13 +296,13 @@
          * Consider, for example, a scenario where a user has two phones with the same phone number.
          * When a user places a call on one device, the telephony stack can represent that call on
          * the other device by adding it to the {@link ConnectionService} with the
-         * {@link Connection#CAPABILITY_IS_EXTERNAL_CALL} capability set.
+         * {@link Connection#PROPERTY_IS_EXTERNAL_CALL} property set.
          * <p>
          * An {@link InCallService} will only see calls with this property if it has the
          * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true}
          * in its manifest.
          * <p>
-         * See {@link Connection#CAPABILITY_IS_EXTERNAL_CALL}.
+         * See {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
          */
         public static final int PROPERTY_IS_EXTERNAL_CALL = 0x00000040;
 
@@ -686,7 +686,7 @@
             sb.append(", caps: ");
             sb.append(capabilitiesToString(mCallCapabilities));
             sb.append(", props: ");
-            sb.append(mCallProperties);
+            sb.append(propertiesToString(mCallProperties));
             sb.append("]");
             return sb.toString();
         }
@@ -813,6 +813,7 @@
     private String mRemainingPostDialSequence;
     private VideoCallImpl mVideoCallImpl;
     private Details mDetails;
+    private Bundle mExtras;
 
     /**
      * Obtains the post-dial sequence remaining to be emitted by this {@code Call}, if any.
@@ -988,6 +989,89 @@
     }
 
     /**
+     * Adds some extras to this {@link Call}.  Existing keys are replaced and new ones are
+     * added.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these
+     * extras.  Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
+     *
+     * @param extras The extras to add.
+     */
+    public final void putExtras(Bundle extras) {
+        if (extras == null) {
+            return;
+        }
+
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putAll(extras);
+        mInCallAdapter.putExtras(mTelecomCallId, extras);
+    }
+
+    /**
+     * Adds a boolean extra to this {@link Call}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, boolean value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putBoolean(key, value);
+        mInCallAdapter.putExtra(mTelecomCallId, key, value);
+    }
+
+    /**
+     * Adds an integer extra to this {@code Connection}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, int value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putInt(key, value);
+        mInCallAdapter.putExtra(mTelecomCallId, key, value);
+    }
+
+    /**
+     * Adds a string extra to this {@code Connection}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, String value) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putString(key, value);
+        mInCallAdapter.putExtra(mTelecomCallId, key, value);
+    }
+
+    /**
+     * Removes extras from this {@code Connection}.
+     *
+     * @param keys The keys of the extras to remove.
+     */
+    public final void removeExtras(List<String> keys) {
+        if (mExtras != null) {
+            for (String key : keys) {
+                mExtras.remove(key);
+            }
+            if (mExtras.size() == 0) {
+                mExtras = null;
+            }
+        }
+        mInCallAdapter.removeExtras(mTelecomCallId, keys);
+    }
+
+    /**
      * Obtains the parent of this {@code Call} in a conference, if any.
      *
      * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 1b70d65..06851ee 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -16,10 +16,12 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.telecom.Connection.VideoProvider;
+import android.util.ArraySet;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -51,10 +53,13 @@
         public void onDestroyed(Conference conference) {}
         public void onConnectionCapabilitiesChanged(
                 Conference conference, int connectionCapabilities) {}
+        public void onConnectionPropertiesChanged(
+                Conference conference, int connectionProperties) {}
         public void onVideoStateChanged(Conference c, int videoState) { }
         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {}
         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
-        public void onExtrasChanged(Conference conference, Bundle extras) {}
+        public void onExtrasChanged(Conference c, Bundle extras) {}
+        public void onExtrasRemoved(Conference c, List<String> keys) {}
     }
 
     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -71,10 +76,12 @@
     private int mState = Connection.STATE_NEW;
     private DisconnectCause mDisconnectCause;
     private int mConnectionCapabilities;
+    private int mConnectionProperties;
     private String mDisconnectMessage;
     private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED;
     private StatusHints mStatusHints;
     private Bundle mExtras;
+    private Set<String> mPreviousExtraKeys;
 
     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
         @Override
@@ -152,6 +159,16 @@
     }
 
     /**
+     * Returns the properties of the conference. See {@code PROPERTY_*} constants in class
+     * {@link Connection} for valid values.
+     *
+     * @return A bitmask of the properties of the conference call.
+     */
+    public final int getConnectionProperties() {
+        return mConnectionProperties;
+    }
+
+    /**
      * Whether the given capabilities support the specified capability.
      *
      * @param capabilities A capability bit field.
@@ -360,7 +377,7 @@
      * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
      * {@link Connection} for valid values.
      *
-     * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
+     * @param connectionCapabilities A bitmask of the {@code Capabilities} of the conference call.
      */
     public final void setConnectionCapabilities(int connectionCapabilities) {
         if (connectionCapabilities != mConnectionCapabilities) {
@@ -373,6 +390,22 @@
     }
 
     /**
+     * Sets the properties of a conference. See {@code PROPERTY_*} constants of class
+     * {@link Connection} for valid values.
+     *
+     * @param connectionProperties A bitmask of the {@code Properties} of the conference call.
+     */
+    public final void setConnectionProperties(int connectionProperties) {
+        if (connectionProperties != mConnectionProperties) {
+            mConnectionProperties = connectionProperties;
+
+            for (Listener l : mListeners) {
+                l.onConnectionPropertiesChanged(this, mConnectionProperties);
+            }
+        }
+    }
+
+    /**
      * Adds the specified connection as a child of this conference.
      *
      * @param connection The connection to add.
@@ -640,23 +673,171 @@
     }
 
     /**
-     * Set some extras that can be associated with this {@code Conference}. No assumptions should
-     * be made as to how an In-Call UI or service will handle these extras.
+     * Replaces all the extras associated with this {@code Conference}.
+     * <p>
+     * New or existing keys are replaced in the {@code Conference} extras.  Keys which are no longer
+     * in the new extras, but were present the last time {@code setExtras} was called are removed.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these extras.
      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
-     * @param extras The extras associated with this {@code Connection}.
+     * @param extras The extras associated with this {@code Conference}.
+     * @deprecated Use {@link #putExtras(Bundle)} to add extras.  Use {@link #removeExtras(List)}
+     * to remove extras.
      */
     public final void setExtras(@Nullable Bundle extras) {
-        mExtras = extras;
+        // Add/replace any new or changed extras values.
+        putExtras(extras);
+
+        // If we have used "setExtras" in the past, compare the key set from the last invocation to
+        // the current one and remove any keys that went away.
+        if (mPreviousExtraKeys != null) {
+            List<String> toRemove = new ArrayList<String>();
+            for (String oldKey : mPreviousExtraKeys) {
+                if (!extras.containsKey(oldKey)) {
+                    toRemove.add(oldKey);
+                }
+            }
+
+            if (!toRemove.isEmpty()) {
+                removeExtras(toRemove);
+            }
+        }
+
+        // Track the keys the last time set called setExtras.  This way, the next time setExtras is
+        // called we can see if the caller has removed any extras values.
+        if (mPreviousExtraKeys == null) {
+            mPreviousExtraKeys = new ArraySet<String>();
+        }
+        mPreviousExtraKeys.clear();
+        mPreviousExtraKeys.addAll(extras.keySet());
+    }
+
+    /**
+     * Adds some extras to this {@link Conference}.  Existing keys are replaced and new ones are
+     * added.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these extras.
+     * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
+     *
+     * @param extras The extras to add.
+     */
+    public final void putExtras(@NonNull Bundle extras) {
+        if (extras == null) {
+            return;
+        }
+
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putAll(extras);
+
         for (Listener l : mListeners) {
             l.onExtrasChanged(this, extras);
         }
     }
 
     /**
-     * @return The extras associated with this conference.
+     * Adds a boolean extra to this {@link Conference}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, boolean value) {
+        Bundle newExtras = new Bundle();
+        newExtras.putBoolean(key, value);
+        putExtras(newExtras);
+    }
+
+    /**
+     * Adds an integer extra to this {@link Conference}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, int value) {
+        Bundle newExtras = new Bundle();
+        newExtras.putInt(key, value);
+        putExtras(newExtras);
+    }
+
+    /**
+     * Adds a string extra to this {@link Conference}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, String value) {
+        Bundle newExtras = new Bundle();
+        newExtras.putString(key, value);
+        putExtras(newExtras);
+    }
+
+    /**
+     * Removes an extra from this {@link Conference}.
+     *
+     * @param keys The key of the extra key to remove.
+     */
+    public final void removeExtras(List<String> keys) {
+        if (keys == null || keys.isEmpty()) {
+            return;
+        }
+
+        if (mExtras != null) {
+            for (String key : keys) {
+                mExtras.remove(key);
+            }
+            if (mExtras.size() == 0) {
+                mExtras = null;
+            }
+        }
+
+        for (Listener l : mListeners) {
+            l.onExtrasRemoved(this, keys);
+        }
+    }
+
+    /**
+     * Returns the extras associated with this conference.
+     * <p>
+     * Extras should be updated using {@link #putExtras(Bundle)} and {@link #removeExtras(List)}.
+     * <p>
+     * Telecom or an {@link InCallService} can also update the extras via
+     * {@link android.telecom.Call#putExtras(Bundle)}, and
+     * {@link Call#removeExtras(List)}.
+     * <p>
+     * The conference is notified of changes to the extras made by Telecom or an
+     * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
+     *
+     * @return The extras associated with this connection.
      */
     public final Bundle getExtras() {
         return mExtras;
     }
+
+    /**
+     * Notifies this {@link Conference} of a change to the extras made outside the
+     * {@link ConnectionService}.
+     * <p>
+     * These extras changes can originate from Telecom itself, or from an {@link InCallService} via
+     * {@link android.telecom.Call#putExtras(Bundle)}, and
+     * {@link Call#removeExtras(List)}.
+     *
+     * @param extras The new extras bundle.
+     */
+    public void onExtrasChanged(Bundle extras) {}
+
+    /**
+     * Handles a change to extras received from Telecom.
+     *
+     * @param extras The new extras.
+     * @hide
+     */
+    final void handleExtrasChanged(Bundle extras) {
+        mExtras = extras;
+        onExtrasChanged(mExtras);
+    }
 }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 51a6588..310c957 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -20,6 +20,7 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraManager;
@@ -30,6 +31,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.view.Surface;
 
 import java.util.ArrayList;
@@ -96,7 +98,7 @@
      * The state of an external connection which is in the process of being pulled from a remote
      * device to the local device.
      * <p>
-     * A connection can only be in this state if the {@link #CAPABILITY_IS_EXTERNAL_CALL} and
+     * A connection can only be in this state if the {@link #PROPERTY_IS_EXTERNAL_CALL} property and
      * {@link #CAPABILITY_CAN_PULL_CALL} capability bits are set on the connection.
      */
     public static final int STATE_PULLING_CALL = 7;
@@ -192,31 +194,28 @@
     public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 0x00002000;
 
     /**
-     * Whether the call is a generic conference, where we do not know the precise state of
-     * participants in the conference (eg. on CDMA).
-     *
+     * Un-used.
      * @hide
      */
-    public static final int CAPABILITY_GENERIC_CONFERENCE = 0x00004000;
+    public static final int CAPABILITY_UNUSED_2 = 0x00004000;
 
     /**
-     * Connection is using high definition audio.
+     * Un-used.
      * @hide
      */
-    public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00008000;
+    public static final int CAPABILITY_UNUSED_3 = 0x00008000;
 
     /**
-     * Connection is using WIFI.
+     * Un-used.
      * @hide
      */
-    public static final int CAPABILITY_WIFI = 0x00010000;
+    public static final int CAPABILITY_UNUSED_4 = 0x00010000;
 
     /**
-     * Indicates that the current device callback number should be shown.
-     *
+     * Un-used.
      * @hide
      */
-    public static final int CAPABILITY_SHOW_CALLBACK_NUMBER = 0x00020000;
+    public static final int CAPABILITY_UNUSED_5 = 0x00020000;
 
     /**
      * Speed up audio setup for MT call.
@@ -279,32 +278,64 @@
     public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 0x00800000;
 
     /**
+     * When set for an external connection, indicates that this {@code Connection} can be pulled
+     * from a remote device to the current device.
+     * <p>
+     * Should only be set on a {@code Connection} where {@link #PROPERTY_IS_EXTERNAL_CALL}
+     * is set.
+     */
+    public static final int CAPABILITY_CAN_PULL_CALL = 0x01000000;
+
+    //**********************************************************************************************
+    // Next CAPABILITY value: 0x02000000
+    //**********************************************************************************************
+
+    /**
+     * Indicates that the current device callback number should be shown.
+     *
+     * @hide
+     */
+    public static final int PROPERTY_SHOW_CALLBACK_NUMBER = 1<<0;
+
+    /**
+     * Whether the call is a generic conference, where we do not know the precise state of
+     * participants in the conference (eg. on CDMA).
+     *
+     * @hide
+     */
+    public static final int PROPERTY_GENERIC_CONFERENCE = 1<<1;
+
+    /**
+     * Connection is using high definition audio.
+     * @hide
+     */
+    public static final int PROPERTY_HIGH_DEF_AUDIO = 1<<2;
+
+    /**
+     * Connection is using WIFI.
+     * @hide
+     */
+    public static final int PROPERTY_WIFI = 1<<3;
+
+    /**
      * When set, indicates that the {@code Connection} does not actually exist locally for the
      * {@link ConnectionService}.
      * <p>
      * Consider, for example, a scenario where a user has two devices with the same phone number.
      * When a user places a call on one devices, the telephony stack can represent that call on the
      * other device by adding is to the {@link ConnectionService} with the
-     * {@code CAPABILITY_IS_EXTERNAL_CALL} capability set.
+     * {@link #PROPERTY_IS_EXTERNAL_CALL} capability set.
      * <p>
      * An {@link ConnectionService} should not assume that all {@link InCallService}s will handle
      * external connections.  Only those {@link InCallService}s which have the
      * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true} in its
      * manifest will see external connections.
      */
-    public static final int CAPABILITY_IS_EXTERNAL_CALL = 0x01000000;
+    public static final int PROPERTY_IS_EXTERNAL_CALL = 1<<4;
 
-    /**
-     * When set for an external connection, indicates that this {@code Connection} can be pulled
-     * from a remote device to the current device.
-     * <p>
-     * Should only be set on a {@code Connection} where {@link #CAPABILITY_IS_EXTERNAL_CALL}
-     * is set.
-     */
-    public static final int CAPABILITY_CAN_PULL_CALL = 0x02000000;
 
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x04000000
+    // Next PROPERTY value: 1<<5
     //**********************************************************************************************
 
     /**
@@ -452,18 +483,6 @@
         if (can(capabilities, CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO)) {
             builder.append(" CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO");
         }
-        if (can(capabilities, CAPABILITY_HIGH_DEF_AUDIO)) {
-            builder.append(" CAPABILITY_HIGH_DEF_AUDIO");
-        }
-        if (can(capabilities, CAPABILITY_WIFI)) {
-            builder.append(" CAPABILITY_WIFI");
-        }
-        if (can(capabilities, CAPABILITY_GENERIC_CONFERENCE)) {
-            builder.append(" CAPABILITY_GENERIC_CONFERENCE");
-        }
-        if (can(capabilities, CAPABILITY_SHOW_CALLBACK_NUMBER)) {
-            builder.append(" CAPABILITY_SHOW_CALLBACK_NUMBER");
-        }
         if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) {
             builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO");
         }
@@ -479,9 +498,6 @@
         if (can(capabilities, CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
             builder.append(" CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION");
         }
-        if (can(capabilities, CAPABILITY_IS_EXTERNAL_CALL)) {
-            builder.append(" CAPABILITY_IS_EXTERNAL_CALL");
-        }
         if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
             builder.append(" CAPABILITY_CAN_PULL_CALL");
         }
@@ -490,6 +506,34 @@
         return builder.toString();
     }
 
+    public static String propertiesToString(int properties) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[Properties:");
+
+        if (can(properties, PROPERTY_SHOW_CALLBACK_NUMBER)) {
+            builder.append(" PROPERTY_SHOW_CALLBACK_NUMBER");
+        }
+
+        if (can(properties, PROPERTY_HIGH_DEF_AUDIO)) {
+            builder.append(" PROPERTY_HIGH_DEF_AUDIO");
+        }
+
+        if (can(properties, PROPERTY_WIFI)) {
+            builder.append(" PROPERTY_WIFI");
+        }
+
+        if (can(properties, PROPERTY_GENERIC_CONFERENCE)) {
+            builder.append(" PROPERTY_GENERIC_CONFERENCE");
+        }
+
+        if (can(properties, PROPERTY_IS_EXTERNAL_CALL)) {
+            builder.append(" PROPERTY_IS_EXTERNAL_CALL");
+        }
+
+        builder.append("]");
+        return builder.toString();
+    }
+
     /** @hide */
     public abstract static class Listener {
         public void onStateChanged(Connection c, int state) {}
@@ -503,6 +547,7 @@
         public void onRingbackRequested(Connection c, boolean ringback) {}
         public void onDestroyed(Connection c) {}
         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {}
+        public void onConnectionPropertiesChanged(Connection c, int properties) {}
         public void onVideoProviderChanged(
                 Connection c, VideoProvider videoProvider) {}
         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
@@ -516,6 +561,7 @@
         public void onConferenceStarted() {}
         public void onConferenceMergeFailed(Connection c) {}
         public void onExtrasChanged(Connection c, Bundle extras) {}
+        public void onExtrasRemoved(Connection c, List<String> keys) {}
         public void onConnectionEvent(Connection c, String event, Bundle extras) {}
     }
 
@@ -1172,6 +1218,7 @@
     private int mCallerDisplayNamePresentation;
     private boolean mRingbackRequested = false;
     private int mConnectionCapabilities;
+    private int mConnectionProperties;
     private VideoProvider mVideoProvider;
     private boolean mAudioModeIsVoip;
     private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
@@ -1183,6 +1230,13 @@
     private Bundle mExtras;
 
     /**
+     * Tracks the key set for the extras bundle provided on the last invocation of
+     * {@link #setExtras(Bundle)}.  Used so that on subsequent invocations we can remove any extras
+     * keys which were set previously but are no longer present in the replacement Bundle.
+     */
+    private Set<String> mPreviousExtraKeys;
+
+    /**
      * Create a new Connection.
      */
     public Connection() {}
@@ -1318,6 +1372,17 @@
     }
 
     /**
+     * Returns the extras associated with this connection.
+     * <p>
+     * Extras should be updated using {@link #putExtras(Bundle)}.
+     * <p>
+     * Telecom or an {@link InCallService} can also update the extras via
+     * {@link android.telecom.Call#putExtras(Bundle)}, and
+     * {@link Call#removeExtras(List)}.
+     * <p>
+     * The connection is notified of changes to the extras made by Telecom or an
+     * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
+     *
      * @return The extras associated with this connection.
      */
     public final Bundle getExtras() {
@@ -1418,6 +1483,13 @@
     }
 
     /**
+     * Returns the connection's properties, as a bit mask of the {@code PROPERTY_*} constants.
+     */
+    public final int getConnectionProperties() {
+        return mConnectionProperties;
+    }
+
+    /**
      * Sets the value of the {@link #getAddress()} property.
      *
      * @param address The new address.
@@ -1614,6 +1686,21 @@
     }
 
     /**
+     * Sets the connection's properties as a bit mask of the {@code PROPERTY_*} constants.
+     *
+     * @param connectionProperties The new connection properties.
+     */
+    public final void setConnectionProperties(int connectionProperties) {
+        checkImmutable();
+        if (mConnectionProperties != connectionProperties) {
+            mConnectionProperties = connectionProperties;
+            for (Listener l : mListeners) {
+                l.onConnectionPropertiesChanged(this, mConnectionProperties);
+            }
+        }
+    }
+
+    /**
      * Tears down the Connection object.
      */
     public final void destroy() {
@@ -1777,21 +1864,133 @@
     }
 
     /**
-     * Set some extras that can be associated with this {@code Connection}. No assumptions should
-     * be made as to how an In-Call UI or service will handle these extras.
+     * Set some extras that can be associated with this {@code Connection}.
+     * <p>
+     * New or existing keys are replaced in the {@code Connection} extras.  Keys which are no longer
+     * in the new extras, but were present the last time {@code setExtras} was called are removed.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these extras.
      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
      *
      * @param extras The extras associated with this {@code Connection}.
+     * @deprecated Use {@link #putExtras(Bundle)} to add extras.  Use {@link #removeExtras(List)}
+     * to remove extras.
      */
     public final void setExtras(@Nullable Bundle extras) {
         checkImmutable();
-        mExtras = extras;
+
+        // Add/replace any new or changed extras values.
+        putExtras(extras);
+
+        // If we have used "setExtras" in the past, compare the key set from the last invocation to
+        // the current one and remove any keys that went away.
+        if (mPreviousExtraKeys != null) {
+            List<String> toRemove = new ArrayList<String>();
+            for (String oldKey : mPreviousExtraKeys) {
+                if (!extras.containsKey(oldKey)) {
+                    toRemove.add(oldKey);
+                }
+            }
+            if (!toRemove.isEmpty()) {
+                removeExtras(toRemove);
+            }
+        }
+
+        // Track the keys the last time set called setExtras.  This way, the next time setExtras is
+        // called we can see if the caller has removed any extras values.
+        if (mPreviousExtraKeys == null) {
+            mPreviousExtraKeys = new ArraySet<String>();
+        }
+        mPreviousExtraKeys.clear();
+        mPreviousExtraKeys.addAll(extras.keySet());
+    }
+
+    /**
+     * Adds some extras to this {@code Connection}.  Existing keys are replaced and new ones are
+     * added.
+     * <p>
+     * No assumptions should be made as to how an In-Call UI or service will handle these extras.
+     * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
+     *
+     * @param extras The extras to add.
+     */
+    public final void putExtras(@NonNull Bundle extras) {
+        checkImmutable();
+        if (extras == null) {
+            return;
+        }
+
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putAll(extras);
+
         for (Listener l : mListeners) {
             l.onExtrasChanged(this, extras);
         }
     }
 
     /**
+     * Adds a boolean extra to this {@code Connection}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, boolean value) {
+        Bundle newExtras = new Bundle();
+        newExtras.putBoolean(key, value);
+        putExtras(newExtras);
+    }
+
+    /**
+     * Adds an integer extra to this {@code Connection}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, int value) {
+        Bundle newExtras = new Bundle();
+        newExtras.putInt(key, value);
+        putExtras(newExtras);
+    }
+
+    /**
+     * Adds a string extra to this {@code Connection}.
+     *
+     * @param key The extra key.
+     * @param value The value.
+     * @hide
+     */
+    public final void putExtra(String key, String value) {
+        Bundle newExtras = new Bundle();
+        newExtras.putString(key, value);
+        putExtras(newExtras);
+    }
+
+    /**
+     * Removes an extra from this {@code Connection}.
+     *
+     * @param keys The key of the extra key to remove.
+     */
+    public final void removeExtras(List<String> keys) {
+        if (mExtras != null) {
+            for (String key : keys) {
+                mExtras.remove(key);
+            }
+
+            if (mExtras.size() == 0) {
+                mExtras = null;
+            }
+        }
+
+        for (Listener l : mListeners) {
+            l.onExtrasRemoved(this, keys);
+        }
+    }
+
+    /**
      * Notifies this Connection that the {@link #getAudioState()} property has a new value.
      *
      * @param state The new connection audio state.
@@ -1909,10 +2108,10 @@
      * The {@link InCallService} issues a request to pull an external call to the local device via
      * {@link Call#pullExternalCall()}.
      * <p>
-     * For a Connection to be pulled, both the {@link Connection#CAPABILITY_CAN_PULL_CALL} and
-     * {@link Connection#CAPABILITY_IS_EXTERNAL_CALL} capability bits must be set.
+     * For a Connection to be pulled, both the {@link Connection#CAPABILITY_CAN_PULL_CALL}
+     * capability and {@link Connection#PROPERTY_IS_EXTERNAL_CALL} property bits must be set.
      * <p>
-     * For more information on external calls, see {@link Connection#CAPABILITY_IS_EXTERNAL_CALL}.
+     * For more information on external calls, see {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
      */
     public void onPullExternalCall() {}
 
@@ -1928,6 +2127,18 @@
      */
     public void onCallEvent(String event, Bundle extras) {}
 
+    /**
+     * Notifies this {@link Connection} of a change to the extras made outside the
+     * {@link ConnectionService}.
+     * <p>
+     * These extras changes can originate from Telecom itself, or from an {@link InCallService} via
+     * the {@link android.telecom.Call#putExtras(Bundle)} and
+     * {@link Call#removeExtras(List)}.
+     *
+     * @param extras The new extras bundle.
+     */
+    public void onExtrasChanged(Bundle extras) {}
+
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
         if (number == null) {
@@ -2048,6 +2259,17 @@
     }
 
     /**
+     * Handles a change to extras received from Telecom.
+     *
+     * @param extras The new extras.
+     * @hide
+     */
+    final void handleExtrasChanged(Bundle extras) {
+        mExtras = extras;
+        onExtrasChanged(mExtras);
+    }
+
+    /**
      * Notifies listeners that the merge request failed.
      *
      * @hide
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index d18b317..4cab7f0 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -105,6 +105,7 @@
     private static final int MSG_SILENCE = 21;
     private static final int MSG_PULL_EXTERNAL_CALL = 22;
     private static final int MSG_SEND_CALL_EVENT = 23;
+    private static final int MSG_ON_EXTRAS_CHANGED = 24;
 
     private static Connection sNullConnection;
 
@@ -261,6 +262,14 @@
             args.arg3 = extras;
             mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget();
         }
+
+        @Override
+        public void onExtrasChanged(String callId, Bundle extras) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = extras;
+            mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget();
+        }
     };
 
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -414,6 +423,17 @@
                     }
                     break;
                 }
+                case MSG_ON_EXTRAS_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        Bundle extras = (Bundle) args.arg2;
+                        handleExtrasChanged(callId, extras);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -475,6 +495,16 @@
         }
 
         @Override
+        public void onConnectionPropertiesChanged(
+                Conference conference,
+                int connectionProperties) {
+            String id = mIdByConference.get(conference);
+            Log.d(this, "call capabilities: conference: %s",
+                    Connection.propertiesToString(connectionProperties));
+            mAdapter.setConnectionProperties(id, connectionProperties);
+        }
+
+        @Override
         public void onVideoStateChanged(Conference c, int videoState) {
             String id = mIdByConference.get(c);
             Log.d(this, "onVideoStateChanged set video state %d", videoState);
@@ -492,13 +522,25 @@
         @Override
         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
             String id = mIdByConference.get(conference);
-            mAdapter.setStatusHints(id, statusHints);
+            if (id != null) {
+                mAdapter.setStatusHints(id, statusHints);
+            }
         }
 
         @Override
-        public void onExtrasChanged(Conference conference, Bundle extras) {
-            String id = mIdByConference.get(conference);
-            mAdapter.setExtras(id, extras);
+        public void onExtrasChanged(Conference c, Bundle extras) {
+            String id = mIdByConference.get(c);
+            if (id != null) {
+                mAdapter.putExtras(id, extras);
+            }
+        }
+
+        @Override
+        public void onExtrasRemoved(Conference c, List<String> keys) {
+            String id = mIdByConference.get(c);
+            if (id != null) {
+                mAdapter.removeExtras(id, keys);
+            }
         }
     };
 
@@ -591,6 +633,14 @@
         }
 
         @Override
+        public void onConnectionPropertiesChanged(Connection c, int properties) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "properties: parcelableconnection: %s",
+                    Connection.propertiesToString(properties));
+            mAdapter.setConnectionProperties(id, properties);
+        }
+
+        @Override
         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
             String id = mIdByConnection.get(c);
             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
@@ -639,12 +689,20 @@
         }
 
         @Override
-        public void onExtrasChanged(Connection connection, Bundle extras) {
-            String id = mIdByConnection.get(connection);
+        public void onExtrasChanged(Connection c, Bundle extras) {
+            String id = mIdByConnection.get(c);
             if (id != null) {
-                mAdapter.setExtras(id, extras);
+                mAdapter.putExtras(id, extras);
             }
         }
+        
+        public void onExtrasRemoved(Connection c, List<String> keys) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.removeExtras(id, keys);
+            }
+        }
+
 
         @Override
         public void onConnectionEvent(Connection connection, String event, Bundle extras) {
@@ -700,10 +758,11 @@
 
         Uri address = connection.getAddress();
         String number = address == null ? "null" : address.getSchemeSpecificPart();
-        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s",
+        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
                 Connection.toLogSafePhoneNumber(number),
                 Connection.stateToString(connection.getState()),
-                Connection.capabilitiesToString(connection.getConnectionCapabilities()));
+                Connection.capabilitiesToString(connection.getConnectionCapabilities()),
+                Connection.propertiesToString(connection.getConnectionProperties()));
 
         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
         mAdapter.handleCreateConnectionComplete(
@@ -713,6 +772,7 @@
                         request.getAccountHandle(),
                         connection.getState(),
                         connection.getConnectionCapabilities(),
+                        connection.getConnectionProperties(),
                         connection.getAddress(),
                         connection.getAddressPresentation(),
                         connection.getCallerDisplayName(),
@@ -929,6 +989,27 @@
 
     }
 
+    /**
+     * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
+     * <p>
+     * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
+     * the {@link android.telecom.Call#putExtra(String, boolean)},
+     * {@link android.telecom.Call#putExtra(String, int)},
+     * {@link android.telecom.Call#putExtra(String, String)},
+     * {@link Call#removeExtras(List)}.
+     *
+     * @param callId The ID of the call receiving the event.
+     * @param extras The new extras bundle.
+     */
+    private void handleExtrasChanged(String callId, Bundle extras) {
+        Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
+        } else if (mConferenceById.containsKey(callId)) {
+            findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
+        }
+    }
+
     private void onPostDialContinue(String callId, boolean proceed) {
         Log.d(this, "onPostDialContinue(%s)", callId);
         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
@@ -1049,6 +1130,7 @@
                     conference.getPhoneAccountHandle(),
                     conference.getState(),
                     conference.getConnectionCapabilities(),
+                    conference.getConnectionProperties(),
                     connectionIds,
                     conference.getVideoProvider() == null ?
                             null : conference.getVideoProvider().getInterface(),
@@ -1089,6 +1171,7 @@
                     phoneAccountHandle,
                     connection.getState(),
                     connection.getConnectionCapabilities(),
+                    connection.getConnectionProperties(),
                     connection.getAddress(),
                     connection.getAddressPresentation(),
                     connection.getCallerDisplayName(),
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index e91128f..c8cd3c0 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -196,6 +196,15 @@
         }
     }
 
+    void setConnectionProperties(String callId, int properties) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setConnectionProperties(callId, properties);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
     /**
      * Indicates whether or not the specified call is currently conferenced into the specified
      * conference call.
@@ -398,16 +407,88 @@
     }
 
     /**
-     * Sets extras associated with a connection.
+     * Adds some extras associated with a {@code Connection}.
      *
      * @param callId The unique ID of the call.
-     * @param extras The extras to associate with this call.
+     * @param extras The extras to add.
      */
-    void setExtras(String callId, Bundle extras) {
-        Log.v(this, "setExtras: %s", extras);
+    void putExtras(String callId, Bundle extras) {
+        Log.v(this, "putExtras: %s", callId);
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.setExtras(callId, extras);
+                adapter.putExtras(callId, extras);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Adds an extra associated with a {@code Connection}.
+     *
+     * @param callId The unique ID of the call.
+     * @param key The extra key.
+     * @param value The extra value.
+     */
+    void putExtra(String callId, String key, boolean value) {
+        Log.v(this, "putExtra: %s %s=%b", callId, key, value);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                Bundle bundle = new Bundle();
+                bundle.putBoolean(key, value);
+                adapter.putExtras(callId, bundle);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Adds an extra associated with a {@code Connection}.
+     *
+     * @param callId The unique ID of the call.
+     * @param key The extra key.
+     * @param value The extra value.
+     */
+    void putExtra(String callId, String key, int value) {
+        Log.v(this, "putExtra: %s %s=%d", callId, key, value);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                Bundle bundle = new Bundle();
+                bundle.putInt(key, value);
+                adapter.putExtras(callId, bundle);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Adds an extra associated with a {@code Connection}.
+     *
+     * @param callId The unique ID of the call.
+     * @param key The extra key.
+     * @param value The extra value.
+     */
+    void putExtra(String callId, String key, String value) {
+        Log.v(this, "putExtra: %s %s=%s", callId, key, value);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                Bundle bundle = new Bundle();
+                bundle.putString(key, value);
+                adapter.putExtras(callId, bundle);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Removes extras associated with a {@code Connection}.
+     *  @param callId The unique ID of the call.
+     * @param keys The extra keys to remove.
+     */
+    void removeExtras(String callId, List<String> keys) {
+        Log.v(this, "removeExtras: %s %s", callId, keys);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.removeExtras(callId, keys);
             } catch (RemoteException ignored) {
             }
         }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 4b15e54..bf28feb 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -61,8 +61,10 @@
     private static final int MSG_ADD_EXISTING_CONNECTION = 21;
     private static final int MSG_ON_POST_DIAL_CHAR = 22;
     private static final int MSG_SET_CONFERENCE_MERGE_FAILED = 23;
-    private static final int MSG_SET_EXTRAS = 24;
-    private static final int MSG_ON_CONNECTION_EVENT = 25;
+    private static final int MSG_PUT_EXTRAS = 24;
+    private static final int MSG_REMOVE_EXTRAS = 25;
+    private static final int MSG_ON_CONNECTION_EVENT = 26;
+    private static final int MSG_SET_CONNECTION_PROPERTIES = 27;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -117,6 +119,9 @@
                 case MSG_SET_CONNECTION_CAPABILITIES:
                     mDelegate.setConnectionCapabilities((String) msg.obj, msg.arg1);
                     break;
+                case MSG_SET_CONNECTION_PROPERTIES:
+                    mDelegate.setConnectionProperties((String) msg.obj, msg.arg1);
+                    break;
                 case MSG_SET_IS_CONFERENCED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -233,10 +238,19 @@
                     }
                     break;
                 }
-                case MSG_SET_EXTRAS: {
+                case MSG_PUT_EXTRAS: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        mDelegate.setExtras((String) args.arg1, (Bundle) args.arg2);
+                        mDelegate.putExtras((String) args.arg1, (Bundle) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_REMOVE_EXTRAS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.removeExtras((String) args.arg1, (List<String>) args.arg2);
                     } finally {
                         args.recycle();
                     }
@@ -312,6 +326,13 @@
         }
 
         @Override
+        public void setConnectionProperties(String connectionId, int connectionProperties) {
+            mHandler.obtainMessage(
+                    MSG_SET_CONNECTION_PROPERTIES, connectionProperties, 0, connectionId)
+                    .sendToTarget();
+        }
+
+        @Override
         public void setConferenceMergeFailed(String callId) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = callId;
@@ -425,11 +446,19 @@
         }
 
         @Override
-        public final void setExtras(String connectionId, Bundle extras) {
+        public final void putExtras(String connectionId, Bundle extras) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = connectionId;
             args.arg2 = extras;
-            mHandler.obtainMessage(MSG_SET_EXTRAS, args).sendToTarget();
+            mHandler.obtainMessage(MSG_PUT_EXTRAS, args).sendToTarget();
+        }
+
+        @Override
+        public final void removeExtras(String connectionId, List<String> keys) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = keys;
+            mHandler.obtainMessage(MSG_REMOVE_EXTRAS, args).sendToTarget();
         }
 
         @Override
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 52ef4a7..3f270d9 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -21,6 +21,8 @@
 
 import com.android.internal.telecom.IInCallAdapter;
 
+import java.util.List;
+
 /**
  * Receives commands from {@link InCallService} implementations which should be executed by
  * Telecom. When Telecom binds to a {@link InCallService}, an instance of this class is given to
@@ -278,6 +280,79 @@
     }
 
     /**
+     * Intructs Telecom to add extras to a call.
+     *
+     * @param callId The callId to add the extras to.
+     * @param extras The extras.
+     */
+    public void putExtras(String callId, Bundle extras) {
+        try {
+            mAdapter.putExtras(callId, extras);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Intructs Telecom to add an extra to a call.
+     *
+     * @param callId The callId to add the extras to.
+     * @param key The extra key.
+     * @param value The extra value.
+     */
+    public void putExtra(String callId, String key, boolean value) {
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(key, value);
+            mAdapter.putExtras(callId, bundle);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Intructs Telecom to add an extra to a call.
+     *
+     * @param callId The callId to add the extras to.
+     * @param key The extra key.
+     * @param value The extra value.
+     */
+    public void putExtra(String callId, String key, int value) {
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putInt(key, value);
+            mAdapter.putExtras(callId, bundle);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Intructs Telecom to add an extra to a call.
+     *
+     * @param callId The callId to add the extras to.
+     * @param key The extra key.
+     * @param value The extra value.
+     */
+    public void putExtra(String callId, String key, String value) {
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putString(key, value);
+            mAdapter.putExtras(callId, bundle);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Intructs Telecom to remove extras from a call.
+     * @param callId The callId to remove the extras from.
+     * @param keys The extra keys to remove.
+     */
+    public void removeExtras(String callId, List<String> keys) {
+        try {
+            mAdapter.removeExtras(callId, keys);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Instructs Telecom to turn the proximity sensor on.
      */
     public void turnProximitySensorOn() {
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index 870f5ee..f5689d8 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -34,6 +34,7 @@
     private PhoneAccountHandle mPhoneAccount;
     private int mState;
     private int mConnectionCapabilities;
+    private int mConnectionProperties;
     private List<String> mConnectionIds;
     private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
     private final IVideoProvider mVideoProvider;
@@ -45,6 +46,7 @@
             PhoneAccountHandle phoneAccount,
             int state,
             int connectionCapabilities,
+            int connectionProperties,
             List<String> connectionIds,
             IVideoProvider videoProvider,
             int videoState,
@@ -54,6 +56,7 @@
         mPhoneAccount = phoneAccount;
         mState = state;
         mConnectionCapabilities = connectionCapabilities;
+        mConnectionProperties = connectionProperties;
         mConnectionIds = connectionIds;
         mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
         mVideoProvider = videoProvider;
@@ -72,6 +75,8 @@
                 .append(Connection.stateToString(mState))
                 .append(", capabilities: ")
                 .append(Connection.capabilitiesToString(mConnectionCapabilities))
+                .append(", properties: ")
+                .append(Connection.propertiesToString(mConnectionProperties))
                 .append(", connectTime: ")
                 .append(mConnectTimeMillis)
                 .append(", children: ")
@@ -95,6 +100,10 @@
         return mConnectionCapabilities;
     }
 
+    public int getConnectionProperties() {
+        return mConnectionProperties;
+    }
+
     public List<String> getConnectionIds() {
         return mConnectionIds;
     }
@@ -134,9 +143,11 @@
             int videoState = source.readInt();
             StatusHints statusHints = source.readParcelable(classLoader);
             Bundle extras = source.readBundle(classLoader);
+            int properties = source.readInt();
 
-            return new ParcelableConference(phoneAccount, state, capabilities, connectionIds,
-                    videoCallProvider, videoState, connectTimeMillis, statusHints, extras);
+            return new ParcelableConference(phoneAccount, state, capabilities, properties,
+                    connectionIds, videoCallProvider, videoState, connectTimeMillis, statusHints,
+                    extras);
         }
 
         @Override
@@ -164,5 +175,6 @@
         destination.writeInt(mVideoState);
         destination.writeParcelable(mStatusHints, 0);
         destination.writeBundle(mExtras);
+        destination.writeInt(mConnectionProperties);
     }
 }
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
index ce51c96..540f388 100644
--- a/telecomm/java/android/telecom/ParcelableConnection.java
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -36,6 +36,7 @@
     private final PhoneAccountHandle mPhoneAccount;
     private final int mState;
     private final int mConnectionCapabilities;
+    private final int mConnectionProperties;
     private final Uri mAddress;
     private final int mAddressPresentation;
     private final String mCallerDisplayName;
@@ -55,6 +56,7 @@
             PhoneAccountHandle phoneAccount,
             int state,
             int capabilities,
+            int properties,
             Uri address,
             int addressPresentation,
             String callerDisplayName,
@@ -71,6 +73,7 @@
         mPhoneAccount = phoneAccount;
         mState = state;
         mConnectionCapabilities = capabilities;
+        mConnectionProperties = properties;
         mAddress = address;
         mAddressPresentation = addressPresentation;
         mCallerDisplayName = callerDisplayName;
@@ -94,11 +97,26 @@
         return mState;
     }
 
-    // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}.
+    /**
+     * Returns the current connection capabilities bit-mask.  Connection capabilities are defined as
+     * {@code CAPABILITY_*} constants in {@link Connection}.
+     *
+     * @return Bit-mask containing capabilities of the connection.
+     */
     public int getConnectionCapabilities() {
         return mConnectionCapabilities;
     }
 
+    /**
+     * Returns the current connection properties bit-mask.  Connection properties are defined as
+     * {@code PROPERTY_*} constants in {@link Connection}.
+     *
+     * @return Bit-mask containing properties of the connection.
+     */
+    public int getConnectionProperties() {
+        return mConnectionProperties;
+    }
+
     public Uri getHandle() {
         return mAddress;
     }
@@ -160,6 +178,8 @@
                 .append(mState)
                 .append(", capabilities:")
                 .append(Connection.capabilitiesToString(mConnectionCapabilities))
+                .append(", properties:")
+                .append(Connection.propertiesToString(mConnectionProperties))
                 .append(", extras:")
                 .append(mExtras)
                 .toString();
@@ -189,11 +209,13 @@
             List<String> conferenceableConnectionIds = new ArrayList<>();
             source.readStringList(conferenceableConnectionIds);
             Bundle extras = Bundle.setDefusable(source.readBundle(classLoader), true);
+            int properties = source.readInt();
 
             return new ParcelableConnection(
                     phoneAccount,
                     state,
                     capabilities,
+                    properties,
                     address,
                     addressPresentation,
                     callerDisplayName,
@@ -241,5 +263,6 @@
         destination.writeParcelable(mDisconnectCause, 0);
         destination.writeStringList(mConferenceableConnectionIds);
         destination.writeBundle(mExtras);
+        destination.writeInt(mConnectionProperties);
     }
 }
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index ae5cd46..943da6d 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -92,6 +92,18 @@
                 int connectionCapabilities) {}
 
         /**
+         * Indicates that the call properties of this {@code RemoteConference} have changed.
+         * See {@link #getConnectionProperties()}.
+         *
+         * @param conference The {@code RemoteConference} invoking this method.
+         * @param connectionProperties The new properties of the {@code RemoteConference}.
+         */
+        public void onConnectionPropertiesChanged(
+                RemoteConference conference,
+                int connectionProperties) {}
+
+
+        /**
          * Invoked when the set of {@link RemoteConnection}s which can be added to this conference
          * call have changed.
          *
@@ -133,6 +145,7 @@
     private int mState = Connection.STATE_NEW;
     private DisconnectCause mDisconnectCause;
     private int mConnectionCapabilities;
+    private int mConnectionProperties;
     private Bundle mExtras;
 
     /** @hide */
@@ -244,6 +257,24 @@
     }
 
     /** @hide */
+    void setConnectionProperties(final int connectionProperties) {
+        if (mConnectionProperties != connectionProperties) {
+            mConnectionProperties = connectionProperties;
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                final RemoteConference conference = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onConnectionPropertiesChanged(
+                                conference, mConnectionProperties);
+                    }
+                });
+            }
+        }
+    }
+
+    /** @hide */
     void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) {
         mConferenceableConnections.clear();
         mConferenceableConnections.addAll(conferenceableConnections);
@@ -279,15 +310,35 @@
     }
 
     /** @hide */
-    void setExtras(final Bundle extras) {
-        mExtras = extras;
+    void putExtras(final Bundle extras) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putAll(extras);
+
+        notifyExtrasChanged();
+    }
+
+    /** @hide */
+    void removeExtras(List<String> keys) {
+        if (mExtras == null || keys == null || keys.isEmpty()) {
+            return;
+        }
+        for (String key : keys) {
+            mExtras.remove(key);
+        }
+
+        notifyExtrasChanged();
+    }
+
+    private void notifyExtrasChanged() {
         for (CallbackRecord<Callback> record : mCallbackRecords) {
             final RemoteConference conference = this;
             final Callback callback = record.getCallback();
             record.getHandler().post(new Runnable() {
                 @Override
                 public void run() {
-                    callback.onExtrasChanged(conference, extras);
+                    callback.onExtrasChanged(conference, mExtras);
                 }
             });
         }
@@ -322,6 +373,16 @@
     }
 
     /**
+     * Returns the properties of the conference. See {@code PROPERTY_*} constants in class
+     * {@link Connection} for valid values.
+     *
+     * @return A bitmask of the properties of the conference call.
+     */
+    public final int getConnectionProperties() {
+        return mConnectionProperties;
+    }
+
+    /**
      * Obtain the extras associated with this {@code RemoteConnection}.
      *
      * @return The extras for this connection.
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 5b602eb..dc8eaf6 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -90,6 +90,17 @@
                 int connectionCapabilities) {}
 
         /**
+         * Indicates that the call properties of this {@code RemoteConnection} have changed.
+         * See {@link #getConnectionProperties()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param connectionProperties The new properties of the {@code RemoteConnection}.
+         */
+        public void onConnectionPropertiesChanged(
+                RemoteConnection connection,
+                int connectionProperties) {}
+
+        /**
          * Invoked when the post-dial sequence in the outgoing {@code Connection} has reached a
          * pause character. This causes the post-dial signals to stop pending user confirmation. An
          * implementation should present this choice to the user and invoke
@@ -588,6 +599,7 @@
     private boolean mRingbackRequested;
     private boolean mConnected;
     private int mConnectionCapabilities;
+    private int mConnectionProperties;
     private int mVideoState;
     private VideoProvider mVideoProvider;
     private boolean mIsVoipAudioMode;
@@ -624,6 +636,7 @@
         mDisconnectCause = connection.getDisconnectCause();
         mRingbackRequested = connection.isRingbackRequested();
         mConnectionCapabilities = connection.getConnectionCapabilities();
+        mConnectionProperties = connection.getConnectionProperties();
         mVideoState = connection.getVideoState();
         mVideoProvider = new RemoteConnection.VideoProvider(connection.getVideoProvider());
         mIsVoipAudioMode = connection.getIsVoipAudioMode();
@@ -719,6 +732,16 @@
     }
 
     /**
+     * Obtains the properties of this {@code RemoteConnection}.
+     *
+     * @return A bitmask of the properties of the {@code RemoteConnection}, as defined in the
+     *         {@code PROPERTY_*} constants in class {@link Connection}.
+     */
+    public int getConnectionProperties() {
+        return mConnectionProperties;
+    }
+
+    /**
      * Determines if the audio mode of this {@code RemoteConnection} is VOIP.
      *
      * @return {@code true} if the {@code RemoteConnection}'s current audio mode is VOIP.
@@ -1114,6 +1137,23 @@
     /**
      * @hide
      */
+    void setConnectionProperties(final int connectionProperties) {
+        mConnectionProperties = connectionProperties;
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onConnectionPropertiesChanged(connection, connectionProperties);
+                }
+            });
+        }
+    }
+
+    /**
+     * @hide
+     */
     void setDestroyed() {
         if (!mCallbackRecords.isEmpty()) {
             // Make sure that the callbacks are notified that the call is destroyed first.
@@ -1302,15 +1342,35 @@
     }
 
     /** @hide */
-    void setExtras(final Bundle extras) {
-        mExtras = extras;
+    void putExtras(final Bundle extras) {
+        if (mExtras == null) {
+            mExtras = new Bundle();
+        }
+        mExtras.putAll(extras);
+
+        notifyExtrasChanged();
+    }
+
+    /** @hide */
+    void removeExtras(List<String> keys) {
+        if (mExtras == null || keys == null || keys.isEmpty()) {
+            return;
+        }
+        for (String key : keys) {
+            mExtras.remove(key);
+        }
+
+        notifyExtrasChanged();
+    }
+
+    private void notifyExtrasChanged() {
         for (CallbackRecord record : mCallbackRecords) {
             final RemoteConnection connection = this;
             final Callback callback = record.getCallback();
             record.getHandler().post(new Runnable() {
                 @Override
                 public void run() {
-                    callback.onExtrasChanged(connection, extras);
+                    callback.onExtrasChanged(connection, mExtras);
                 }
             });
         }
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index fa7183a..21a7706 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -61,6 +61,7 @@
                 mPendingConnections.remove(connection);
                 // Unconditionally initialize the connection ...
                 connection.setConnectionCapabilities(parcel.getConnectionCapabilities());
+                connection.setConnectionProperties(parcel.getConnectionProperties());
                 if (parcel.getHandle() != null
                     || parcel.getState() != Connection.STATE_DISCONNECTED) {
                     connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation());
@@ -156,6 +157,17 @@
         }
 
         @Override
+        public void setConnectionProperties(String callId, int connectionProperties) {
+            if (mConnectionById.containsKey(callId)) {
+                findConnectionForAction(callId, "setConnectionProperties")
+                        .setConnectionProperties(connectionProperties);
+            } else {
+                findConferenceForAction(callId, "setConnectionProperties")
+                        .setConnectionProperties(connectionProperties);
+            }
+        }
+
+        @Override
         public void setIsConferenced(String callId, String conferenceCallId) {
             // Note: callId should not be null; conferenceCallId may be null
             RemoteConnection connection =
@@ -321,13 +333,20 @@
         }
 
         @Override
-        public void setExtras(String callId, Bundle extras) {
-            if (mConnectionById.containsKey(callId)) {
-                findConnectionForAction(callId, "setExtras")
-                        .setExtras(extras);
+        public void putExtras(String callId, Bundle extras) {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "putExtras").putExtras(extras);
             } else {
-                findConferenceForAction(callId, "setExtras")
-                        .setExtras(extras);
+                findConferenceForAction(callId, "putExtras").putExtras(extras);
+            }
+        }
+
+        @Override
+        public void removeExtras(String callId, List<String> keys) {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "removeExtra").removeExtras(keys);
+            } else {
+                findConferenceForAction(callId, "removeExtra").removeExtras(keys);
             }
         }
 
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 3ee0e9f..a4c1798 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -79,4 +79,6 @@
     void pullExternalCall(String callId);
 
     void sendCallEvent(String callId, String event, in Bundle extras);
+
+    void onExtrasChanged(String callId, in Bundle extras);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index dff1b11..9bc8ffe 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -55,6 +55,8 @@
 
     void setConnectionCapabilities(String callId, int connectionCapabilities);
 
+    void setConnectionProperties(String callId, int connectionProperties);
+
     void setIsConferenced(String callId, String conferenceCallId);
 
     void setConferenceMergeFailed(String callId);
@@ -85,7 +87,9 @@
 
     void addExistingConnection(String callId, in ParcelableConnection connection);
 
-    void setExtras(String callId, in Bundle extras);
+    void putExtras(String callId, in Bundle extras);
+
+    void removeExtras(String callId, in List<String> keys);
 
     void onConnectionEvent(String callId, String event, in Bundle extras);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 0678fe2..49f9b3b 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -65,4 +65,8 @@
     void pullExternalCall(String callId);
 
     void sendCallEvent(String callId, String event, in Bundle extras);
+
+    void putExtras(String callId, in Bundle extras);
+
+    void removeExtras(String callId, in List<String> keys);
 }
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index ae130d4..bb2b447 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.telephony.IPhoneStateListener;
 import java.util.List;
+import java.lang.ref.WeakReference;
 
 /**
  * A listener class for monitoring changes in specific telephony states
@@ -533,84 +534,101 @@
     /**
      * The callback methods need to be called on the handler thread where
      * this object was created.  If the binder did that for us it'd be nice.
+     *
+     * Using a static class and weak reference here to avoid memory leak caused by the
+     * IPhoneStateListener.Stub callback retaining references to the outside PhoneStateListeners:
+     * even caller has been destroyed and "un-registered" the PhoneStateListener, it is still not
+     * eligible for GC given the references coming from:
+     * Native Stack --> PhoneStateListener --> Context (Activity).
+     * memory of caller's context will be collected after GC from service side get triggered
      */
-    IPhoneStateListener callback = new IPhoneStateListener.Stub() {
+    private static class IPhoneStateListenerStub extends IPhoneStateListener.Stub {
+        private WeakReference<PhoneStateListener> mPhoneStateListenerWeakRef;
+
+        public IPhoneStateListenerStub(PhoneStateListener phoneStateListener) {
+            mPhoneStateListenerWeakRef = new WeakReference<PhoneStateListener>(phoneStateListener);
+        }
+
+        private void send(int what, int arg1, int arg2, Object obj) {
+            PhoneStateListener listener = mPhoneStateListenerWeakRef.get();
+            if (listener != null) {
+                Message.obtain(listener.mHandler, what, arg1, arg2, obj).sendToTarget();
+            }
+        }
+
         public void onServiceStateChanged(ServiceState serviceState) {
-            Message.obtain(mHandler, LISTEN_SERVICE_STATE, 0, 0, serviceState).sendToTarget();
+            send(LISTEN_SERVICE_STATE, 0, 0, serviceState);
         }
 
         public void onSignalStrengthChanged(int asu) {
-            Message.obtain(mHandler, LISTEN_SIGNAL_STRENGTH, asu, 0, null).sendToTarget();
+            send(LISTEN_SIGNAL_STRENGTH, asu, 0, null);
         }
 
         public void onMessageWaitingIndicatorChanged(boolean mwi) {
-            Message.obtain(mHandler, LISTEN_MESSAGE_WAITING_INDICATOR, mwi ? 1 : 0, 0, null)
-                    .sendToTarget();
+            send(LISTEN_MESSAGE_WAITING_INDICATOR, mwi ? 1 : 0, 0, null);
         }
 
         public void onCallForwardingIndicatorChanged(boolean cfi) {
-            Message.obtain(mHandler, LISTEN_CALL_FORWARDING_INDICATOR, cfi ? 1 : 0, 0, null)
-                    .sendToTarget();
+            send(LISTEN_CALL_FORWARDING_INDICATOR, cfi ? 1 : 0, 0, null);
         }
 
         public void onCellLocationChanged(Bundle bundle) {
             CellLocation location = CellLocation.newFromBundle(bundle);
-            Message.obtain(mHandler, LISTEN_CELL_LOCATION, 0, 0, location).sendToTarget();
+            send(LISTEN_CELL_LOCATION, 0, 0, location);
         }
 
         public void onCallStateChanged(int state, String incomingNumber) {
-            Message.obtain(mHandler, LISTEN_CALL_STATE, state, 0, incomingNumber).sendToTarget();
+            send(LISTEN_CALL_STATE, state, 0, incomingNumber);
         }
 
         public void onDataConnectionStateChanged(int state, int networkType) {
-            Message.obtain(mHandler, LISTEN_DATA_CONNECTION_STATE, state, networkType).
-                    sendToTarget();
+            send(LISTEN_DATA_CONNECTION_STATE, state, networkType, null);
         }
 
         public void onDataActivity(int direction) {
-            Message.obtain(mHandler, LISTEN_DATA_ACTIVITY, direction, 0, null).sendToTarget();
+            send(LISTEN_DATA_ACTIVITY, direction, 0, null);
         }
 
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-            Message.obtain(mHandler, LISTEN_SIGNAL_STRENGTHS, 0, 0, signalStrength).sendToTarget();
+            send(LISTEN_SIGNAL_STRENGTHS, 0, 0, signalStrength);
         }
 
         public void onOtaspChanged(int otaspMode) {
-            Message.obtain(mHandler, LISTEN_OTASP_CHANGED, otaspMode, 0).sendToTarget();
+            send(LISTEN_OTASP_CHANGED, otaspMode, 0, null);
         }
 
         public void onCellInfoChanged(List<CellInfo> cellInfo) {
-            Message.obtain(mHandler, LISTEN_CELL_INFO, 0, 0, cellInfo).sendToTarget();
+            send(LISTEN_CELL_INFO, 0, 0, cellInfo);
         }
 
         public void onPreciseCallStateChanged(PreciseCallState callState) {
-            Message.obtain(mHandler, LISTEN_PRECISE_CALL_STATE, 0, 0, callState).sendToTarget();
+            send(LISTEN_PRECISE_CALL_STATE, 0, 0, callState);
         }
 
         public void onPreciseDataConnectionStateChanged(
                 PreciseDataConnectionState dataConnectionState) {
-            Message.obtain(mHandler, LISTEN_PRECISE_DATA_CONNECTION_STATE, 0, 0,
-                    dataConnectionState).sendToTarget();
+            send(LISTEN_PRECISE_DATA_CONNECTION_STATE, 0, 0, dataConnectionState);
         }
 
         public void onDataConnectionRealTimeInfoChanged(
                 DataConnectionRealTimeInfo dcRtInfo) {
-            Message.obtain(mHandler, LISTEN_DATA_CONNECTION_REAL_TIME_INFO, 0, 0,
-                    dcRtInfo).sendToTarget();
+            send(LISTEN_DATA_CONNECTION_REAL_TIME_INFO, 0, 0, dcRtInfo);
         }
 
         public void onVoLteServiceStateChanged(VoLteServiceState lteState) {
-            Message.obtain(mHandler, LISTEN_VOLTE_STATE, 0, 0, lteState).sendToTarget();
+            send(LISTEN_VOLTE_STATE, 0, 0, lteState);
         }
 
         public void onOemHookRawEvent(byte[] rawData) {
-            Message.obtain(mHandler, LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData).sendToTarget();
+            send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
         }
 
         public void onCarrierNetworkChange(boolean active) {
-            Message.obtain(mHandler, LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active).sendToTarget();
+            send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
         }
-    };
+    }
+
+    IPhoneStateListener callback = new IPhoneStateListenerStub(this);
 
     private void log(String s) {
         Rlog.d(LOG_TAG, s);
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 96c6243..303746c 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -201,7 +201,7 @@
      * "14" vs (int) 14).
      * Note: This is used by {@link com.android.internal.telephony.imsphone.ImsPhoneConnection#
      *      updateWifiStateFromExtras(Bundle)} to determine whether to set the
-     * {@link android.telecom.Connection#CAPABILITY_WIFI} capability on a connection.
+     * {@link android.telecom.Connection#PROPERTY_WIFI} property on a connection.
      */
     public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
 
diff --git a/telephony/java/com/android/ims/ImsExternalCallState.java b/telephony/java/com/android/ims/ImsExternalCallState.java
index edb6bfc..71c1837 100644
--- a/telephony/java/com/android/ims/ImsExternalCallState.java
+++ b/telephony/java/com/android/ims/ImsExternalCallState.java
@@ -28,7 +28,7 @@
  */
 
 /**
- * Parcelable object to handle VICE Dialog Information
+ * Parcelable object to handle MultiEndpoint Dialog Information
  * @hide
  */
 public class ImsExternalCallState implements Parcelable {
@@ -39,19 +39,30 @@
     public static final int CALL_STATE_CONFIRMED = 1;
     public static final int CALL_STATE_TERMINATED = 2;
     // Dialog Id
-    public int mCallId;
+    private int mCallId;
     // Number
-    public Uri mAddress;
-    public boolean mIsPullable;
+    private Uri mAddress;
+    private boolean mIsPullable;
     // CALL_STATE_CONFIRMED / CALL_STATE_TERMINATED
-    public int mCallState;
+    private int mCallState;
     // ImsCallProfile#CALL_TYPE_*
-    public int mCallType;
-    public boolean mIsHeld;
+    private int mCallType;
+    private boolean mIsHeld;
 
     public ImsExternalCallState() {
     }
 
+    public ImsExternalCallState(int callId, Uri address, boolean isPullable, int callState,
+            int callType, boolean isCallheld) {
+        mCallId = callId;
+        mAddress = address;
+        mIsPullable = isPullable;
+        mCallState = callState;
+        mCallType = callType;
+        mIsHeld = isCallheld;
+        Rlog.d(TAG, "ImsExternalCallState = " + this);
+    }
+
     public ImsExternalCallState(Parcel in) {
         mCallId = in.readInt();
         ClassLoader classLoader = ImsExternalCallState.class.getClassLoader();
@@ -60,12 +71,7 @@
         mCallState = in.readInt();
         mCallType = in.readInt();
         mIsHeld = (in.readInt() != 0);
-        Rlog.d(TAG, "ImsExternalCallState const = " +
-                "callid = " + getCallId() +
-                ", address = " + getAddress() +
-                ", mCallState = " + getCallState() +
-                ", calltype = " + getCallType() +
-                ", isheld = " + isCallHeld());
+        Rlog.d(TAG, "ImsExternalCallState const = " + this);
     }
 
     @Override
@@ -81,6 +87,7 @@
         out.writeInt(mCallState);
         out.writeInt(mCallType);
         out.writeInt(mIsHeld ? 1 : 0);
+        Rlog.d(TAG, "ImsExternalCallState writeToParcel = " + out.toString());
     }
 
     public static final Parcelable.Creator<ImsExternalCallState> CREATOR =
diff --git a/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl b/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl
index 70a474e..27b8fa1 100644
--- a/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl
@@ -32,7 +32,7 @@
      *
      * @return void.
      */
-    void notifyRefreshExternalCallState(in List<ImsExternalCallState> externalCallDialogs);
+    void onImsExternalCallStateUpdate(in List<ImsExternalCallState> externalCallDialogs);
 
 }
 
diff --git a/telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl b/telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl
index 1bfb9b2..1374caa 100644
--- a/telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl
+++ b/telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl
@@ -34,5 +34,5 @@
      * Query api to get the latest Dialog Event Package information
      * Should be invoked only after setListener is done
      */
-    void requestDialogEventPackageState();
+    void requestImsExternalCallStateInfo();
 }
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index ea3b5c9..6567ea7 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -218,11 +218,4 @@
      */
     static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output";
 
-    /**
-     * For MultiEndpoint Feature
-     * If true: Dial intent is for call pull functionality
-     * if false: normal dial
-     */
-    static final String EXTRA_IS_CALL_PULL =
-            "android.telephony.extra.IS_CALL_PULL";
 }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index de7b9c2..18fd985 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -347,6 +347,15 @@
         </activity>
 
         <activity
+                android:name="SingleFrameTextureViewTestActivity"
+                android:label="TextureView/SingleFrameTextureViewTest">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="HardwareCanvasSurfaceViewActivity"
                 android:label="SurfaceView/HardwareCanvas">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/SingleFrameTextureViewTestActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/SingleFrameTextureViewTestActivity.java
new file mode 100644
index 0000000..4d3826b
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/SingleFrameTextureViewTestActivity.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2016 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.test.hwui;
+
+import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
+import static android.opengl.GLES20.glClear;
+import static android.opengl.GLES20.glClearColor;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLUtils;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+
+public class SingleFrameTextureViewTestActivity extends Activity implements SurfaceTextureListener {
+    private static final String LOG_TAG = "SingleFrameTest";
+
+    private View mPreview;
+    private TextureView mTextureView;
+    private Thread mGLThread;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TextView preview = new TextView(this);
+        preview.setText("This is a preview");
+        preview.setBackgroundColor(Color.WHITE);
+        mPreview = preview;
+        mTextureView = new TextureView(this);
+        mTextureView.setSurfaceTextureListener(this);
+
+        FrameLayout content = new FrameLayout(this);
+        content.addView(mTextureView,
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+        content.addView(mPreview,
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+
+        setContentView(content);
+    }
+
+    private void stopGlThread() {
+        if (mGLThread != null) {
+            try {
+                mGLThread.join();
+                mGLThread = null;
+            } catch (InterruptedException e) { }
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        Log.d(LOG_TAG, "onSurfaceAvailable");
+        mGLThread = new Thread() {
+            static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+            static final int EGL_OPENGL_ES2_BIT = 4;
+
+            private EGL10 mEgl;
+            private EGLDisplay mEglDisplay;
+            private EGLConfig mEglConfig;
+            private EGLContext mEglContext;
+            private EGLSurface mEglSurface;
+
+            @Override
+            public void run() {
+                initGL();
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {}
+
+                for (int i = 0; i < 2; i++) {
+                    if (i == 0) {
+                        glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+                    } else {
+                        glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+                    }
+                    glClear(GL_COLOR_BUFFER_BIT);
+                    Log.d(LOG_TAG, "eglSwapBuffers");
+                    if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+                        throw new RuntimeException("Cannot swap buffers");
+                    }
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException e) {}
+                }
+
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {}
+
+                finishGL();
+            }
+
+            private void finishGL() {
+                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+            }
+
+            private void initGL() {
+                mEgl = (EGL10) EGLContext.getEGL();
+
+                mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+                if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                    throw new RuntimeException("eglGetDisplay failed "
+                            + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+                }
+
+                int[] version = new int[2];
+                if (!mEgl.eglInitialize(mEglDisplay, version)) {
+                    throw new RuntimeException("eglInitialize failed " +
+                            GLUtils.getEGLErrorString(mEgl.eglGetError()));
+                }
+
+                mEglConfig = chooseEglConfig();
+                if (mEglConfig == null) {
+                    throw new RuntimeException("eglConfig not initialized");
+                }
+
+                mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+                mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
+
+                if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                    int error = mEgl.eglGetError();
+                    if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+                        Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                        return;
+                    }
+                    throw new RuntimeException("createWindowSurface failed "
+                            + GLUtils.getEGLErrorString(error));
+                }
+
+                if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                    throw new RuntimeException("eglMakeCurrent failed "
+                            + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+                }
+            }
+
+
+            EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+                int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+                return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+            }
+
+            private EGLConfig chooseEglConfig() {
+                int[] configsCount = new int[1];
+                EGLConfig[] configs = new EGLConfig[1];
+                int[] configSpec = getConfig();
+                if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
+                    throw new IllegalArgumentException("eglChooseConfig failed " +
+                            GLUtils.getEGLErrorString(mEgl.eglGetError()));
+                } else if (configsCount[0] > 0) {
+                    return configs[0];
+                }
+                return null;
+            }
+
+            private int[] getConfig() {
+                return new int[] {
+                        EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                        EGL10.EGL_RED_SIZE, 8,
+                        EGL10.EGL_GREEN_SIZE, 8,
+                        EGL10.EGL_BLUE_SIZE, 8,
+                        EGL10.EGL_ALPHA_SIZE, 8,
+                        EGL10.EGL_DEPTH_SIZE, 0,
+                        EGL10.EGL_STENCIL_SIZE, 0,
+                        EGL10.EGL_NONE
+                };
+            }
+        };
+        mGLThread.start();
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        Log.d(LOG_TAG, "onSurfaceTextureSizeChanged");
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        Log.d(LOG_TAG, "onSurfaceTextureDestroyed");
+        stopGlThread();
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        Log.d(LOG_TAG, "onSurfaceTextureUpdated");
+        mPreview.setVisibility(View.GONE);
+    }
+}
diff --git a/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml b/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
new file mode 100644
index 0000000..5d23d36e
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <domain-config>
+    <domain>android.com
+    </domain>
+    <domain>   developer.android.com    </domain>
+    <pin-set>
+      <pin digest="SHA-256">  7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=  </pin>
+    </pin-set>
+  </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index 10bcc18..f7066a6 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -464,4 +464,16 @@
         } catch (RuntimeException expected) {
         }
     }
+
+    public void testDomainWhitespaceTrimming() throws Exception {
+        XmlConfigSource source =
+                new XmlConfigSource(getContext(), R.xml.domain_whitespace, false);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        NetworkSecurityConfig defaultConfig = appConfig.getConfigForHostname("");
+        MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("developer.android.com"));
+        MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("android.com"));
+        SSLContext context = TestUtils.getSSLContext(source);
+        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+    }
 }
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index a848346..75e837b 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -9,17 +9,37 @@
 from fontTools import ttLib
 
 LANG_TO_SCRIPT = {
+    'as': 'Beng',
+    'bn': 'Beng',
+    'cy': 'Latn',
+    'da': 'Latn',
     'de': 'Latn',
     'en': 'Latn',
     'es': 'Latn',
+    'et': 'Latn',
     'eu': 'Latn',
-    'ja': 'Jpan',
-    'ko': 'Kore',
+    'fr': 'Latn',
+    'ga': 'Latn',
+    'gu': 'Gujr',
+    'hi': 'Deva',
+    'hr': 'Latn',
     'hu': 'Latn',
     'hy': 'Armn',
+    'ja': 'Jpan',
+    'kn': 'Knda',
+    'ko': 'Kore',
+    'ml': 'Mlym',
+    'mn': 'Cyrl',
+    'mr': 'Deva',
     'nb': 'Latn',
     'nn': 'Latn',
+    'or': 'Orya',
+    'pa': 'Guru',
     'pt': 'Latn',
+    'sl': 'Latn',
+    'ta': 'Taml',
+    'te': 'Telu',
+    'tk': 'Latn',
 }
 
 def lang_to_script(lang_code):
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index 985dd5a..6c775b9 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -21,6 +21,7 @@
 import com.android.ide.common.rendering.api.DensityBasedResourceValue;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.BridgeConstants;
@@ -84,7 +85,7 @@
         return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile);
     }
 
-    private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+    private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id,
             boolean[] platformResFlag_out) {
         // first get the String related to this id in the framework
         Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
@@ -97,11 +98,7 @@
 
         if (resourceInfo != null) {
             platformResFlag_out[0] = true;
-            String attributeName = resourceInfo.getSecond();
-
-            return Pair.of(attributeName,
-                    resources.mContext.getRenderResources().getFrameworkResource(
-                            resourceInfo.getFirst(), attributeName));
+            return resourceInfo;
         }
 
         // didn't find a match in the framework? look in the project.
@@ -110,13 +107,24 @@
 
             if (resourceInfo != null) {
                 platformResFlag_out[0] = false;
-                String attributeName = resourceInfo.getSecond();
-
-                return Pair.of(attributeName,
-                        resources.mContext.getRenderResources().getProjectResource(
-                                resourceInfo.getFirst(), attributeName));
+                return resourceInfo;
             }
         }
+        return null;
+    }
+
+    private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+            boolean[] platformResFlag_out) {
+        Pair<ResourceType, String> resourceInfo =
+                getResourceInfo(resources, id, platformResFlag_out);
+
+        if (resourceInfo != null) {
+            String attributeName = resourceInfo.getSecond();
+            RenderResources renderResources = resources.mContext.getRenderResources();
+            return Pair.of(attributeName, platformResFlag_out[0] ?
+                    renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
+                    renderResources.getProjectResource(resourceInfo.getFirst(), attributeName));
+        }
 
         return null;
     }
@@ -626,17 +634,57 @@
 
     @LayoutlibDelegate
     static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
-        throw new UnsupportedOperationException();
+        Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+        if (resourceInfo != null) {
+            return resourceInfo.getSecond();
+        }
+        throwException(resid, null);
+        return null;
+
     }
 
     @LayoutlibDelegate
     static String getResourceName(Resources resources, int resid) throws NotFoundException {
-        throw new UnsupportedOperationException();
+        boolean[] platformOut = new boolean[1];
+        Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+        String namespace;
+        if (resourceInfo != null) {
+            if (platformOut[0]) {
+                namespace = SdkConstants.ANDROID_NS_NAME;
+            } else {
+                namespace = resources.mContext.getPackageName();
+                namespace = namespace == null ? SdkConstants.APP_PREFIX : namespace;
+            }
+            return namespace + ':' + resourceInfo.getFirst().getName() + '/' +
+                    resourceInfo.getSecond();
+        }
+        throwException(resid, null);
+        return null;
+    }
+
+    @LayoutlibDelegate
+    static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
+        boolean[] platformOut = new boolean[1];
+        Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+        if (resourceInfo != null) {
+            if (platformOut[0]) {
+                return SdkConstants.ANDROID_NS_NAME;
+            }
+            String packageName = resources.mContext.getPackageName();
+            return packageName == null ? SdkConstants.APP_PREFIX : packageName;
+        }
+        throwException(resid, null);
+        return null;
     }
 
     @LayoutlibDelegate
     static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
-        throw new UnsupportedOperationException();
+        Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+        if (resourceInfo != null) {
+            return resourceInfo.getFirst().getName();
+        }
+        throwException(resid, null);
+        return null;
     }
 
     @LayoutlibDelegate
@@ -849,22 +897,17 @@
      * @throws NotFoundException
      */
     private static void throwException(Resources resources, int id) throws NotFoundException {
-        // first get the String related to this id in the framework
-        Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+        throwException(id, getResourceInfo(resources, id, new boolean[1]));
+    }
 
-        // if the name is unknown in the framework, get it from the custom view loader.
-        if (resourceInfo == null && resources.mLayoutlibCallback != null) {
-            resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
-        }
-
+    private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) {
         String message;
         if (resourceInfo != null) {
             message = String.format(
                     "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
                     resourceInfo.getFirst(), id, resourceInfo.getSecond());
         } else {
-            message = String.format(
-                    "Could not resolve resource value: 0x%1$X.", id);
+            message = String.format("Could not resolve resource value: 0x%1$X.", id);
         }
 
         throw new NotFoundException(message);
diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java
deleted file mode 100644
index 34ae825..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Composite;
-
-/**
- * Delegate implementing the native methods of android.graphics.AvoidXfermode
- *
- * Through the layoutlib_create tool, the original native methods of AvoidXfermode have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original AvoidXfermode class.
- *
- * Because this extends {@link Xfermode_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
- * {@link Xfermode_Delegate}.
- *
- */
-public class AvoidXfermode_Delegate extends Xfermode_Delegate {
-
-    // ---- delegate data ----
-
-    // ---- Public Helper methods ----
-
-    @Override
-    public Composite getComposite(int alpha) {
-        // FIXME
-        return null;
-    }
-
-    @Override
-    public boolean isSupported() {
-        return false;
-    }
-
-    @Override
-    public String getSupportMessage() {
-        return "Avoid Xfermodes are not supported in Layout Preview mode.";
-    }
-
-    // ---- native methods ----
-
-    @LayoutlibDelegate
-    /*package*/ static long nativeCreate(int opColor, int tolerance, int nativeMode) {
-        AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate();
-        return sManager.addNewDelegate(newDelegate);
-    }
-
-    // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index 08f0cb4..f5938cf 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -167,13 +167,13 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setFillType(long nPath, int ft) {
+    public static void native_setFillType(long nPath, int ft) {
         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
         if (pathDelegate == null) {
             return;
         }
 
-        pathDelegate.mFillType = Path.sFillTypeArray[ft];
+        pathDelegate.setFillType(Path.sFillTypeArray[ft]);
     }
 
     @LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
deleted file mode 100644
index f27144f..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Composite;
-
-/**
- * Delegate implementing the native methods of android.graphics.PixelXorXfermode
- *
- * Through the layoutlib_create tool, the original native methods of PixelXorXfermode have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PixelXorXfermode class.
- *
- * Because this extends {@link Xfermode_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
- * {@link Xfermode_Delegate}.
- *
- * @see Xfermode_Delegate
- */
-public class PixelXorXfermode_Delegate extends Xfermode_Delegate {
-    // ---- delegate data ----
-
-    // ---- Public Helper methods ----
-
-    @Override
-    public Composite getComposite(int alpha) {
-        // FIXME
-        return null;
-    }
-
-    @Override
-    public boolean isSupported() {
-        return false;
-    }
-
-    @Override
-    public String getSupportMessage() {
-        return "Pixel XOR Xfermodes are not supported in Layout Preview mode.";
-    }
-
-    // ---- native methods ----
-
-    @LayoutlibDelegate
-    /*package*/ static long nativeCreate(int opColor) {
-        PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate();
-        return sManager.addNewDelegate(newDelegate);
-    }
-
-    // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index 90b84f8..d8ff57b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -178,6 +178,7 @@
         properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin());
         properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4,
                 path.getStrokeMiterlimit());
+        properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType());
 
         return true;
     }
@@ -186,7 +187,7 @@
     static void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
             int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
             float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
-            int strokeLineJoin) {
+            int strokeLineJoin, int fillType) {
         VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
 
         path.setStrokeWidth(strokeWidth);
@@ -200,6 +201,7 @@
         path.setStrokeMiterlimit(strokeMiterLimit);
         path.setStrokeLineCap(strokeLineCap);
         path.setStrokeLineJoin(strokeLineJoin);
+        path.setFillType(fillType);
     }
 
     @LayoutlibDelegate
@@ -530,6 +532,7 @@
         private static final int STROKE_LINE_CAP_INDEX = 8;
         private static final int STROKE_LINE_JOIN_INDEX = 9;
         private static final int STROKE_MITER_LIMIT_INDEX = 10;
+        private static final int FILL_TYPE_INDEX = 11;
 
         private static final int LINECAP_BUTT = 0;
         private static final int LINECAP_ROUND = 1;
@@ -590,6 +593,8 @@
         Join mStrokeLineJoin = MITER;
         float mStrokeMiterlimit = 4;
 
+        int mFillType = 0; // WINDING(0) is the default value. See Path.FillType
+
         private VFullPath_Delegate() {
             // Empty constructor.
         }
@@ -612,6 +617,7 @@
 
             mStrokeGradient = copy.mStrokeGradient;
             mFillGradient = copy.mFillGradient;
+            mFillType = copy.mFillType;
         }
 
         private int getStrokeLineCap() {
@@ -755,6 +761,14 @@
         private void setFillGradient(long gradientPtr) {
             mFillGradient = gradientPtr;
         }
+
+        private void setFillType(int fillType) {
+            mFillType = fillType;
+        }
+
+        private int getFillType() {
+            return mFillType;
+        }
     }
 
     static class VGroup_Delegate implements VNativeObject {
@@ -1124,6 +1138,7 @@
                     assert fillPaintDelegate != null;
                     fillPaintDelegate.setColorFilter(filterPtr);
                     fillPaintDelegate.setShader(fullPath.mFillGradient);
+                    Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType);
                     Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
                             .getNativeInstance());
                 }
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 7f41348..309c1b8 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -564,7 +564,8 @@
     }
 
     @Override
-    public void requestAppKeyboardShortcuts(IResultReceiver receiver) throws RemoteException {
+    public void requestAppKeyboardShortcuts(
+            IResultReceiver receiver, int deviceId) throws RemoteException {
     }
 
     @Override
diff --git a/tools/layoutlib/bridge/src/android/view/WindowCallback.java b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
index 411417c..1ea8a9f 100644
--- a/tools/layoutlib/bridge/src/android/view/WindowCallback.java
+++ b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
@@ -141,9 +141,4 @@
     public void onActionModeFinished(ActionMode mode) {
 
     }
-
-    @Override
-    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, @Nullable Menu menu) {
-
-    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index 533a10a..a83f100 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -103,7 +103,8 @@
     }
 
     @Override
-    public void requestAppKeyboardShortcuts(IResultReceiver receiver) throws RemoteException {
+    public void requestAppKeyboardShortcuts(
+            IResultReceiver receiver, int deviceId) throws RemoteException {
     }
 
     @Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
index d417eb7..3031701 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
@@ -66,6 +66,6 @@
 
     @Override
     public void requestAppKeyboardShortcuts(
-            KeyboardShortcutsReceiver receiver) {
+            KeyboardShortcutsReceiver receiver, int deviceId) {
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
index 08a8faf..161bf41 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
@@ -27,8 +27,8 @@
 
 public class DynamicIdMap {
 
-    private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<Pair<ResourceType, String>, Integer>();
-    private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<Pair<ResourceType, String>>();
+    private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<>();
+    private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<>();
     private int mDynamicSeed;
 
     public DynamicIdMap(int seed) {
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
index 47cb042..55d6a20 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
index 5c19b08..32e6e73 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
@@ -63,6 +63,25 @@
             android:fillColor="@color/gradient"
             android:pathData="M-20,-20 l0, 10 l10, 0 l0, -10 l-10,0 "
         />
+
+        <!--
+            Draw squares with different fill types
+        -->
+        <path
+            android:fillType="evenOdd"
+            android:strokeWidth="1"
+            android:strokeColor="#AABBCC"
+            android:fillColor="#AAEFCC"
+            android:pathData="M-20,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0"
+        />
+
+        <path
+            android:fillType="nonZero"
+            android:strokeWidth="1"
+            android:strokeColor="#AABBCC"
+            android:fillColor="#AAEFCC"
+            android:pathData="M0,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0"
+        />
     </group>
 
 </vector>
\ No newline at end of file
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 48544ca..11d4c81 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -438,7 +438,8 @@
 
             try {
                 // exclude classes that are part of the default JRE (the one executing this program)
-                if (getClass().getClassLoader().loadClass(className) != null) {
+                if (className.startsWith("java.") ||
+                        getClass().getClassLoader().loadClass(className) != null) {
                     return;
                 }
             } catch (ClassNotFoundException e) {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index bd37665..1a00cc9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -168,6 +168,7 @@
         "android.content.res.Resources#getLayout",
         "android.content.res.Resources#getResourceEntryName",
         "android.content.res.Resources#getResourceName",
+        "android.content.res.Resources#getResourcePackageName",
         "android.content.res.Resources#getResourceTypeName",
         "android.content.res.Resources#getString",
         "android.content.res.Resources#getStringArray",
@@ -256,7 +257,6 @@
      */
     public final static String[] DELEGATE_CLASS_NATIVES = new String[] {
         "android.animation.PropertyValuesHolder",
-        "android.graphics.AvoidXfermode",
         "android.graphics.Bitmap",
         "android.graphics.BitmapFactory",
         "android.graphics.BitmapShader",
@@ -284,7 +284,6 @@
         "android.graphics.PathDashPathEffect",
         "android.graphics.PathEffect",
         "android.graphics.PathMeasure",
-        "android.graphics.PixelXorXfermode",
         "android.graphics.PorterDuffColorFilter",
         "android.graphics.PorterDuffXfermode",
         "android.graphics.RadialGradient",
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index a9259fa..67cf107 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -56,6 +56,11 @@
      */
     public int anqpDomainId;
 
+    /*
+     * This field is equivalent to the |flags|, rather than the |capabilities| field
+     * of the per-BSS scan results returned by WPA supplicant. See the definition of
+     * |struct wpa_bss| in wpa_supplicant/bss.h for more details.
+     */
     /**
      * Describes the authentication, key management, and encryption schemes
      * supported by the access point.
@@ -211,6 +216,10 @@
     /** {@hide} */
     public static final long FLAG_80211mc_RESPONDER               = 0x0000000000000002;
 
+    /*
+     * These flags are specific to the ScanResult class, and are not related to the |flags|
+     * field of the per-BSS scan results from WPA supplicant.
+     */
     /**
      * Defines flags; such as {@link #FLAG_PASSPOINT_NETWORK}.
      * {@hide}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 7dc8049..06dea07 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -684,6 +684,13 @@
 
     /**
      * @hide
+     * A hint about whether or not the network represented by this WifiConfiguration
+     * is metered.
+     */
+    public boolean meteredHint;
+
+    /**
+     * @hide
      * Number of time the scorer overrode a the priority based choice, when comparing two
      * WifiConfigurations, note that since comparing WifiConfiguration happens very often
      * potentially at every scan, this number might become very large, even on an idle
@@ -1302,6 +1309,7 @@
         selfAdded = false;
         didSelfAdd = false;
         ephemeral = false;
+        meteredHint = false;
         validatedInternetAccess = false;
         mIpConfiguration = new IpConfiguration();
         lastUpdateUid = -1;
@@ -1399,7 +1407,9 @@
         if (this.selfAdded) sbuf.append(" selfAdded");
         if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
         if (this.ephemeral) sbuf.append(" ephemeral");
-        if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess || this.ephemeral) {
+        if (this.meteredHint) sbuf.append(" meteredHint");
+        if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
+            || this.ephemeral || this.meteredHint) {
             sbuf.append("\n");
         }
         sbuf.append(" KeyMgmt:");
@@ -1546,18 +1556,6 @@
         return sbuf.toString();
     }
 
-    /**
-     * Construct a WifiConfiguration from a scanned network
-     * @param scannedAP the scan result used to construct the config entry
-     * TODO: figure out whether this is a useful way to construct a new entry.
-     *
-    public WifiConfiguration(ScanResult scannedAP) {
-        networkId = -1;
-        SSID = scannedAP.SSID;
-        BSSID = scannedAP.BSSID;
-    }
-    */
-
     /** {@hide} */
     public String getPrintableSsid() {
         if (SSID == null) return "";
@@ -1832,6 +1830,7 @@
             selfAdded = source.selfAdded;
             validatedInternetAccess = source.validatedInternetAccess;
             ephemeral = source.ephemeral;
+            meteredHint = source.meteredHint;
             if (source.visibility != null) {
                 visibility = new Visibility(source.visibility);
             }
@@ -1870,11 +1869,6 @@
         }
     }
 
-    /** {@hide} */
-    //public static final int NOTHING_TAG = 0;
-    /** {@hide} */
-    //public static final int SCAN_CACHE_TAG = 1;
-
     /** Implement the Parcelable interface {@hide} */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
@@ -1916,6 +1910,7 @@
         dest.writeInt(didSelfAdd ? 1 : 0);
         dest.writeInt(validatedInternetAccess ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
+        dest.writeInt(meteredHint ? 1 : 0);
         dest.writeInt(creatorUid);
         dest.writeInt(lastConnectUid);
         dest.writeInt(lastUpdateUid);
@@ -1985,6 +1980,7 @@
                 config.didSelfAdd = in.readInt() != 0;
                 config.validatedInternetAccess = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
+                config.meteredHint = in.readInt() != 0;
                 config.creatorUid = in.readInt();
                 config.lastConnectUid = in.readInt();
                 config.lastUpdateUid = in.readInt();