Merge changes Ibf5ac3d2,Ie4b216db

* changes:
  Introduce EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
  Add comments for some int fields in EditorInfo
diff --git a/Android.mk b/Android.mk
index bd04214..9ff8d41 100644
--- a/Android.mk
+++ b/Android.mk
@@ -519,6 +519,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES :=                          \
     framework-protos                                    \
     android.hardware.thermal@1.0-java-constants         \
+    android.hardware.health@1.0-java-constants          \
 
 LOCAL_MODULE := framework
 
diff --git a/api/current.txt b/api/current.txt
index b8e8dcd..873957b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3463,6 +3463,7 @@
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public void enterPictureInPictureMode();
+    method public void enterPictureInPictureMode(float);
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
@@ -3634,6 +3635,7 @@
     method public void setIntent(android.content.Intent);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setOverlayWithDecorCaptionEnabled(boolean);
+    method public void setPictureInPictureAspectRatio(float);
     method public final deprecated void setProgress(int);
     method public final deprecated void setProgressBarIndeterminate(boolean);
     method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -4952,9 +4954,9 @@
     ctor public Notification(android.os.Parcel);
     method public android.app.Notification clone();
     method public int describeContents();
+    method public java.lang.String getChannel();
     method public java.lang.String getGroup();
     method public android.graphics.drawable.Icon getLargeIcon();
-    method public java.lang.String getNotificationChannel();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
     method public void writeToParcel(android.os.Parcel, int);
@@ -34917,25 +34919,6 @@
     method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
-    method public void onNotificationRemoved(java.lang.String, long, int);
-    field public static final int REASON_APP_CANCEL = 8; // 0x8
-    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
-    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
-    field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
-    field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
-    field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
-    field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
-    field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
-    field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
-    field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
-    field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
-    field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
-    field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
-    field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
-    field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
-    field public static final int REASON_SNOOZED = 18; // 0x12
-    field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
-    field public static final int REASON_USER_STOPPED = 6; // 0x6
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
 
@@ -34960,6 +34943,7 @@
     method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public static void requestRebind(android.content.ComponentName);
@@ -34974,6 +34958,25 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final int REASON_APP_CANCEL = 8; // 0x8
+    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
+    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
+    field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
+    field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
+    field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
+    field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
+    field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
+    field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
+    field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
+    field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
+    field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
+    field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
+    field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
+    field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
+    field public static final int REASON_SNOOZED = 18; // 0x12
+    field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
+    field public static final int REASON_USER_STOPPED = 6; // 0x6
+    field public static final int REASON_USER_SWITCH = 19; // 0x13
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
     field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
     field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -36576,6 +36579,7 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
     method public void setCallDataUsage(long);
     field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
+    field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
     field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
     field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
     field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
@@ -36726,6 +36730,7 @@
     field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
     field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+    field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
     field public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 256; // 0x100
     field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
@@ -37247,8 +37252,12 @@
     method public int describeContents();
     method public boolean equals(java.lang.Object);
     method public int getAsuLevel();
+    method public int getCqi();
     method public int getDbm();
     method public int getLevel();
+    method public int getRsrp();
+    method public int getRsrq();
+    method public int getRssnr();
     method public int getTimingAdvance();
     method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
@@ -37594,6 +37603,7 @@
 
   public class TelephonyManager {
     method public boolean canChangeDtmfToneLength();
+    method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
@@ -37616,6 +37626,7 @@
     method public int getNetworkType();
     method public int getPhoneCount();
     method public int getPhoneType();
+    method public android.telephony.ServiceState getServiceState();
     method public java.lang.String getSimCountryIso();
     method public java.lang.String getSimOperator();
     method public java.lang.String getSimOperatorName();
@@ -40570,6 +40581,45 @@
     method public abstract void setValue(T, float);
   }
 
+  public final class Half {
+    method public static short abs(short);
+    method public static short ceil(short);
+    method public static short copySign(short, short);
+    method public static boolean equals(short, short);
+    method public static short floor(short);
+    method public static int getExponent(short);
+    method public static int getSign(short);
+    method public static int getSignificand(short);
+    method public static boolean greater(short, short);
+    method public static boolean greaterEquals(short, short);
+    method public static boolean isInfinite(short);
+    method public static boolean isNaN(short);
+    method public static boolean isNormalized(short);
+    method public static boolean less(short, short);
+    method public static boolean lessEquals(short, short);
+    method public static short max(short, short);
+    method public static short min(short, short);
+    method public static short round(short);
+    method public static float toFloat(short);
+    method public static java.lang.String toHexString(short);
+    method public static java.lang.String toString(short);
+    method public static short trunc(short);
+    method public static short valueOf(float);
+    field public static final short EPSILON = 5120; // 0x1400
+    field public static final short LOWEST_VALUE = -1025; // 0xfffffbff
+    field public static final int MAX_EXPONENT = 15; // 0xf
+    field public static final short MAX_VALUE = 31743; // 0x7bff
+    field public static final int MIN_EXPONENT = -14; // 0xfffffff2
+    field public static final short MIN_NORMAL = 1024; // 0x400
+    field public static final short MIN_VALUE = 1; // 0x1
+    field public static final short NEGATIVE_INFINITY = -1024; // 0xfffffc00
+    field public static final short NEGATIVE_ZERO = -32768; // 0xffff8000
+    field public static final short NaN = 32256; // 0x7e00
+    field public static final short POSITIVE_INFINITY = 31744; // 0x7c00
+    field public static final short POSITIVE_ZERO = 0; // 0x0
+    field public static final int SIZE = 16; // 0x10
+  }
+
   public abstract class IntProperty<T> extends android.util.Property {
     ctor public IntProperty(java.lang.String);
     method public final void set(T, java.lang.Integer);
diff --git a/api/system-current.txt b/api/system-current.txt
index fa88050..88f25a8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3580,6 +3580,7 @@
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public void enterPictureInPictureMode();
+    method public void enterPictureInPictureMode(float);
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
@@ -3753,6 +3754,7 @@
     method public void setIntent(android.content.Intent);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setOverlayWithDecorCaptionEnabled(boolean);
+    method public void setPictureInPictureAspectRatio(float);
     method public final deprecated void setProgress(int);
     method public final deprecated void setProgressBarIndeterminate(boolean);
     method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -5105,9 +5107,9 @@
     ctor public Notification(android.os.Parcel);
     method public android.app.Notification clone();
     method public int describeContents();
+    method public java.lang.String getChannel();
     method public java.lang.String getGroup();
     method public android.graphics.drawable.Icon getLargeIcon();
-    method public java.lang.String getNotificationChannel();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
     method public void writeToParcel(android.os.Parcel, int);
@@ -37710,25 +37712,6 @@
     method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
-    method public void onNotificationRemoved(java.lang.String, long, int);
-    field public static final int REASON_APP_CANCEL = 8; // 0x8
-    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
-    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
-    field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
-    field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
-    field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
-    field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
-    field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
-    field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
-    field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
-    field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
-    field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
-    field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
-    field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
-    field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
-    field public static final int REASON_SNOOZED = 18; // 0x12
-    field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
-    field public static final int REASON_USER_STOPPED = 6; // 0x6
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
 
@@ -37755,6 +37738,7 @@
     method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
     method public void registerAsSystemService(android.content.Context, android.content.ComponentName, int) throws android.os.RemoteException;
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
@@ -37772,6 +37756,25 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final int REASON_APP_CANCEL = 8; // 0x8
+    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
+    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
+    field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
+    field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
+    field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
+    field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
+    field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
+    field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
+    field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
+    field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
+    field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
+    field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
+    field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
+    field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
+    field public static final int REASON_SNOOZED = 18; // 0x12
+    field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
+    field public static final int REASON_USER_STOPPED = 6; // 0x6
+    field public static final int REASON_USER_SWITCH = 19; // 0x13
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
     field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
     field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -39464,6 +39467,7 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
     method public void setCallDataUsage(long);
     field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
+    field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
     field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
     field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
     field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
@@ -39738,6 +39742,7 @@
     field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
     field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+    field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
     field public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 256; // 0x100
     field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
@@ -40325,8 +40330,12 @@
     method public int describeContents();
     method public boolean equals(java.lang.Object);
     method public int getAsuLevel();
+    method public int getCqi();
     method public int getDbm();
     method public int getLevel();
+    method public int getRsrp();
+    method public int getRsrq();
+    method public int getRssnr();
     method public int getTimingAdvance();
     method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
@@ -40696,6 +40705,7 @@
     method public boolean canChangeDtmfToneLength();
     method public int checkCarrierPrivilegesForPackage(java.lang.String);
     method public int checkCarrierPrivilegesForPackageAnyPhone(java.lang.String);
+    method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
     method public void dial(java.lang.String);
     method public boolean disableDataConnectivity();
@@ -40733,6 +40743,7 @@
     method public int getNetworkType();
     method public int getPhoneCount();
     method public int getPhoneType();
+    method public android.telephony.ServiceState getServiceState();
     method public java.lang.String getSimCountryIso();
     method public java.lang.String getSimOperator();
     method public java.lang.String getSimOperatorName();
@@ -43737,6 +43748,45 @@
     method public abstract void setValue(T, float);
   }
 
+  public final class Half {
+    method public static short abs(short);
+    method public static short ceil(short);
+    method public static short copySign(short, short);
+    method public static boolean equals(short, short);
+    method public static short floor(short);
+    method public static int getExponent(short);
+    method public static int getSign(short);
+    method public static int getSignificand(short);
+    method public static boolean greater(short, short);
+    method public static boolean greaterEquals(short, short);
+    method public static boolean isInfinite(short);
+    method public static boolean isNaN(short);
+    method public static boolean isNormalized(short);
+    method public static boolean less(short, short);
+    method public static boolean lessEquals(short, short);
+    method public static short max(short, short);
+    method public static short min(short, short);
+    method public static short round(short);
+    method public static float toFloat(short);
+    method public static java.lang.String toHexString(short);
+    method public static java.lang.String toString(short);
+    method public static short trunc(short);
+    method public static short valueOf(float);
+    field public static final short EPSILON = 5120; // 0x1400
+    field public static final short LOWEST_VALUE = -1025; // 0xfffffbff
+    field public static final int MAX_EXPONENT = 15; // 0xf
+    field public static final short MAX_VALUE = 31743; // 0x7bff
+    field public static final int MIN_EXPONENT = -14; // 0xfffffff2
+    field public static final short MIN_NORMAL = 1024; // 0x400
+    field public static final short MIN_VALUE = 1; // 0x1
+    field public static final short NEGATIVE_INFINITY = -1024; // 0xfffffc00
+    field public static final short NEGATIVE_ZERO = -32768; // 0xffff8000
+    field public static final short NaN = 32256; // 0x7e00
+    field public static final short POSITIVE_INFINITY = 31744; // 0x7c00
+    field public static final short POSITIVE_ZERO = 0; // 0x0
+    field public static final int SIZE = 16; // 0x10
+  }
+
   public abstract class IntProperty<T> extends android.util.Property {
     ctor public IntProperty(java.lang.String);
     method public final void set(T, java.lang.Integer);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7599c54..5d08832 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3465,6 +3465,7 @@
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public void enterPictureInPictureMode();
+    method public void enterPictureInPictureMode(float);
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
@@ -3636,6 +3637,7 @@
     method public void setIntent(android.content.Intent);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setOverlayWithDecorCaptionEnabled(boolean);
+    method public void setPictureInPictureAspectRatio(float);
     method public final deprecated void setProgress(int);
     method public final deprecated void setProgressBarIndeterminate(boolean);
     method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -4962,9 +4964,9 @@
     ctor public Notification(android.os.Parcel);
     method public android.app.Notification clone();
     method public int describeContents();
+    method public java.lang.String getChannel();
     method public java.lang.String getGroup();
     method public android.graphics.drawable.Icon getLargeIcon();
-    method public java.lang.String getNotificationChannel();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
     method public void writeToParcel(android.os.Parcel, int);
@@ -35007,25 +35009,6 @@
     method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
-    method public void onNotificationRemoved(java.lang.String, long, int);
-    field public static final int REASON_APP_CANCEL = 8; // 0x8
-    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
-    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
-    field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
-    field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
-    field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
-    field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
-    field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
-    field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
-    field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
-    field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
-    field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
-    field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
-    field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
-    field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
-    field public static final int REASON_SNOOZED = 18; // 0x12
-    field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
-    field public static final int REASON_USER_STOPPED = 6; // 0x6
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
 
@@ -35050,6 +35033,7 @@
     method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public static void requestRebind(android.content.ComponentName);
@@ -35064,6 +35048,25 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final int REASON_APP_CANCEL = 8; // 0x8
+    field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
+    field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
+    field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
+    field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
+    field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
+    field public static final int REASON_DELEGATE_ERROR = 4; // 0x4
+    field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
+    field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc
+    field public static final int REASON_LISTENER_CANCEL = 10; // 0xa
+    field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
+    field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
+    field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
+    field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
+    field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
+    field public static final int REASON_SNOOZED = 18; // 0x12
+    field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
+    field public static final int REASON_USER_STOPPED = 6; // 0x6
+    field public static final int REASON_USER_SWITCH = 19; // 0x13
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
     field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
     field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -36666,6 +36669,7 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
     method public void setCallDataUsage(long);
     field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
+    field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
     field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
     field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
     field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
@@ -36816,6 +36820,7 @@
     field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
     field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+    field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
     field public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 256; // 0x100
     field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
@@ -37337,8 +37342,12 @@
     method public int describeContents();
     method public boolean equals(java.lang.Object);
     method public int getAsuLevel();
+    method public int getCqi();
     method public int getDbm();
     method public int getLevel();
+    method public int getRsrp();
+    method public int getRsrq();
+    method public int getRssnr();
     method public int getTimingAdvance();
     method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
@@ -37684,6 +37693,7 @@
 
   public class TelephonyManager {
     method public boolean canChangeDtmfToneLength();
+    method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
@@ -37706,6 +37716,7 @@
     method public int getNetworkType();
     method public int getPhoneCount();
     method public int getPhoneType();
+    method public android.telephony.ServiceState getServiceState();
     method public java.lang.String getSimCountryIso();
     method public java.lang.String getSimOperator();
     method public java.lang.String getSimOperatorName();
@@ -40663,6 +40674,45 @@
     method public abstract void setValue(T, float);
   }
 
+  public final class Half {
+    method public static short abs(short);
+    method public static short ceil(short);
+    method public static short copySign(short, short);
+    method public static boolean equals(short, short);
+    method public static short floor(short);
+    method public static int getExponent(short);
+    method public static int getSign(short);
+    method public static int getSignificand(short);
+    method public static boolean greater(short, short);
+    method public static boolean greaterEquals(short, short);
+    method public static boolean isInfinite(short);
+    method public static boolean isNaN(short);
+    method public static boolean isNormalized(short);
+    method public static boolean less(short, short);
+    method public static boolean lessEquals(short, short);
+    method public static short max(short, short);
+    method public static short min(short, short);
+    method public static short round(short);
+    method public static float toFloat(short);
+    method public static java.lang.String toHexString(short);
+    method public static java.lang.String toString(short);
+    method public static short trunc(short);
+    method public static short valueOf(float);
+    field public static final short EPSILON = 5120; // 0x1400
+    field public static final short LOWEST_VALUE = -1025; // 0xfffffbff
+    field public static final int MAX_EXPONENT = 15; // 0xf
+    field public static final short MAX_VALUE = 31743; // 0x7bff
+    field public static final int MIN_EXPONENT = -14; // 0xfffffff2
+    field public static final short MIN_NORMAL = 1024; // 0x400
+    field public static final short MIN_VALUE = 1; // 0x1
+    field public static final short NEGATIVE_INFINITY = -1024; // 0xfffffc00
+    field public static final short NEGATIVE_ZERO = -32768; // 0xffff8000
+    field public static final short NaN = 32256; // 0x7e00
+    field public static final short POSITIVE_INFINITY = 31744; // 0x7c00
+    field public static final short POSITIVE_ZERO = 0; // 0x0
+    field public static final int SIZE = 16; // 0x10
+  }
+
   public abstract class IntProperty<T> extends android.util.Property {
     ctor public IntProperty(java.lang.String);
     method public final void set(T, java.lang.Integer);
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 0552d34..58f5a78 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -1494,22 +1494,22 @@
         public void onFocusChange(View v, boolean hasFocus) {
             if (!hasFocus) {
                 v.setOnFocusChangeListener(null);
-                final View focused = mFocusRoot.findFocus();
-                if (focused != null) {
-                    focused.setOnFocusChangeListener(this);
-                } else {
-                    mFocusRoot.post(this);
-                }
+                mFocusRoot.post(this);
             }
         }
 
         @Override
         public void run() {
-            if (mContainer != null) {
-                mContainer.setTouchscreenBlocksFocus(true);
-            }
-            if (mToolbar != null) {
-                mToolbar.setTouchscreenBlocksFocus(true);
+            final View focused = mFocusRoot.findFocus();
+            if (focused != null) {
+                focused.setOnFocusChangeListener(this);
+            } else {
+                if (mContainer != null) {
+                    mContainer.setTouchscreenBlocksFocus(true);
+                }
+                if (mToolbar != null) {
+                    mToolbar.setTouchscreenBlocksFocus(true);
+                }
             }
         }
     }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b3e2f57..b381339 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1929,6 +1929,32 @@
     }
 
     /**
+     * Puts the activity in picture-in-picture mode with a given aspect ratio.
+     * @see android.R.attr#supportsPictureInPicture
+     *
+     * @param aspectRatio the new aspect ratio of the picture-in-picture.
+     */
+    public void enterPictureInPictureMode(float aspectRatio) {
+        try {
+            ActivityManagerNative.getDefault().enterPictureInPictureModeWithAspectRatio(mToken,
+                    aspectRatio);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Updates the aspect ratio of the current picture-in-picture activity.
+     *
+     * @param aspectRatio the new aspect ratio of the picture-in-picture.
+     */
+    public void setPictureInPictureAspectRatio(float aspectRatio) {
+        try {
+            ActivityManagerNative.getDefault().setPictureInPictureAspectRatio(mToken, aspectRatio);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Called by the system when the device configuration changes while your
      * activity is running.  Note that this will <em>only</em> be called if
      * you have selected configurations you would like to handle with the
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index df970a4..4a39e4a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -102,7 +102,7 @@
         }
 
         @Override
-        public void onUidGone(int uid) {
+        public void onUidGone(int uid, boolean disabled) {
             mListener.onUidImportance(uid, RunningAppProcessInfo.IMPORTANCE_GONE);
         }
 
@@ -111,7 +111,7 @@
         }
 
         @Override
-        public void onUidIdle(int uid) {
+        public void onUidIdle(int uid, boolean disabled) {
         }
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3414f4..78204a0 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5839,6 +5839,7 @@
                     case BlockedNumberContract.AUTHORITY:
                     case CalendarContract.AUTHORITY:
                     case Downloads.Impl.AUTHORITY:
+                    case "telephony":
                         Binder.allowBlocking(provider.asBinder());
                 }
             }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 80267205..9fe34c0 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -499,7 +499,7 @@
     boolean supportsLocalVoiceInteraction() = 365;
     void notifyPinnedStackAnimationEnded() = 366;
     void removeStack(int stackId) = 367;
-    void setLenientBackgroundCheck(boolean enabled) = 368;
+    void makePackageIdle(String packageName, int userId) = 368;
     int getMemoryTrimLevel() = 369;
     /**
      * Resizes the pinned stack.
@@ -562,7 +562,9 @@
     boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId) = 401;
     void unregisterTaskStackListener(ITaskStackListener listener) = 402;
     void moveStackToDisplay(int stackId, int displayId) = 403;
+    void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio) = 404;
+    void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio) = 405;
 
     // Please keep these transaction codes the same -- they are also
     // sent by C++ code. when a new method is added, use the next available transaction id.
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index cae54b6..848464c 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -53,6 +53,16 @@
     int getNightMode();
 
     /**
+     * Sets whith theme overlays to use within /vendor/overlay.
+     */
+    void setTheme(String theme);
+
+    /**
+     * Gets whith theme overlays to use within /vendor/overlay.
+     */
+    String getTheme();
+
+    /**
      * Tells if UI mode is locked or not.
      */
     boolean isUiModeLocked();
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index fa8d0c9..64cb9b1 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -26,7 +26,7 @@
     /**
      * Report that there are no longer any processes running for a uid.
      */
-    void onUidGone(int uid);
+    void onUidGone(int uid, boolean disabled);
 
     /**
      * Report that a uid is now active (no longer idle).
@@ -37,5 +37,5 @@
      * Report that a uid is idle -- it has either been running in the background for
      * a sufficient period of time, or all of its processes have gone away.
      */
-    void onUidIdle(int uid);
+    void onUidIdle(int uid, boolean disabled);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index bdb2685..261fde2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2239,7 +2239,7 @@
     /**
      * Returns the id of the channel this notification posts to.
      */
-    public String getNotificationChannel() {
+    public String getChannel() {
         return mChannelId;
     }
 
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 2d15beb..45831a3 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -826,7 +826,8 @@
 
             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                 ResourcesKey key = mResourceImpls.keyAt(i);
-                ResourcesImpl r = mResourceImpls.valueAt(i).get();
+                WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+                ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
                 if (r != null) {
                     if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
                             + r + " config to: " + config);
@@ -890,8 +891,9 @@
 
             final int implCount = mResourceImpls.size();
             for (int i = 0; i < implCount; i++) {
-                final ResourcesImpl impl = mResourceImpls.valueAt(i).get();
                 final ResourcesKey key = mResourceImpls.keyAt(i);
+                final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+                final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
                 if (impl != null && key.mResDir.equals(assetPath)) {
                     if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
                         final int newLibAssetCount = 1 +
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0046b0e..2e21729 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -241,6 +241,35 @@
     }
 
     /**
+     * Sets the vendor theme overlay property, then triggers a reboot.
+     * @hide
+     */
+    public void setTheme(String theme) {
+        if (mService != null) {
+            try {
+                mService.setTheme(theme);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Gets the vendor theme overlay property.
+     * @hide
+     */
+    public String getTheme() {
+        if (mService != null) {
+            try {
+                return mService.getTheme();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the currently configured night mode.
      * <p>
      * May be one of:
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index bb344a6..b0e27a4 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -604,10 +604,6 @@
      */
     public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
         if (!getLeAccess()) return null;
-        if (!isMultipleAdvertisementSupported() && !isPeripheralModeSupported()) {
-            Log.e(TAG, "Bluetooth LE advertising not supported");
-            return null;
-        }
         synchronized(mLock) {
             if (sBluetoothLeAdvertiser == null) {
                 sBluetoothLeAdvertiser = new BluetoothLeAdvertiser(mManagerService);
@@ -1357,24 +1353,6 @@
     }
 
     /**
-     * Returns whether peripheral mode is supported.
-     *
-     * @hide
-     */
-    public boolean isPeripheralModeSupported() {
-        if (getState() != STATE_ON) return false;
-        try {
-            mServiceLock.readLock().lock();
-            if (mService != null) return mService.isPeripheralModeSupported();
-        } catch (RemoteException e) {
-            Log.e(TAG, "failed to get peripheral mode capability: ", e);
-        } finally {
-            mServiceLock.readLock().unlock();
-        }
-        return false;
-    }
-
-    /**
      * Return true if offloaded filters are supported
      *
      * @return true if chipset supports on-chip filtering
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 96a1ae8..7c5458b 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -100,7 +100,6 @@
     boolean factoryReset();
 
     boolean isMultiAdvertisementSupported();
-    boolean isPeripheralModeSupported();
     boolean isOffloadedFilteringSupported();
     boolean isOffloadedScanBatchingSupported();
     boolean isActivityAndEnergyReportingSupported();
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
index f53ca94..9e87230 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -30,10 +30,24 @@
  * @hide
  */
 public class OobData implements Parcelable {
+    private byte[] leBluetoothDeviceAddress;
     private byte[] securityManagerTk;
     private byte[] leSecureConnectionsConfirmation;
     private byte[] leSecureConnectionsRandom;
 
+    public byte[] getLeBluetoothDeviceAddress() {
+        return leBluetoothDeviceAddress;
+    }
+
+    /**
+     * Sets the LE Bluetooth Device Address value to be used during LE pairing.
+     * The value shall be 7 bytes. Please see Bluetooth CSSv6, Part A 1.16 for
+     * a detailed description.
+     */
+    public void setLeBluetoothDeviceAddress(byte[] leBluetoothDeviceAddress) {
+        this.leBluetoothDeviceAddress = leBluetoothDeviceAddress;
+    }
+
     public byte[] getSecurityManagerTk() {
         return securityManagerTk;
     }
@@ -66,6 +80,7 @@
     public OobData() { }
 
     private OobData(Parcel in) {
+        leBluetoothDeviceAddress = in.createByteArray();
         securityManagerTk = in.createByteArray();
         leSecureConnectionsConfirmation = in.createByteArray();
         leSecureConnectionsRandom = in.createByteArray();
@@ -77,6 +92,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(leBluetoothDeviceAddress);
         out.writeByteArray(securityManagerTk);
         out.writeByteArray(leSecureConnectionsConfirmation);
         out.writeByteArray(leSecureConnectionsRandom);
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 26f2dea..5d27662 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -111,12 +111,6 @@
             if (callback == null) {
                 throw new IllegalArgumentException("callback cannot be null");
             }
-            if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&
-                    !mBluetoothAdapter.isPeripheralModeSupported()) {
-                postStartFailure(callback,
-                        AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
-                return;
-            }
             boolean isConnectable = settings.isConnectable();
             if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||
                     totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {
@@ -236,9 +230,9 @@
         private final AdvertiseSettings mSettings;
         private final IBluetoothGatt mBluetoothGatt;
 
-        // mAdvertiserId 0: not registered
-        // -1: advertise stopped or registration timeout
-        // >0: registered and advertising started
+        // mAdvertiserId -1: not registered
+        // -2: advertise stopped or registration timeout
+        // >=0: registered and advertising started
         private int mAdvertiserId;
         private boolean mIsAdvertising = false;
 
@@ -251,12 +245,12 @@
             mScanResponse = scanResponse;
             mSettings = settings;
             mBluetoothGatt = bluetoothGatt;
-            mAdvertiserId = 0;
+            mAdvertiserId = -1;
         }
 
         public void startRegisteration() {
             synchronized (this) {
-                if (mAdvertiserId == -1) return;
+                if (mAdvertiserId == -2) return;
 
                 try {
                     mBluetoothGatt.registerAdvertiser(this);
@@ -264,13 +258,13 @@
                 } catch (InterruptedException | RemoteException e) {
                     Log.e(TAG, "Failed to start registeration", e);
                 }
-                if (mAdvertiserId > 0 && mIsAdvertising) {
+                if (mAdvertiserId >= 0 && mIsAdvertising) {
                     mLeAdvertisers.put(mAdvertiseCallback, this);
-                } else if (mAdvertiserId <= 0) {
+                } else if (mAdvertiserId < 0) {
 
                     // Registration timeout, reset mClientIf to -1 so no subsequent operations can
                     // proceed.
-                    if (mAdvertiserId == 0) mAdvertiserId = -1;
+                    if (mAdvertiserId == 0) mAdvertiserId = -2;
                     // Post internal error if registration failed.
                     postStartFailure(mAdvertiseCallback,
                             AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
@@ -278,7 +272,7 @@
                     // Unregister application if it's already registered but advertise failed.
                     try {
                         mBluetoothGatt.unregisterAdvertiser(mAdvertiserId);
-                        mAdvertiserId = -1;
+                        mAdvertiserId = -2;
                     } catch (RemoteException e) {
                         Log.e(TAG, "remote exception when unregistering", e);
                     }
@@ -312,7 +306,7 @@
             synchronized (this) {
                 if (status == BluetoothGatt.GATT_SUCCESS) {
                     try {
-                        if (mAdvertiserId == -1) {
+                        if (mAdvertiserId == -2) {
                             // Registration succeeds after timeout, unregister advertiser.
                             mBluetoothGatt.unregisterAdvertiser(advertiserId);
                         } else {
@@ -326,7 +320,7 @@
                     }
                 }
                 // Registration failed.
-                mAdvertiserId = -1;
+                mAdvertiserId = -2;
                 notifyAll();
             }
         }
@@ -348,7 +342,7 @@
                     // unregister advertiser for stop.
                     try {
                         mBluetoothGatt.unregisterAdvertiser(mAdvertiserId);
-                        mAdvertiserId = -1;
+                        mAdvertiserId = -2;
                         mIsAdvertising = false;
                         mLeAdvertisers.remove(mAdvertiseCallback);
                     } catch (RemoteException e) {
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 1e6424e..49b5853 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -42,6 +42,7 @@
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -463,6 +464,23 @@
             }
         }
 
+        @Override
+        public boolean refresh(String callingPkg, Uri uri, Bundle args,
+                ICancellationSignal cancellationSignal) throws RemoteException {
+            validateIncomingUri(uri);
+            uri = getUriWithoutUserId(uri);
+            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+            final String original = setCallingPackage(callingPkg);
+            try {
+                return ContentProvider.this.refresh(uri, args,
+                        CancellationSignal.fromTransport(cancellationSignal));
+            } finally {
+                setCallingPackage(original);
+            }
+        }
+
         private void enforceFilePermission(String callingPkg, Uri uri, String mode,
                 IBinder callerToken) throws FileNotFoundException, SecurityException {
             if (mode != null && mode.indexOf('w') != -1) {
@@ -1093,6 +1111,34 @@
     }
 
     /**
+     * Implement this to support refresh of content identified by {@code uri}. By default, this
+     * method returns false; providers who wish to implement this should return true to signal the
+     * client that the provider has tried refreshing with its own implementation.
+     * <p>
+     * This allows clients to request an explicit refresh of content identified by {@code uri}.
+     * <p>
+     * Client code should only invoke this method when there is a strong indication (such as a user
+     * initiated pull to refresh gesture) that the content is stale.
+     * <p>
+     * Remember to send {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)}
+     * notifications when content changes.
+     *
+     * @param uri The Uri identifying the data to refresh.
+     * @param args Additional options from the client. The definitions of these are specific to the
+     *            content provider being called.
+     * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if
+     *            none. For example, if you called refresh on a particular uri, you should call
+     *            {@link CancellationSignal#throwIfCanceled()} to check whether the client has
+     *            canceled the refresh request.
+     * @return true if the provider actually tried refreshing.
+     * @hide
+     */
+    public boolean refresh(Uri uri, @Nullable Bundle args,
+            @Nullable CancellationSignal cancellationSignal) {
+        return false;
+    }
+
+    /**
      * @hide
      * Implementation when a caller has performed an insert on the content
      * provider, but that call has been rejected for the operation given
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 9221fbb..857610d 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -234,6 +234,30 @@
         }
     }
 
+    /** @hide */
+    public boolean refresh(Uri url, @Nullable Bundle args,
+            @Nullable CancellationSignal cancellationSignal) throws RemoteException {
+        Preconditions.checkNotNull(url, "url");
+
+        beforeRemote();
+        try {
+            ICancellationSignal remoteCancellationSignal = null;
+            if (cancellationSignal != null) {
+                cancellationSignal.throwIfCanceled();
+                remoteCancellationSignal = mContentProvider.createCancellationSignal();
+                cancellationSignal.setRemote(remoteCancellationSignal);
+            }
+            return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        } finally {
+            afterRemote();
+        }
+    }
+
     /** See {@link ContentProvider#insert ContentProvider.insert} */
     public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
             throws RemoteException {
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 439e1ff..eadc013 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -355,6 +355,20 @@
                     Uri.writeToParcel(reply, out);
                     return true;
                 }
+
+                case REFRESH_TRANSACTION: {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+                    Bundle args = data.readBundle();
+                    ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+                            data.readStrongBinder());
+
+                    boolean out = refresh(callingPkg, url, args, signal);
+                    reply.writeNoException();
+                    reply.writeInt(out ? 0 : -1);
+                    return true;
+                }
             }
         } catch (Exception e) {
             DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -761,5 +775,28 @@
         }
     }
 
+    public boolean refresh(String callingPkg, Uri url, Bundle args, ICancellationSignal signal)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        try {
+            data.writeInterfaceToken(IContentProvider.descriptor);
+
+            data.writeString(callingPkg);
+            url.writeToParcel(data, 0);
+            data.writeBundle(args);
+            data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+
+            mRemote.transact(IContentProvider.REFRESH_TRANSACTION, data, reply, 0);
+
+            DatabaseUtils.readExceptionFromParcel(reply);
+            int success = reply.readInt();
+            return (success == 0);
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index daa1b93..705c091 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -194,6 +194,15 @@
     public static final String EXTRA_SIZE = "android.content.extra.SIZE";
 
     /**
+     * An extra boolean describing whether a particular provider supports refresh
+     * or not. If a provider supports refresh, it should include this key in its
+     * returned Cursor as part of its query call.
+     *
+     * @hide
+     */
+    public static final String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
+
+    /**
      * This is the Android platform's base MIME type for a content: URI
      * containing a Cursor of a single item.  Applications should use this
      * as the base type along with their own sub-type of their content: URIs
@@ -664,6 +673,54 @@
     }
 
     /**
+     * Implement this to support refresh of content identified by {@code uri}. By default, this
+     * method returns false; providers who wish to implement this should return true to signal the
+     * client that the provider has tried refreshing with its own implementation.
+     * <p>
+     * This allows clients to request an explicit refresh of content identified by {@code uri}.
+     * <p>
+     * Client code should only invoke this method when there is a strong indication (such as a user
+     * initiated pull to refresh gesture) that the content is stale.
+     * <p>
+     * Remember to send {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)}
+     * notifications when content changes.
+     *
+     * @param uri The Uri identifying the data to refresh.
+     * @param args Additional options from the client. The definitions of these are specific to the
+     *            content provider being called.
+     * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if
+     *            none. For example, if you called refresh on a particular uri, you should call
+     *            {@link CancellationSignal#throwIfCanceled()} to check whether the client has
+     *            canceled the refresh request.
+     * @return true if the provider actually tried refreshing.
+     * @hide
+     */
+    public final boolean refresh(@NonNull Uri url, @Nullable Bundle args,
+            @Nullable CancellationSignal cancellationSignal) {
+        Preconditions.checkNotNull(url, "url");
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return false;
+        }
+
+        try {
+            ICancellationSignal remoteCancellationSignal = null;
+            if (cancellationSignal != null) {
+                cancellationSignal.throwIfCanceled();
+                remoteCancellationSignal = provider.createCancellationSignal();
+                cancellationSignal.setRemote(remoteCancellationSignal);
+            }
+            return provider.refresh(mPackageName, url, args, remoteCancellationSignal);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return false;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
      * Open a stream on to the content associated with a content URI.  If there
      * is no data associated with the URI, FileNotFoundException is thrown.
      *
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 4afe38b..ee8a22f 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -65,6 +65,9 @@
     public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
     public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;
 
+    public boolean refresh(String callingPkg, Uri url, @Nullable Bundle args,
+            ICancellationSignal cancellationSignal) throws RemoteException;
+
     // Data interchange.
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
     public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
@@ -88,4 +91,5 @@
     static final int CREATE_CANCELATION_SIGNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 23;
     static final int CANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 24;
     static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25;
+    static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26;
 }
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 1f013ae..2aa7ac6 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -162,6 +162,12 @@
     public abstract boolean isPackageDataProtected(int userId, String packageName);
 
     /**
+     * Returns {@code true} if a given package is installed as ephemeral. Otherwise, returns
+     * {@code false}.
+     */
+    public abstract boolean isPackageEphemeral(int userId, String packageName);
+
+    /**
      * Gets whether the package was ever launched.
      * @param packageName The package name.
      * @param userId The user for which to check.
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index a93870e..f7c4d59 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -193,7 +193,11 @@
  * The following list includes descriptions for the different attributes within a static shortcut:
  * <dl>
  *   <dt>{@code android:shortcutId}</dt>
- *   <dd>Mandatory shortcut ID</dd>
+ *   <dd>Mandatory shortcut ID.
+ *   <p>
+ *   This must be a string literal.
+ *   A resource string, such as <code>@string/foo</code>, cannot be used.
+ *   </dd>
  *
  *   <dt>{@code android:enabled}</dt>
  *   <dd>Default is {@code true}.  Can be set to {@code false} in order
@@ -206,15 +210,24 @@
  *
  *   <dt>{@code android:shortcutShortLabel}</dt>
  *   <dd>Mandatory shortcut short label.
- *   See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.</dd>
+ *   See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.
+ *   <p>
+ *   This must be a resource string, such as <code>@string/shortcut_label</code>.
+ *   </dd>
  *
  *   <dt>{@code android:shortcutLongLabel}</dt>
  *   <dd>Shortcut long label.
- *   See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.</dd>
+ *   See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.
+ *   <p>
+ *   This must be a resource string, such as <code>@string/shortcut_long_label</code>.
+ *   </dd>
  *
  *   <dt>{@code android:shortcutDisabledMessage}</dt>
  *   <dd>When {@code android:enabled} is set to
- *   {@code false}, this attribute is used to display a custom disabled message.</dd>
+ *   {@code false}, this attribute is used to display a custom disabled message.
+ *   <p>
+ *   This must be a resource string, such as <code>@string/shortcut_disabled_message</code>.
+ *   </dd>
  *
  *   <dt>{@code intent}</dt>
  *   <dd>Intent to launch when the user selects the shortcut.
diff --git a/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl b/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl
index c99cb0c..a7dd035 100644
--- a/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl
+++ b/core/java/android/hardware/location/IFusedLocationHardwareSink.aidl
@@ -24,7 +24,7 @@
  *
  * @hide
  */
-interface IFusedLocationHardwareSink {
+oneway interface IFusedLocationHardwareSink {
     /**
      * Event generated when a batch of location information is available.
      *
@@ -50,4 +50,4 @@
      * changes (location is successful/unsuccessful).
      */
     void onStatusChanged(int status) = 3;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 252385f..263750a 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.hardware.health.V1_0.Constants;
 import com.android.internal.app.IBatteryStats;
 
 /**
@@ -118,20 +119,20 @@
      public static final String EXTRA_CHARGE_COUNTER = "charge_counter";
 
     // values for "status" field in the ACTION_BATTERY_CHANGED Intent
-    public static final int BATTERY_STATUS_UNKNOWN = 1;
-    public static final int BATTERY_STATUS_CHARGING = 2;
-    public static final int BATTERY_STATUS_DISCHARGING = 3;
-    public static final int BATTERY_STATUS_NOT_CHARGING = 4;
-    public static final int BATTERY_STATUS_FULL = 5;
+    public static final int BATTERY_STATUS_UNKNOWN = Constants.BATTERY_STATUS_UNKNOWN;
+    public static final int BATTERY_STATUS_CHARGING = Constants.BATTERY_STATUS_CHARGING;
+    public static final int BATTERY_STATUS_DISCHARGING = Constants.BATTERY_STATUS_DISCHARGING;
+    public static final int BATTERY_STATUS_NOT_CHARGING = Constants.BATTERY_STATUS_NOT_CHARGING;
+    public static final int BATTERY_STATUS_FULL = Constants.BATTERY_STATUS_FULL;
 
     // values for "health" field in the ACTION_BATTERY_CHANGED Intent
-    public static final int BATTERY_HEALTH_UNKNOWN = 1;
-    public static final int BATTERY_HEALTH_GOOD = 2;
-    public static final int BATTERY_HEALTH_OVERHEAT = 3;
-    public static final int BATTERY_HEALTH_DEAD = 4;
-    public static final int BATTERY_HEALTH_OVER_VOLTAGE = 5;
-    public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6;
-    public static final int BATTERY_HEALTH_COLD = 7;
+    public static final int BATTERY_HEALTH_UNKNOWN = Constants.BATTERY_HEALTH_UNKNOWN;
+    public static final int BATTERY_HEALTH_GOOD = Constants.BATTERY_HEALTH_GOOD;
+    public static final int BATTERY_HEALTH_OVERHEAT = Constants.BATTERY_HEALTH_OVERHEAT;
+    public static final int BATTERY_HEALTH_DEAD = Constants.BATTERY_HEALTH_DEAD;
+    public static final int BATTERY_HEALTH_OVER_VOLTAGE = Constants.BATTERY_HEALTH_OVER_VOLTAGE;
+    public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = Constants.BATTERY_HEALTH_UNSPECIFIED_FAILURE;
+    public static final int BATTERY_HEALTH_COLD = Constants.BATTERY_HEALTH_COLD;
 
     // values of the "plugged" field in the ACTION_BATTERY_CHANGED intent.
     // These must be powers of 2.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07538dd..79b42ba 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8629,13 +8629,6 @@
         public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
 
         /**
-         * @hide
-         * If not 0, the activity manager will implement a looser version of background
-         * check that is more compatible with existing apps.
-         */
-        public static final String LENIENT_BACKGROUND_CHECK = "lenient_background_check";
-
-        /**
          * Use Dock audio output for media:
          *      0 = disabled
          *      1 = enabled
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 8c35901..f639c0d 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -28,7 +28,7 @@
     void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder,
             in NotificationRankingUpdate update);
     void onNotificationRemoved(in IStatusBarNotificationHolder notificationHolder,
-            in NotificationRankingUpdate update);
+            in NotificationRankingUpdate update, int reason);
     void onNotificationRankingUpdate(in NotificationRankingUpdate update);
     void onListenerHintsChanged(int hints);
     void onInterruptionFilterChanged(int interruptionFilter);
@@ -38,5 +38,4 @@
     void onNotificationVisibilityChanged(String key, long time, boolean visible);
     void onNotificationClick(String key, long time);
     void onNotificationActionClick(String key, long time, int actionIndex);
-    void onNotificationRemovedReason(String key, long time, int reason);
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index fdf1b26..4e00c64 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -42,60 +42,6 @@
     public static final String SERVICE_INTERFACE
             = "android.service.notification.NotificationAssistantService";
 
-    /** Notification was canceled by the status bar reporting a click. */
-    public static final int REASON_DELEGATE_CLICK = 1;
-
-    /** Notification was canceled by the status bar reporting a user dismissal. */
-    public static final int REASON_DELEGATE_CANCEL = 2;
-
-    /** Notification was canceled by the status bar reporting a user dismiss all. */
-    public static final int REASON_DELEGATE_CANCEL_ALL = 3;
-
-    /** Notification was canceled by the status bar reporting an inflation error. */
-    public static final int REASON_DELEGATE_ERROR = 4;
-
-    /** Notification was canceled by the package manager modifying the package. */
-    public static final int REASON_PACKAGE_CHANGED = 5;
-
-    /** Notification was canceled by the owning user context being stopped. */
-    public static final int REASON_USER_STOPPED = 6;
-
-    /** Notification was canceled by the user banning the package. */
-    public static final int REASON_PACKAGE_BANNED = 7;
-
-    /** Notification was canceled by the app canceling this specific notification. */
-    public static final int REASON_APP_CANCEL = 8;
-
-    /** Notification was canceled by the app cancelling all its notifications. */
-    public static final int REASON_APP_CANCEL_ALL = 9;
-
-    /** Notification was canceled by a listener reporting a user dismissal. */
-    public static final int REASON_LISTENER_CANCEL = 10;
-
-    /** Notification was canceled by a listener reporting a user dismiss all. */
-    public static final int REASON_LISTENER_CANCEL_ALL = 11;
-
-    /** Notification was canceled because it was a member of a canceled group. */
-    public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
-
-    /** Notification was canceled because it was an invisible member of a group. */
-    public static final int REASON_GROUP_OPTIMIZATION = 13;
-
-    /** Notification was canceled by the device administrator suspending the package. */
-    public static final int REASON_PACKAGE_SUSPENDED = 14;
-
-    /** Notification was canceled by the owning managed profile being turned off. */
-    public static final int REASON_PROFILE_TURNED_OFF = 15;
-
-    /** Autobundled summary notification was canceled because its group was unbundled */
-    public static final int REASON_UNAUTOBUNDLED = 16;
-
-    /** Notification was canceled by the user banning the channel. */
-    public static final int REASON_CHANNEL_BANNED = 17;
-
-    /** Notification was snoozed. */
-    public static final int REASON_SNOOZED = 18;
-
     private Handler mHandler;
 
     @Override
@@ -124,17 +70,6 @@
           int importance, boolean user);
 
     /**
-     * A notification was removed.
-
-     * @param key the notification key
-     * @param time milliseconds since midnight, January 1, 1970 UTC.
-     * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
-     */
-    public void onNotificationRemoved(String key, long time, int reason) {
-        // Do nothing, Override this to collect dismissal statistics
-    }
-
-    /**
      * Updates a notification.  N.B. this won’t cause
      * an existing notification to alert, but might allow a future update to
      * this notification to alert.
@@ -185,21 +120,10 @@
             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
                     args).sendToTarget();
         }
-
-        @Override
-        public void onNotificationRemovedReason(String key, long time, int reason) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = key;
-            args.arg2 = time;
-            args.argi1 = reason;
-            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED_REASON,
-                    args).sendToTarget();
-        }
     }
 
     private final class MyHandler extends Handler {
         public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
-        public static final int MSG_ON_NOTIFICATION_REMOVED_REASON = 5;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -219,15 +143,6 @@
                         adjustNotification(adjustment);
                     }
                 } break;
-
-                case MSG_ON_NOTIFICATION_REMOVED_REASON: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    final String key = (String) args.arg1;
-                    final long time = (long) args.arg2;
-                    final int reason = args.argi1;
-                    args.recycle();
-                    onNotificationRemoved(key, time, reason);
-                } break;
             }
         }
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index e606ebf..45011eb 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -77,6 +77,7 @@
  * </p>
  */
 public abstract class NotificationListenerService extends Service {
+
     // TAG = "NotificationListenerService[MySubclass]"
     private final String TAG = NotificationListenerService.class.getSimpleName()
             + "[" + getClass().getSimpleName() + "]";
@@ -146,6 +147,48 @@
     public static final int SUPPRESSED_EFFECT_SCREEN_ON =
             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 
+
+    // Notification cancellation reasons
+
+    /** Notification was canceled by the status bar reporting a click. */
+    public static final int REASON_DELEGATE_CLICK = 1;
+    /** Notification was canceled by the status bar reporting a user dismissal. */
+    public static final int REASON_DELEGATE_CANCEL = 2;
+    /** Notification was canceled by the status bar reporting a user dismiss all. */
+    public static final int REASON_DELEGATE_CANCEL_ALL = 3;
+    /** Notification was canceled by the status bar reporting an inflation error. */
+    public static final int REASON_DELEGATE_ERROR = 4;
+    /** Notification was canceled by the package manager modifying the package. */
+    public static final int REASON_PACKAGE_CHANGED = 5;
+    /** Notification was canceled by the owning user context being stopped. */
+    public static final int REASON_USER_STOPPED = 6;
+    /** Notification was canceled by the user banning the package. */
+    public static final int REASON_PACKAGE_BANNED = 7;
+    /** Notification was canceled by the app canceling this specific notification. */
+    public static final int REASON_APP_CANCEL = 8;
+    /** Notification was canceled by the app cancelling all its notifications. */
+    public static final int REASON_APP_CANCEL_ALL = 9;
+    /** Notification was canceled by a listener reporting a user dismissal. */
+    public static final int REASON_LISTENER_CANCEL = 10;
+    /** Notification was canceled by a listener reporting a user dismiss all. */
+    public static final int REASON_LISTENER_CANCEL_ALL = 11;
+    /** Notification was canceled because it was a member of a canceled group. */
+    public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
+    /** Notification was canceled because it was an invisible member of a group. */
+    public static final int REASON_GROUP_OPTIMIZATION = 13;
+    /** Notification was canceled by the device administrator suspending the package. */
+    public static final int REASON_PACKAGE_SUSPENDED = 14;
+    /** Notification was canceled by the owning managed profile being turned off. */
+    public static final int REASON_PROFILE_TURNED_OFF = 15;
+    /** Autobundled summary notification was canceled because its group was unbundled */
+    public static final int REASON_UNAUTOBUNDLED = 16;
+    /** Notification was canceled by the user banning the channel. */
+    public static final int REASON_CHANNEL_BANNED = 17;
+    /** Notification was snoozed. */
+    public static final int REASON_SNOOZED = 18;
+    /** Notification no longer visible because of user switch */
+    public static final int REASON_USER_SWITCH = 19;
+
     /**
      * The full trim of the StatusBarNotification including all its features.
      *
@@ -282,6 +325,32 @@
         onNotificationRemoved(sbn);
     }
 
+
+    /**
+     * Implement this method to learn when notifications are removed and why.
+     * <p>
+     * This might occur because the user has dismissed the notification using system UI (or another
+     * notification listener) or because the app has withdrawn the notification.
+     * <p>
+     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
+     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
+     * fields such as {@link android.app.Notification#contentView} and
+     * {@link android.app.Notification#largeIcon}. However, all other fields on
+     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
+     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
+     *
+     ** @param sbn A data structure encapsulating at least the original information (tag and id)
+     *            and source (package name) used to post the {@link android.app.Notification} that
+     *            was just removed.
+     * @param rankingMap The current ranking map that can be used to retrieve ranking information
+     *                   for active notifications.
+     * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
+     */
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            int reason) {
+        onNotificationRemoved(sbn, rankingMap);
+    }
+
     /**
      * Implement this method to learn about when the listener is enabled and connected to
      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
@@ -927,7 +996,7 @@
 
         @Override
         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
-                NotificationRankingUpdate update) {
+                NotificationRankingUpdate update, int reason) {
             StatusBarNotification sbn;
             try {
                 sbn = sbnHolder.get();
@@ -941,6 +1010,7 @@
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = sbn;
                 args.arg2 = mRankingMap;
+                args.arg3 = reason;
                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
                         args).sendToTarget();
             }
@@ -1003,12 +1073,6 @@
                 throws RemoteException {
             // no-op in the listener
         }
-
-        @Override
-        public void onNotificationRemovedReason(String key, long time, int reason)
-                throws RemoteException {
-            // no-op in the listener
-        }
     }
 
     private void applyUpdateLocked(NotificationRankingUpdate update) {
@@ -1413,8 +1477,9 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
                     RankingMap rankingMap = (RankingMap) args.arg2;
+                    int reason = (int) args.arg3;
                     args.recycle();
-                    onNotificationRemoved(sbn, rankingMap);
+                    onNotificationRemoved(sbn, rankingMap, reason);
                 } break;
 
                 case MSG_ON_LISTENER_CONNECTED: {
diff --git a/core/java/android/text/Editable.java b/core/java/android/text/Editable.java
index b3f2c2a..c0948a6 100644
--- a/core/java/android/text/Editable.java
+++ b/core/java/android/text/Editable.java
@@ -121,8 +121,10 @@
     public InputFilter[] getFilters();
 
     /**
-     * Factory used by TextView to create new Editables.  You can subclass
-     * it to provide something other than SpannableStringBuilder.
+     * Factory used by TextView to create new {@link Editable Editables}. You can subclass
+     * it to provide something other than {@link SpannableStringBuilder}.
+     *
+     * @see android.widget.TextView#setEditableFactory(Factory)
      */
     public static class Factory {
         private static Editable.Factory sInstance = new Editable.Factory();
diff --git a/core/java/android/text/Spannable.java b/core/java/android/text/Spannable.java
index ae5d356..39b78eb 100644
--- a/core/java/android/text/Spannable.java
+++ b/core/java/android/text/Spannable.java
@@ -46,15 +46,17 @@
     public void removeSpan(Object what);
 
     /**
-     * Factory used by TextView to create new Spannables.  You can subclass
-     * it to provide something other than SpannableString.
+     * Factory used by TextView to create new {@link Spannable Spannables}. You can subclass
+     * it to provide something other than {@link SpannableString}.
+     *
+     * @see android.widget.TextView#setSpannableFactory(Factory)
      */
     public static class Factory {
         private static Spannable.Factory sInstance = new Spannable.Factory();
 
         /**
          * Returns the standard Spannable Factory.
-         */ 
+         */
         public static Spannable.Factory getInstance() {
             return sInstance;
         }
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
index f4eb132..08fb948 100644
--- a/core/java/android/util/Half.java
+++ b/core/java/android/util/Half.java
@@ -27,20 +27,64 @@
  * <ul>
  * <li>Sign bit: 1 bit</li>
  * <li>Exponent width: 5 bits</li>
- * <li>Mantissa: 10 bits</li>
+ * <li>Significand: 10 bits</li>
  * </ul>
  *
- * <p>The format is laid out thusly:</p>
+ * <p>The format is laid out as follows:</p>
  * <pre>
  * 1   11111   1111111111
  * ^   --^--   -----^----
- * sign  |          |_______ mantissa
+ * sign  |          |_______ significand
  *       |
  *       -- exponent
  * </pre>
  *
- * @hide
+ * <p>Half-precision floating points can be useful to save memory and/or
+ * bandwidth at the expense of range and precision when compared to single-precision
+ * floating points (fp32).</p>
+ * <p>To help you decide whether fp16 is the right storage type for you need, please
+ * refer to the table below that shows the available precision throughout the range of
+ * possible values. The <em>precision</em> column indicates the step size between two
+ * consecutive numbers in a specific part of the range.</p>
+ *
+ * <table summary="Precision of fp16 across the range">
+ *     <tr><th>Range start</th><th>Precision</th></tr>
+ *     <tr><td>0</td><td>1 &frasl; 16,777,216</td></tr>
+ *     <tr><td>1 &frasl; 16,384</td><td>1 &frasl; 16,777,216</td></tr>
+ *     <tr><td>1 &frasl; 8,192</td><td>1 &frasl; 8,388,608</td></tr>
+ *     <tr><td>1 &frasl; 4,096</td><td>1 &frasl; 4,194,304</td></tr>
+ *     <tr><td>1 &frasl; 2,048</td><td>1 &frasl; 2,097,152</td></tr>
+ *     <tr><td>1 &frasl; 1,024</td><td>1 &frasl; 1,048,576</td></tr>
+ *     <tr><td>1 &frasl; 512</td><td>1 &frasl; 524,288</td></tr>
+ *     <tr><td>1 &frasl; 256</td><td>1 &frasl; 262,144</td></tr>
+ *     <tr><td>1 &frasl; 128</td><td>1 &frasl; 131,072</td></tr>
+ *     <tr><td>1 &frasl; 64</td><td>1 &frasl; 65,536</td></tr>
+ *     <tr><td>1 &frasl; 32</td><td>1 &frasl; 32,768</td></tr>
+ *     <tr><td>1 &frasl; 16</td><td>1 &frasl; 16,384</td></tr>
+ *     <tr><td>1 &frasl; 8</td><td>1 &frasl; 8,192</td></tr>
+ *     <tr><td>1 &frasl; 4</td><td>1 &frasl; 4,096</td></tr>
+ *     <tr><td>1 &frasl; 2</td><td>1 &frasl; 2,048</td></tr>
+ *     <tr><td>1</td><td>1 &frasl; 1,024</td></tr>
+ *     <tr><td>2</td><td>1 &frasl; 512</td></tr>
+ *     <tr><td>4</td><td>1 &frasl; 256</td></tr>
+ *     <tr><td>8</td><td>1 &frasl; 128</td></tr>
+ *     <tr><td>16</td><td>1 &frasl; 64</td></tr>
+ *     <tr><td>32</td><td>1 &frasl; 32</td></tr>
+ *     <tr><td>64</td><td>1 &frasl; 16</td></tr>
+ *     <tr><td>128</td><td>1 &frasl; 8</td></tr>
+ *     <tr><td>256</td><td>1 &frasl; 4</td></tr>
+ *     <tr><td>512</td><td>1 &frasl; 2</td></tr>
+ *     <tr><td>1,024</td><td>1</td></tr>
+ *     <tr><td>2,048</td><td>2</td></tr>
+ *     <tr><td>4,096</td><td>4</td></tr>
+ *     <tr><td>8,192</td><td>8</td></tr>
+ *     <tr><td>16,384</td><td>16</td></tr>
+ *     <tr><td>32,768</td><td>32</td></tr>
+ * </table>
+ *
+ * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p>
  */
+@SuppressWarnings("SimplifiableIfStatement")
 public final class Half {
     /**
      * The number of bits used to represent a half-precision float value.
@@ -59,7 +103,7 @@
     /**
      * Maximum exponent a finite half-precision float may have.
      */
-    public static final short MAX_EXPONENT       = 15;
+    public static final int MAX_EXPONENT         = 15;
     /**
      * Maximum positive finite value a half-precision float may have.
      */
@@ -67,7 +111,7 @@
     /**
      * Minimum exponent a normalized half-precision float may have.
      */
-    public static final short MIN_EXPONENT       = -14;
+    public static final int MIN_EXPONENT         = -14;
     /**
      * Smallest positive normal value a half-precision float may have.
      */
@@ -97,41 +141,345 @@
      */
     public static final short POSITIVE_ZERO      = (short) 0x0000;
 
-    private static final int FP16_SIGN_SHIFT     = 15;
-    private static final int FP16_EXPONENT_SHIFT = 10;
-    private static final int FP16_EXPONENT_MASK  = 0x1f;
-    private static final int FP16_MANTISSA_MASK  = 0x3ff;
-    private static final int FP16_EXPONENT_BIAS  = 15;
+    private static final int FP16_SIGN_SHIFT        = 15;
+    private static final int FP16_SIGN_MASK         = 0x8000;
+    private static final int FP16_EXPONENT_SHIFT    = 10;
+    private static final int FP16_EXPONENT_MASK     = 0x1f;
+    private static final int FP16_SIGNIFICAND_MASK  = 0x3ff;
+    private static final int FP16_EXPONENT_BIAS     = 15;
+    private static final int FP16_COMBINED          = 0x7fff;
+    private static final int FP16_EXPONENT_MAX      = 0x7c00;
 
-    private static final int FP32_SIGN_SHIFT     = 31;
-    private static final int FP32_EXPONENT_SHIFT = 23;
-    private static final int FP32_EXPONENT_MASK  = 0xff;
-    private static final int FP32_MANTISSA_MASK  = 0x7fffff;
-    private static final int FP32_EXPONENT_BIAS  = 127;
+    private static final int FP32_SIGN_SHIFT        = 31;
+    private static final int FP32_EXPONENT_SHIFT    = 23;
+    private static final int FP32_EXPONENT_MASK     = 0xff;
+    private static final int FP32_SIGNIFICAND_MASK  = 0x7fffff;
+    private static final int FP32_EXPONENT_BIAS     = 127;
 
-    private static final int   FP32_DENORMAL_MAGIC = 126 << 23;
-    private static final float FP32_DENORMAL_FLOAT =
-            Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
+    private static final int FP32_DENORMAL_MAGIC = 126 << 23;
+    private static final float FP32_DENORMAL_FLOAT = Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
 
     private Half() {
     }
 
     /**
+     * Returns the first parameter with the sign of the second parameter.
+     * This method treats NaNs as having a sign.
+     *
+     * @param magnitude A half-precision float value providing the magnitude of the result
+     * @param sign  A half-precision float value providing the sign of the result
+     * @return A value with the magnitude of the first parameter and the sign
+     *         of the second parameter
+     */
+    public static short copySign(short magnitude, short sign) {
+        return (short) ((sign & FP16_SIGN_MASK) | (magnitude & FP16_COMBINED));
+    }
+
+    /**
+     * Returns the absolute value of the specified half-precision float.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is positive zero (see {@link #POSITIVE_ZERO})</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is positive infinity (see {@link #POSITIVE_INFINITY})</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The absolute value of the specified half-precision float
+     */
+    public static short abs(short h) {
+        return (short) (h & FP16_COMBINED);
+    }
+
+    /**
+     * Returns the closest integral half-precision float value to the specified
+     * half-precision float value. Special values are handled in the
+     * following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The value of the specified half-precision float rounded to the nearest
+     *         half-precision float value
+     */
+    public static short round(short h) {
+        int bits = h & 0xffff;
+        int e = bits & 0x7fff;
+        int result = bits;
+
+        if (e < 0x3c00) {
+            result &= FP16_SIGN_MASK;
+            result |= (0x3c00 & (e >= 0x3800 ? 0xffff : 0x0));
+        } else if (e < 0x6400) {
+            e = 25 - (e >> 10);
+            int mask = (1 << e) - 1;
+            result += (1 << (e - 1));
+            result &= ~mask;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the smallest half-precision float value toward negative infinity
+     * greater than or equal to the specified half-precision float value.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The smallest half-precision float value toward negative infinity
+     *         greater than or equal to the specified half-precision float value
+     */
+    public static short ceil(short h) {
+        int bits = h & 0xffff;
+        int e = bits & 0x7fff;
+        int result = bits;
+
+        if (e < 0x3c00) {
+            result &= FP16_SIGN_MASK;
+            result |= 0x3c00 & -(~(bits >> 15) & (e != 0 ? 1 : 0));
+        } else if (e < 0x6400) {
+            e = 25 - (e >> 10);
+            int mask = (1 << e) - 1;
+            result += mask & ((bits >> 15) - 1);
+            result &= ~mask;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the largest half-precision float value toward positive infinity
+     * less than or equal to the specified half-precision float value.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The largest half-precision float value toward positive infinity
+     *         less than or equal to the specified half-precision float value
+     */
+    public static short floor(short h) {
+        int bits = h & 0xffff;
+        int e = bits & 0x7fff;
+        int result = bits;
+
+        if (e < 0x3c00) {
+            result &= FP16_SIGN_MASK;
+            result |= 0x3c00 & (bits > 0x8000 ? 0xffff : 0x0);
+        } else if (e < 0x6400) {
+            e = 25 - (e >> 10);
+            int mask = (1 << e) - 1;
+            result += mask & -(bits >> 15);
+            result &= ~mask;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the truncated half-precision float value of the specified
+     * half-precision float value. Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The truncated half-precision float value of the specified
+     *         half-precision float value
+     */
+    public static short trunc(short h) {
+        int bits = h & 0xffff;
+        int e = bits & 0x7fff;
+        int result = bits;
+
+        if (e < 0x3c00) {
+            result &= FP16_SIGN_MASK;
+        } else if (e < 0x6400) {
+            e = 25 - (e >> 10);
+            int mask = (1 << e) - 1;
+            result &= ~mask;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the smaller of two half-precision float values (the value closest
+     * to negative infinity). Special values are handled in the following ways:
+     * <ul>
+     * <li>If either value is NaN, the result is NaN</li>
+     * <li>{@link #NEGATIVE_ZERO} is smaller than {@link #POSITIVE_ZERO}</li>
+     * </ul>
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     * @return The smaller of the two specified half-precision values
+     */
+    public static short min(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
+
+        if ((x & FP16_COMBINED) == 0 && (y & FP16_COMBINED) == 0) {
+            return (x & FP16_SIGN_MASK) != 0 ? x : y;
+        }
+
+        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
+               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+    }
+
+    /**
+     * Returns the larger of two half-precision float values (the value closest
+     * to positive infinity). Special values are handled in the following ways:
+     * <ul>
+     * <li>If either value is NaN, the result is NaN</li>
+     * <li>{@link #POSITIVE_ZERO} is greater than {@link #NEGATIVE_ZERO}</li>
+     * </ul>
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return The larger of the two specified half-precision values
+     */
+    public static short max(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return NaN;
+
+        if ((x & FP16_COMBINED) == 0 && (y & FP16_COMBINED) == 0) {
+            return (x & FP16_SIGN_MASK) != 0 ? y : x;
+        }
+
+        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
+               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+    }
+
+    /**
+     * Returns true if the first half-precision float value is less (smaller
+     * toward negative infinity) than the second half-precision float value.
+     * If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is less than y, false otherwise
+     */
+    public static boolean less(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+
+        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
+               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is less (smaller
+     * toward negative infinity) than or equal to the second half-precision
+     * float value. If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is less than or equal to y, false otherwise
+     */
+    public static boolean lessEquals(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+
+        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <=
+               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is greater (larger
+     * toward positive infinity) than the second half-precision float value.
+     * If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is greater than y, false otherwise
+     */
+    public static boolean greater(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+
+        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
+               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is greater (larger
+     * toward positive infinity) than or equal to the second half-precision float
+     * value. If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is greater than y, false otherwise
+     */
+    public static boolean greaterEquals(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+
+        return ((x & FP16_SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >=
+               ((y & FP16_SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the two half-precision float values are equal.
+     * If either of the values is NaN, the result is false. {@link #POSITIVE_ZERO}
+     * and {@link #NEGATIVE_ZERO} are considered equal.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is equal to y, false otherwise
+     */
+    public static boolean equals(short x, short y) {
+        if ((x & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+        if ((y & FP16_COMBINED) > FP16_EXPONENT_MAX) return false;
+
+        return x == y || ((x | y) & FP16_COMBINED) == 0;
+    }
+
+    /**
      * Returns the sign of the specified half-precision float.
      *
      * @param h A half-precision float value
      * @return 1 if the value is positive, -1 if the value is negative
      */
     public static int getSign(short h) {
-        return (h >>> FP16_SIGN_SHIFT) == 0 ? 1 : -1;
+        return (h & FP16_SIGN_MASK) == 0 ? 1 : -1;
     }
 
     /**
      * Returns the unbiased exponent used in the representation of
      * the specified  half-precision float value. if the value is NaN
      * or infinite, this* method returns {@link #MAX_EXPONENT} + 1.
-     * If the argument is* 0 or denormal, this method returns
-     * {@link #MIN_EXPONENT} - 1.
+     * If the argument is 0 or a subnormal representation, this method
+     * returns {@link #MIN_EXPONENT} - 1.
      *
      * @param h A half-precision float value
      * @return The unbiased exponent of the specified value
@@ -141,14 +489,14 @@
     }
 
     /**
-     * Returns the mantissa, or significand, used in the representation
+     * Returns the significand, or mantissa, used in the representation
      * of the specified half-precision float value.
      *
      * @param h A half-precision float value
-     * @return The mantissa, or significand, of the specified vlaue
+     * @return The significand, or significand, of the specified vlaue
      */
-    public static int getMantissa(short h) {
-        return h & FP16_MANTISSA_MASK;
+    public static int getSignificand(short h) {
+        return h & FP16_SIGNIFICAND_MASK;
     }
 
     /**
@@ -160,9 +508,7 @@
      *         false otherwise
      */
     public static boolean isInfinite(short h) {
-        int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
-        int m = (h                        ) & FP16_MANTISSA_MASK;
-        return e == 0x1f && m == 0;
+        return (h & FP16_COMBINED) == FP16_EXPONENT_MAX;
     }
 
     /**
@@ -173,9 +519,21 @@
      * @return true if the value is a NaN, false otherwise
      */
     public static boolean isNaN(short h) {
-        int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
-        int m = (h                        ) & FP16_MANTISSA_MASK;
-        return e == 0x1f && m != 0;
+        return (h & FP16_COMBINED) > FP16_EXPONENT_MAX;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value is normalized
+     * (does not have a subnormal representation). If the specified value is
+     * {@link #POSITIVE_INFINITY}, {@link #NEGATIVE_INFINITY},
+     * {@link #POSITIVE_ZERO}, {@link #NEGATIVE_ZERO}, NaN or any subnormal
+     * number, this method returns false.
+     *
+     * @param h A half-precision float value
+     * @return true if the value is normalized, false otherwise
+     */
+    public static boolean isNormalized(short h) {
+        return (h & FP16_EXPONENT_MAX) != 0 && (h & FP16_EXPONENT_MAX) != FP16_EXPONENT_MAX;
     }
 
     /**
@@ -195,9 +553,9 @@
      */
     public static float toFloat(short h) {
         int bits = h & 0xffff;
-        int s = (bits >>> FP16_SIGN_SHIFT    );
+        int s = bits & FP16_SIGN_MASK;
         int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
-        int m = (bits                        ) & FP16_MANTISSA_MASK;
+        int m = (bits                        ) & FP16_SIGNIFICAND_MASK;
 
         int outE = 0;
         int outM = 0;
@@ -218,7 +576,7 @@
             }
         }
 
-        int out = (s << FP32_SIGN_SHIFT) | (outE << FP32_EXPONENT_SHIFT) | outM;
+        int out = (s << 16) | (outE << FP32_EXPONENT_SHIFT) | outM;
         return Float.intBitsToFloat(out);
     }
 
@@ -249,7 +607,7 @@
         int bits = Float.floatToRawIntBits(f);
         int s = (bits >>> FP32_SIGN_SHIFT    );
         int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_EXPONENT_MASK;
-        int m = (bits                        ) & FP32_MANTISSA_MASK;
+        int m = (bits                        ) & FP32_SIGNIFICAND_MASK;
 
         int outE = 0;
         int outM = 0;
@@ -278,14 +636,12 @@
                     // Round to nearest "0.5" up
                     int out = (outE << FP16_EXPONENT_SHIFT) | outM;
                     out++;
-                    out |= (s << FP16_SIGN_SHIFT);
-                    return (short) out;
+                    return (short) (out | (s << FP16_SIGN_SHIFT));
                 }
             }
         }
 
-        int out = (s << FP16_SIGN_SHIFT) | (outE << FP16_EXPONENT_SHIFT) | outM;
-        return (short) out;
+        return (short) ((s << FP16_SIGN_SHIFT) | (outE << FP16_EXPONENT_SHIFT) | outM);
     }
 
     /**
@@ -311,16 +667,16 @@
      * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
      * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
      * <li>If the value has a normalized representation, the exponent and
-     * mantissa are represented in the string in two fields. The mantissa starts
-     * with <code>"0x1."</code> followed by its lowercase hexadecimal
+     * significand are represented in the string in two fields. The significand
+     * starts with <code>"0x1."</code> followed by its lowercase hexadecimal
      * representation. Trailing zeroes are removed unless all digits are 0, then
-     * a single zero is used. The mantissa representation is followed by the
+     * a single zero is used. The significand representation is followed by the
      * exponent, represented by <code>"p"</code>, itself followed by a decimal
      * string of the unbiased exponent</li>
-     * <li>If the value has a denormal representation, the mantissa starts
+     * <li>If the value has a subnormal representation, the significand starts
      * with <code>"0x0."</code> followed by its lowercase hexadecimal
      * representation. Trailing zeroes are removed unless all digits are 0, then
-     * a single zero is used. The mantissa representation is followed by the
+     * a single zero is used. The significand representation is followed by the
      * exponent, represented by <code>"p-14"</code></li>
      * </ul>
      *
@@ -333,11 +689,11 @@
         int bits = h & 0xffff;
         int s = (bits >>> FP16_SIGN_SHIFT    );
         int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
-        int m = (bits                        ) & FP16_MANTISSA_MASK;
+        int m = (bits                        ) & FP16_SIGNIFICAND_MASK;
 
         if (e == 0x1f) { // Infinite or NaN
             if (m == 0) {
-                if (s == 1) o.append('-');
+                if (s != 0) o.append('-');
                 o.append("Infinity");
             } else {
                 o.append("NaN");
@@ -349,14 +705,14 @@
                     o.append("0x0.0p0");
                 } else {
                     o.append("0x0.");
-                    String mantissa = Integer.toHexString(m);
-                    o.append(mantissa.replaceFirst("0{2,}$", ""));
+                    String significand = Integer.toHexString(m);
+                    o.append(significand.replaceFirst("0{2,}$", ""));
                     o.append("p-14");
                 }
             } else {
                 o.append("0x1.");
-                String mantissa = Integer.toHexString(m);
-                o.append(mantissa.replaceFirst("0{2,}$", ""));
+                String significand = Integer.toHexString(m);
+                o.append(significand.replaceFirst("0{2,}$", ""));
                 o.append('p');
                 o.append(Integer.toString(e - FP16_EXPONENT_BIAS));
             }
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index a81eef8..d59be02 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -32,6 +32,11 @@
     oneway void setInInteractiveMode(boolean inInteractiveMode);
 
     /**
+     * Notifies the controller that the PIP is currently minimized.
+     */
+    oneway void setIsMinimized(boolean isMinimized);
+
+    /**
      * Notifies the controller that the desired snap mode is to the closest edge.
      */
     oneway void setSnapToEdge(boolean snapToEdge);
diff --git a/core/java/android/view/inputmethod/InputContentInfo.java b/core/java/android/view/inputmethod/InputContentInfo.java
index b39705e..7104a28 100644
--- a/core/java/android/view/inputmethod/InputContentInfo.java
+++ b/core/java/android/view/inputmethod/InputContentInfo.java
@@ -18,11 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ClipDescription;
+import android.content.ContentProvider;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.UserHandle;
 
 import com.android.internal.inputmethod.IInputContentUriToken;
 
@@ -33,8 +36,24 @@
  */
 public final class InputContentInfo implements Parcelable {
 
+    /**
+     * The content URI that may or may not have a user ID embedded by
+     * {@link ContentProvider#maybeAddUserId(Uri, int)}.  This always preserves the exact value
+     * specified to a constructor.  In other words, if it had user ID embedded when it was passed
+     * to the constructor, it still has the same user ID no matter if it is valid or not.
+     */
     @NonNull
     private final Uri mContentUri;
+    /**
+     * The user ID to which {@link #mContentUri} belongs to.  If {@link #mContentUri} already
+     * embedded the user ID when it was specified then this fields has the same user ID.  Otherwise
+     * the user ID is determined based on the process ID when the constructor is called.
+     *
+     * <p>CAUTION: If you received {@link InputContentInfo} from a different process, there is no
+     * guarantee that this value is correct and valid.  Never use this for any security purpose</p>
+     */
+    @UserIdInt
+    private final int mContentUriOwnerUserId;
     @NonNull
     private final ClipDescription mDescription;
     @Nullable
@@ -73,6 +92,8 @@
             @Nullable Uri linkUri) {
         validateInternal(contentUri, description, linkUri, true /* throwException */);
         mContentUri = contentUri;
+        mContentUriOwnerUserId =
+                ContentProvider.getUserIdFromUri(mContentUri, UserHandle.myUserId());
         mDescription = description;
         mLinkUri = linkUri;
     }
@@ -139,7 +160,14 @@
      * @return Content URI with which the content can be obtained.
      */
     @NonNull
-    public Uri getContentUri() { return mContentUri; }
+    public Uri getContentUri() {
+        // Fix up the content URI when and only when the caller's user ID does not match the owner's
+        // user ID.
+        if (mContentUriOwnerUserId != UserHandle.myUserId()) {
+            return ContentProvider.maybeAddUserId(mContentUri, mContentUriOwnerUserId);
+        }
+        return mContentUri;
+    }
 
     /**
      * @return {@link ClipDescription} object that contains the metadata of {@code #getContentUri()}
@@ -203,6 +231,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         Uri.writeToParcel(dest, mContentUri);
+        dest.writeInt(mContentUriOwnerUserId);
         mDescription.writeToParcel(dest, flags);
         Uri.writeToParcel(dest, mLinkUri);
         if (mUriToken != null) {
@@ -215,6 +244,7 @@
 
     private InputContentInfo(@NonNull Parcel source) {
         mContentUri = Uri.CREATOR.createFromParcel(source);
+        mContentUriOwnerUserId = source.readInt();
         mDescription = ClipDescription.CREATOR.createFromParcel(source);
         mLinkUri = Uri.CREATOR.createFromParcel(source);
         if (source.readInt() == 1) {
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 9d228cf..bbc50da 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -312,10 +312,10 @@
     }
 
     /**
-     * Control whether methods that change the list ({@link #add},
-     * {@link #insert}, {@link #remove}, {@link #clear}) automatically call
-     * {@link #notifyDataSetChanged}.  If set to false, caller must
-     * manually call notifyDataSetChanged() to have the changes
+     * Control whether methods that change the list ({@link #add}, {@link #addAll(Collection)},
+     * {@link #addAll(Object[])}, {@link #insert}, {@link #remove}, {@link #clear},
+     * {@link #sort(Comparator)}) automatically call {@link #notifyDataSetChanged}.  If set to
+     * false, caller must manually call notifyDataSetChanged() to have the changes
      * reflected in the attached view.
      *
      * The default is true, and calling notifyDataSetChanged()
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 73af755..5426a37 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1683,12 +1683,13 @@
     }
 
     /**
-     * Return the text the TextView is displaying. If setText() was called with
-     * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
+     * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
+     * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
+     * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
      * the return value from this method to Spannable or Editable, respectively.
-     *
-     * Note: The content of the return value should not be modified. If you want
-     * a modifiable one, you should make your own copy first.
+     * <p/>
+     * The content of the return value should not be modified. If you want a modifiable one, you
+     * should make your own copy first.
      *
      * @attr ref android.R.styleable#TextView_text
      */
@@ -1705,8 +1706,8 @@
     }
 
     /**
-     * Return the text the TextView is displaying as an Editable object.  If
-     * the text is not editable, null is returned.
+     * Return the text that TextView is displaying as an Editable object. If the text is not
+     * editable, null is returned.
      *
      * @see #getText
      */
@@ -4148,18 +4149,26 @@
     }
 
     /**
-     * Convenience method: Append the specified text to the TextView's
-     * display buffer, upgrading it to BufferType.EDITABLE if it was
-     * not already editable.
+     * Convenience method to append the specified text to the TextView's
+     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
+     * if it was not already editable.
+     *
+     * @param text text to be appended to the already displayed text
      */
     public final void append(CharSequence text) {
         append(text, 0, text.length());
     }
 
     /**
-     * Convenience method: Append the specified text slice to the TextView's
-     * display buffer, upgrading it to BufferType.EDITABLE if it was
-     * not already editable.
+     * Convenience method to append the specified text slice to the TextView's
+     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
+     * if it was not already editable.
+     *
+     * @param text text to be appended to the already displayed text
+     * @param start the index of the first character in the {@code text}
+     * @param end the index of the character following the last character in the {@code text}
+     *
+     * @see Appendable#append(CharSequence, int, int)
      */
     public void append(CharSequence text, int start, int end) {
         if (!(mText instanceof Editable)) {
@@ -4403,7 +4412,12 @@
     ///////////////////////////////////////////////////////////////////////////
 
     /**
-     * Sets the Factory used to create new Editables.
+     * Sets the Factory used to create new {@link Editable Editables}.
+     *
+     * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
+     *
+     * @see android.text.Editable.Factory
+     * @see android.widget.TextView.BufferType#EDITABLE
      */
     public final void setEditableFactory(Editable.Factory factory) {
         mEditableFactory = factory;
@@ -4411,7 +4425,12 @@
     }
 
     /**
-     * Sets the Factory used to create new Spannables.
+     * Sets the Factory used to create new {@link Spannable Spannables}.
+     *
+     * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
+     *
+     * @see android.text.Spannable.Factory
+     * @see android.widget.TextView.BufferType#SPANNABLE
      */
     public final void setSpannableFactory(Spannable.Factory factory) {
         mSpannableFactory = factory;
@@ -4419,13 +4438,20 @@
     }
 
     /**
-     * Sets the string value of the TextView. TextView <em>does not</em> accept
+     * Sets the text to be displayed. TextView <em>does not</em> accept
      * HTML-like formatting, which you can do with text strings in XML resource files.
      * To style your strings, attach android.text.style.* objects to a
-     * {@link android.text.SpannableString SpannableString}, or see the
+     * {@link android.text.SpannableString}, or see the
      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
      * Available Resource Types</a> documentation for an example of setting
      * formatted text in the XML resource file.
+     * <p/>
+     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
+     * intermediate {@link Spannable Spannables}. Likewise it will use
+     * {@link android.text.Editable.Factory} to create final or intermediate
+     * {@link Editable Editables}.
+     *
+     * @param text text to be displayed
      *
      * @attr ref android.R.styleable#TextView_text
      */
@@ -4435,10 +4461,16 @@
     }
 
     /**
-     * Like {@link #setText(CharSequence)},
-     * except that the cursor position (if any) is retained in the new text.
+     * Sets the text to be displayed but retains the cursor position. Same as
+     * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
+     * new text.
+     * <p/>
+     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
+     * intermediate {@link Spannable Spannables}. Likewise it will use
+     * {@link android.text.Editable.Factory} to create final or intermediate
+     * {@link Editable Editables}.
      *
-     * @param text The new text to place in the text view.
+     * @param text text to be displayed
      *
      * @see #setText(CharSequence)
      */
@@ -4448,9 +4480,21 @@
     }
 
     /**
-     * Sets the text that this TextView is to display (see
-     * {@link #setText(CharSequence)}) and also sets whether it is stored
-     * in a styleable/spannable buffer and whether it is editable.
+     * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
+     * <p/>
+     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
+     * intermediate {@link Spannable Spannables}. Likewise it will use
+     * {@link android.text.Editable.Factory} to create final or intermediate
+     * {@link Editable Editables}.
+     *
+     * @param text text to be displayed
+     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
+     *              stored as a static text, styleable/spannable text, or editable text
+     *
+     * @see #setText(CharSequence)
+     * @see android.widget.TextView.BufferType
+     * @see #setSpannableFactory(Spannable.Factory)
+     * @see #setEditableFactory(Editable.Factory)
      *
      * @attr ref android.R.styleable#TextView_text
      * @attr ref android.R.styleable#TextView_bufferType
@@ -4617,10 +4661,14 @@
 
     /**
      * Sets the TextView to display the specified slice of the specified
-     * char array.  You must promise that you will not change the contents
+     * char array. You must promise that you will not change the contents
      * of the array except for right before another call to setText(),
      * since the TextView has no way to know that the text
      * has changed and that it needs to invalidate and re-layout.
+     *
+     * @param text char array to be displayed
+     * @param start start index in the char array
+     * @param len length of char count after {@code start}
      */
     public final void setText(char[] text, int start, int len) {
         int oldlen = 0;
@@ -4651,8 +4699,19 @@
     }
 
     /**
-     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
-     * except that the cursor position (if any) is retained in the new text.
+     * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
+     * the cursor position. Same as
+     * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
+     * position (if any) is retained in the new text.
+     * <p/>
+     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
+     * intermediate {@link Spannable Spannables}. Likewise it will use
+     * {@link android.text.Editable.Factory} to create final or intermediate
+     * {@link Editable Editables}.
+     *
+     * @param text text to be displayed
+     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
+     *              stored as a static text, styleable/spannable text, or editable text
      *
      * @see #setText(CharSequence, android.widget.TextView.BufferType)
      */
@@ -4672,11 +4731,42 @@
         }
     }
 
+    /**
+     * Sets the text to be displayed using a string resource identifier.
+     *
+     * @param resid the resource identifier of the string resource to be displayed
+     *
+     * @see #setText(CharSequence)
+     *
+     * @attr ref android.R.styleable#TextView_text
+     */
     @android.view.RemotableViewMethod
     public final void setText(@StringRes int resid) {
         setText(getContext().getResources().getText(resid));
     }
 
+    /**
+     * Sets the text to be displayed using a string resource identifier and the
+     * {@link android.widget.TextView.BufferType}.
+     * <p/>
+     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
+     * intermediate {@link Spannable Spannables}. Likewise it will use
+     * {@link android.text.Editable.Factory} to create final or intermediate
+     * {@link Editable Editables}.
+     *
+     * @param resid the resource identifier of the string resource to be displayed
+     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
+     *              stored as a static text, styleable/spannable text, or editable text
+     *
+     * @see #setText(int)
+     * @see #setText(CharSequence)
+     * @see android.widget.TextView.BufferType
+     * @see #setSpannableFactory(Spannable.Factory)
+     * @see #setEditableFactory(Editable.Factory)
+     *
+     * @attr ref android.R.styleable#TextView_text
+     * @attr ref android.R.styleable#TextView_bufferType
+     */
     public final void setText(@StringRes int resid, BufferType type) {
         setText(getContext().getResources().getText(resid), type);
     }
@@ -5110,7 +5200,7 @@
      * Set the extra input data of the text, which is the
      * {@link EditorInfo#extras TextBoxAttribute.extras}
      * Bundle that will be filled in when creating an input connection.  The
-     * given integer is the resource ID of an XML resource holding an
+     * given integer is the resource identifier of an XML resource holding an
      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
      *
      * @see #getInputExtras(boolean)
@@ -8832,8 +8922,12 @@
         }
     }
 
+    /**
+     * Type of the text buffer that defines the characteristics of the text such as static,
+     * styleable, or editable.
+     */
     public enum BufferType {
-        NORMAL, SPANNABLE, EDITABLE,
+        NORMAL, SPANNABLE, EDITABLE
     }
 
     /**
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index cbacf26..1e2a53b 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -208,15 +208,19 @@
         final int fromTop = Math.abs(stackBounds.top - movementBounds.top);
         final int fromRight = Math.abs(movementBounds.right - stackBounds.left);
         final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top);
+        final int boundedLeft = Math.max(movementBounds.left, Math.min(movementBounds.right,
+                stackBounds.left));
+        final int boundedTop = Math.max(movementBounds.top, Math.min(movementBounds.bottom,
+                stackBounds.top));
         boundsOut.set(stackBounds);
         if (fromLeft <= fromTop && fromLeft <= fromRight && fromLeft <= fromBottom) {
-            boundsOut.offsetTo(movementBounds.left, stackBounds.top);
+            boundsOut.offsetTo(movementBounds.left, boundedTop);
         } else if (fromTop <= fromLeft && fromTop <= fromRight && fromTop <= fromBottom) {
-            boundsOut.offsetTo(stackBounds.left, movementBounds.top);
+            boundsOut.offsetTo(boundedLeft, movementBounds.top);
         } else if (fromRight < fromLeft && fromRight < fromTop && fromRight < fromBottom) {
-            boundsOut.offsetTo(movementBounds.right, stackBounds.top);
+            boundsOut.offsetTo(movementBounds.right, boundedTop);
         } else {
-            boundsOut.offsetTo(stackBounds.left, movementBounds.bottom);
+            boundsOut.offsetTo(boundedLeft, movementBounds.bottom);
         }
     }
 
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 7387b29..886c0e6 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -267,17 +267,17 @@
 
     const jchar *interfaceName = env->GetStringCritical(interfaceNameObj, NULL);
     if (interfaceName) {
-        hardware::Parcel *parcel =
-            JHwParcel::GetNativeContext(env, thiz)->getParcel();
-
-        status_t err = parcel->writeInterfaceToken(
-                String16(
-                    reinterpret_cast<const char16_t *>(interfaceName),
-                    env->GetStringLength(interfaceNameObj)));
+        String16 nameCopy(
+                reinterpret_cast<const char16_t *>(interfaceName),
+                env->GetStringLength(interfaceNameObj));
 
         env->ReleaseStringCritical(interfaceNameObj, interfaceName);
         interfaceName = NULL;
 
+        hardware::Parcel *parcel =
+            JHwParcel::GetNativeContext(env, thiz)->getParcel();
+
+        status_t err = parcel->writeInterfaceToken(nameCopy);
         signalExceptionForError(env, err);
     }
 }
@@ -294,17 +294,18 @@
 
     const jchar *interfaceName = env->GetStringCritical(interfaceNameObj, NULL);
     if (interfaceName) {
-        hardware::Parcel *parcel =
-            JHwParcel::GetNativeContext(env, thiz)->getParcel();
-
-        bool valid = parcel->enforceInterface(
-                String16(
-                    reinterpret_cast<const char16_t *>(interfaceName),
-                    env->GetStringLength(interfaceNameObj)));
+        String16 interfaceNameCopy(
+                reinterpret_cast<const char16_t *>(interfaceName),
+                env->GetStringLength(interfaceNameObj));
 
         env->ReleaseStringCritical(interfaceNameObj, interfaceName);
         interfaceName = NULL;
 
+        hardware::Parcel *parcel =
+            JHwParcel::GetNativeContext(env, thiz)->getParcel();
+
+        bool valid = parcel->enforceInterface(interfaceNameCopy);
+
         if (!valid) {
             jniThrowException(
                     env,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index fc85b4b..da059e3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -319,12 +319,6 @@
         bool force_mount_namespace) {
     // See storage config details at http://source.android.com/tech/storage/
 
-    // Create a second private mount namespace for our process
-    if (unshare(CLONE_NEWNS) == -1) {
-        ALOGW("Failed to unshare(): %s", strerror(errno));
-        return false;
-    }
-
     String8 storageSource;
     if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
         storageSource = "/mnt/runtime/default";
@@ -332,10 +326,17 @@
         storageSource = "/mnt/runtime/read";
     } else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
         storageSource = "/mnt/runtime/write";
-    } else {
+    } else if (!force_mount_namespace) {
         // Sane default of no storage visible
         return true;
     }
+
+    // Create a second private mount namespace for our process
+    if (unshare(CLONE_NEWNS) == -1) {
+        ALOGW("Failed to unshare(): %s", strerror(errno));
+        return false;
+    }
+
     if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
             NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
         ALOGW("Failed to mount %s to /storage: %s", storageSource.string(), strerror(errno));
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3dea051..0f7b5a5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3130,6 +3130,12 @@
     <permission android:name="android.permission.MANAGE_AUTO_FILL"
         android:protectionLevel="signature" />
 
+    <!-- Allows an app to set the theme overlay in /vendor/overlay
+         being used.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MODIFY_THEME_OVERLAY"
+                android:protectionLevel="signature" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/values-watch/colors_material.xml b/core/res/res/values-watch/colors_material.xml
index 18bfd4d..1456976 100644
--- a/core/res/res/values-watch/colors_material.xml
+++ b/core/res/res/values-watch/colors_material.xml
@@ -14,13 +14,13 @@
      limitations under the License.
 -->
 <resources>
-    <color name="background_material_dark">#ff232e33</color>
-    <color name="background_floating_material_dark">#ff3e5059</color>
+    <color name="background_material_dark">#232E33</color>
+    <color name="background_floating_material_dark">#3E5059</color>
 
-    <color name="accent_material_700">#ff2e4978</color>
-    <color name="accent_material_light">#ff4285f4</color>
-    <color name="accent_material_dark">#ff5e97f6</color>
-    <color name="accent_material_50">#ffd0def7</color>
+    <color name="accent_material_700">#5385DB</color>
+    <color name="accent_material_light">#75A4F5</color>
+    <color name="accent_material_dark">#5E97F6</color>
+    <color name="accent_material_50">#93B7F5</color>
 
     <color name="primary_material_dark">#4D4D4D</color>
 
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 89691e9..8f0350a 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -28,10 +28,10 @@
     <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
     <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
 
-    <color name="accent_device_default_700">@color/material_deep_teal_700</color>
+    <color name="accent_device_default_700">@color/accent_material_700</color>
     <color name="accent_device_default_light">@color/accent_material_light</color>
     <color name="accent_device_default_dark">@color/accent_material_dark</color>
-    <color name="accent_device_default_50">@color/material_deep_teal_50</color>
+    <color name="accent_device_default_50">@color/accent_material_50</color>
 
     <color name="background_device_default_dark">@color/background_material_dark</color>
     <color name="background_device_default_light">@color/background_material_light</color>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 92426c6..37feff8 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -36,8 +36,10 @@
     <color name="tertiary_material_settings">@color/material_blue_grey_700</color>
     <color name="quaternary_material_settings">@color/material_blue_grey_200</color>
 
+    <color name="accent_material_700">@color/material_deep_teal_700</color>
     <color name="accent_material_light">@color/material_deep_teal_500</color>
     <color name="accent_material_dark">@color/material_deep_teal_200</color>
+    <color name="accent_material_50">@color/material_deep_teal_50</color>
 
     <color name="button_material_dark">#ff5a595b</color>
     <color name="button_material_light">#ffd6d7d7</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 52db0b9..7005afe 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2502,6 +2502,14 @@
          Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
     <integer name="config_defaultPictureInPictureGravity">0x55</integer>
 
+    <!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture.  Any
+         ratio smaller than this is considered too tall and thin to be usable. -->
+    <item name="config_pictureInPictureMinAspectRatio" format="float" type="dimen">0.5</item>
+
+    <!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture. Any
+         ratio larger than this is considered to wide and short to be usable. -->
+    <item name="config_pictureInPictureMaxAspectRatio" format="float" type="dimen">2.35</item>
+
     <!-- 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/symbols.xml b/core/res/res/values/symbols.xml
index 9f7b1ed..6c0dc35 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -314,6 +314,8 @@
   <java-symbol type="string" name="config_defaultPictureInPictureScreenEdgeInsets" />
   <java-symbol type="string" name="config_defaultPictureInPictureSize" />
   <java-symbol type="integer" name="config_defaultPictureInPictureGravity" />
+  <java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" />
+  <java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" />
   <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" />
diff --git a/docs/html/guide/topics/ui/settings.jd b/docs/html/guide/topics/ui/settings.jd
index 619fd26..b51e6d9 100644
--- a/docs/html/guide/topics/ui/settings.jd
+++ b/docs/html/guide/topics/ui/settings.jd
@@ -390,7 +390,9 @@
     <dd>The package part of the component name, as per the {@link
 android.content.Intent#setComponent setComponent()} method.</dd>
 </dl>
-
+<p class="note"><strong>Note: </strong>You must use string literals as the values for these
+intent attributes. You cannot use resource strings, such as <code>@string/foo</code>, to define the attributes.
+</p>
 
 
 <h2 id="Activity">Creating a Preference Activity</h2>
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 50bc6c3..59b1185 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -49,10 +49,60 @@
 }
 
 Typeface* gDefaultTypeface = NULL;
+pthread_once_t gDefaultTypefaceOnce = PTHREAD_ONCE_INIT;
+
+// This installs a default typeface (from a hardcoded path) that allows
+// layouts to work (not crash on null pointer) before the default
+// typeface is set. This happens if HWUI is used outside of zygote/app_process.
+static minikin::FontCollection *makeFontCollection() {
+    std::vector<minikin::FontFamily *>typefaces;
+    const char *fns[] = {
+        "/system/fonts/Roboto-Regular.ttf",
+    };
+
+    minikin::FontFamily *family = new minikin::FontFamily();
+    for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) {
+        const char *fn = fns[i];
+        ALOGD("makeFontCollection adding %s", fn);
+        sk_sp<SkTypeface> skFace = SkTypeface::MakeFromFile(fn);
+        if (skFace != NULL) {
+            // TODO: might be a nice optimization to get access to the underlying font
+            // data, but would require us opening the file ourselves and passing that
+            // to the appropriate Create method of SkTypeface.
+            minikin::MinikinFont *font = new MinikinFontSkia(std::move(skFace), NULL, 0, 0);
+            family->addFont(font);
+            font->Unref();
+        } else {
+            ALOGE("failed to create font %s", fn);
+        }
+    }
+    typefaces.push_back(family);
+
+    minikin::FontCollection *result = new minikin::FontCollection(typefaces);
+    family->Unref();
+    return result;
+}
+
+static void getDefaultTypefaceOnce() {
+  minikin::Layout::init();
+    if (gDefaultTypeface == NULL) {
+        // We expect the client to set a default typeface, but provide a
+        // default so we can make progress before that happens.
+        gDefaultTypeface = new Typeface;
+        gDefaultTypeface->fFontCollection = makeFontCollection();
+        gDefaultTypeface->fSkiaStyle = SkTypeface::kNormal;
+        gDefaultTypeface->fBaseWeight = 400;
+        resolveStyle(gDefaultTypeface);
+    }
+}
 
 Typeface* Typeface::resolveDefault(Typeface* src) {
-    LOG_ALWAYS_FATAL_IF(gDefaultTypeface == nullptr);
-    return src == nullptr ? gDefaultTypeface : src;
+    if (src == NULL) {
+        pthread_once(&gDefaultTypefaceOnce, getDefaultTypefaceOnce);
+        return gDefaultTypeface;
+    } else {
+        return src;
+    }
 }
 
 Typeface* Typeface::createFromTypeface(Typeface* src, SkTypeface::Style style) {
diff --git a/packages/PrintSpooler/res/values-bs-rBA/strings.xml b/packages/PrintSpooler/res/values-bs-rBA/strings.xml
index a50391f..2450be3 100644
--- a/packages/PrintSpooler/res/values-bs-rBA/strings.xml
+++ b/packages/PrintSpooler/res/values-bs-rBA/strings.xml
@@ -32,7 +32,7 @@
     <string name="template_page_range" msgid="428638530038286328">"Opseg od <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"npr. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Pregled prije štampanja"</string>
-    <string name="install_for_print_preview" msgid="6366303997385509332">"Instaliraj PDF preglednik za prikaz"</string>
+    <string name="install_for_print_preview" msgid="6366303997385509332">"Instaliraj PDF pregledavač za prikaz"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplikacija za štampanje je prestala raditi"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Kreiranje zadatka za štampu"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Sačuvaj kao PDF"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e07d865..0b5383a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -171,6 +171,8 @@
     <!-- shortcut manager -->
     <uses-permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING" />
 
+    <uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FragmentBase.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FragmentBase.java
new file mode 100644
index 0000000..af55e8b
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FragmentBase.java
@@ -0,0 +1,33 @@
+/*
+ * 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.plugins;
+
+import android.content.Context;
+import android.view.View;
+
+/**
+ * Interface to deal with lack of multiple inheritance
+ *
+ * This interface is designed to be used as a base class for plugin interfaces
+ * that need fragment methods. Plugins should not extend Fragment directly, so
+ * plugins that are fragments should be extending PluginFragment, but in SysUI
+ * these same versions should extend Fragment directly.
+ *
+ * Only methods that are on Fragment should be included here.
+ */
+public interface FragmentBase {
+    View getView();
+    Context getContext();
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
new file mode 100644
index 0000000..a9d1fa9
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
@@ -0,0 +1,77 @@
+/*
+ * 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.plugins;
+
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+
+public abstract class PluginFragment extends Fragment implements Plugin {
+
+    private static final String KEY_PLUGIN_PACKAGE = "plugin_package_name";
+    private Context mPluginContext;
+
+    @Override
+    public void onCreate(Context sysuiContext, Context pluginContext) {
+        mPluginContext = pluginContext;
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            Context sysuiContext = getContext();
+            Context pluginContext = recreatePluginContext(sysuiContext, savedInstanceState);
+            onCreate(sysuiContext, pluginContext);
+        }
+        if (mPluginContext == null) {
+            throw new RuntimeException("PluginFragments must call super.onCreate("
+                    + "Context sysuiContext, Context pluginContext)");
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString(KEY_PLUGIN_PACKAGE, getContext().getPackageName());
+    }
+
+    private Context recreatePluginContext(Context sysuiContext, Bundle savedInstanceState) {
+        final String pkg = savedInstanceState.getString(KEY_PLUGIN_PACKAGE);
+        try {
+            ApplicationInfo appInfo = sysuiContext.getPackageManager().getApplicationInfo(pkg, 0);
+            return PluginManager.getInstance(sysuiContext).getContext(appInfo, pkg);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException("Plugin with invalid package? " + pkg, e);
+        }
+    }
+
+    @Override
+    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
+        return super.getLayoutInflater(savedInstanceState).cloneInContext(mPluginContext);
+    }
+
+    /**
+     * Should only be called after {@link Plugin#onCreate(Context, Context)}.
+     */
+    @Override
+    public Context getContext() {
+        return mPluginContext != null ? mPluginContext : super.getContext();
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
index c64b188..62d3ce4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -158,7 +158,11 @@
                 case PLUGIN_DISCONNECTED:
                     if (DEBUG) Log.d(TAG, "onPluginDisconnected");
                     mListener.onPluginDisconnected((T) msg.obj);
-                    ((T) msg.obj).onDestroy();
+                    if (!(msg.obj instanceof PluginFragment)) {
+                        // Only call onDestroy for plugins that aren't fragments, as fragments
+                        // will get the onDestroy as part of the fragment lifecycle.
+                        ((T) msg.obj).onDestroy();
+                    }
                     break;
                 default:
                     super.handleMessage(msg);
@@ -186,7 +190,11 @@
                     for (int i = mPlugins.size() - 1; i >= 0; i--) {
                         PluginInfo<T> plugin = mPlugins.get(i);
                         mListener.onPluginDisconnected(plugin.mPlugin);
-                        plugin.mPlugin.onDestroy();
+                        if (!(plugin.mPlugin instanceof PluginFragment)) {
+                            // Only call onDestroy for plugins that aren't fragments, as fragments
+                            // will get the onDestroy as part of the fragment lifecycle.
+                            plugin.mPlugin.onDestroy();
+                        }
                     }
                     mPlugins.clear();
                     handleQueryPlugins(null);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
index 85f2e2a..60cf312 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
@@ -18,6 +18,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.Uri;
 import android.os.Build;
 import android.os.HandlerThread;
@@ -26,6 +28,7 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
 
 import dalvik.system.PathClassLoader;
 
@@ -163,6 +166,16 @@
         return mParentClassLoader;
     }
 
+    public Context getAllPluginContext(Context context) {
+        return new PluginContextWrapper(context,
+                new AllPluginClassLoader(context.getClassLoader()));
+    }
+
+    public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
+        ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
+        return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
+    }
+
     public static PluginManager getInstance(Context context) {
         if (sInstance == null) {
             sInstance = new PluginManager(context.getApplicationContext());
@@ -170,6 +183,28 @@
         return sInstance;
     }
 
+    private class AllPluginClassLoader extends ClassLoader {
+        public AllPluginClassLoader(ClassLoader classLoader) {
+            super(classLoader);
+        }
+
+        @Override
+        public Class<?> loadClass(String s) throws ClassNotFoundException {
+            try {
+                return super.loadClass(s);
+            } catch (ClassNotFoundException e) {
+                for (ClassLoader classLoader : mClassLoaders.values()) {
+                    try {
+                        return classLoader.loadClass(s);
+                    } catch (ClassNotFoundException e1) {
+                        // Will re-throw e if all fail.
+                    }
+                }
+                throw e;
+            }
+        }
+    }
+
     @VisibleForTesting
     public static class PluginInstanceManagerFactory {
         public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
@@ -180,7 +215,6 @@
         }
     }
 
-
     // This allows plugins to include any libraries or copied code they want by only including
     // classes from the plugin library.
     private static class ClassLoaderFilter extends ClassLoader {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
similarity index 93%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 4947863..a9874fc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -25,17 +25,21 @@
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
-public abstract class QSContainer extends FrameLayout {
+import com.android.systemui.plugins.FragmentBase;
+
+/**
+ * Fragment that contains QS in the notification shade.  Most of the interface is for
+ * handling the expand/collapsing of the view interaction.
+ */
+public interface QS extends FragmentBase {
 
     public static final String ACTION = "com.android.systemui.action.PLUGIN_QS";
 
     // This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader
     // change in incompatible ways.
-    public static final int VERSION = 3;
+    public static final int VERSION = 4;
 
-    public QSContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
+    String TAG = "QS";
 
     public abstract void setPanelView(HeightListener notificationPanelView);
     public abstract BaseStatusBarHeader getHeader();
diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml
index 7af247e..9ab8ac6 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -15,7 +15,7 @@
      limitations under the License.
 -->
 
-<!-- Height is 0 because it will be managed by the QSContainer manually -->
+<!-- Height is 0 because it will be managed by the QS manually -->
 <com.android.systemui.qs.customize.QSCustomizer
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 0339e03..f09657f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -39,15 +39,15 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
-        <com.android.systemui.PluginInflateContainer
-            android:id="@+id/qs_auto_reinflate_container"
+        <FrameLayout
+            android:id="@+id/qs_frame"
             android:layout="@layout/qs_panel"
             android:layout_width="@dimen/notification_panel_width"
             android:layout_height="match_parent"
             android:layout_gravity="@integer/notification_panel_layout_gravity"
             android:clipToPadding="false"
             android:clipChildren="false"
-            systemui:viewType="com.android.systemui.plugins.qs.QSContainer" />
+            systemui:viewType="com.android.systemui.plugins.qs.QS" />
 
         <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ea070b3..5294c9c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Maak <xliff:g id="ID_1">%s</xliff:g>-instellings oop."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Wysig volgorde van instellings."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Bladsy <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Vou uit"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 837d677..601930a 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"የ<xliff:g id="ID_1">%s</xliff:g> ቅንብሮችን ክፈት።"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"የቅንብሮድ ቅደም-ተከተል አርትዕ።"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"ገጽ <xliff:g id="ID_1">%1$d</xliff:g> ከ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ዘርጋ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index f207dbc1..502a94e 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -656,6 +656,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"فتح إعدادات <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"تعديل ترتيب الإعدادات."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"الصفحة <xliff:g id="ID_1">%1$d</xliff:g> من <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"توسيع"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index c4b0f73..1bc9e59 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ayarlarını açın."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Ayarların sıralanmasını redaktə edin."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> səhifədən <xliff:g id="ID_1">%1$d</xliff:g> səhifə"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Genişləndirin"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index e70e485..8105d9e 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Otvori podešavanja za <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Izmeni redosled podešavanja."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>. strana od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Proširi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-be-rBY/strings.xml b/packages/SystemUI/res/values-be-rBY/strings.xml
index 7f7a67f..83c67d4 100644
--- a/packages/SystemUI/res/values-be-rBY/strings.xml
+++ b/packages/SystemUI/res/values-be-rBY/strings.xml
@@ -654,6 +654,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Адкрыць налады <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Змяніць парадак налад."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Старонка <xliff:g id="ID_1">%1$d</xliff:g> з <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Разгарнуць"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 16dfeeb..bf3a614 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Отваряне на настройките за <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Редактиране на подредбата на настройките."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Страница <xliff:g id="ID_1">%1$d</xliff:g> от <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Разгъване"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index f8ee362..bd599bc 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> সেটিংস খুলুন৷"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ক্রম বা সেটিংস সম্পাদনা করুন৷"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g>টির মধ্যে <xliff:g id="ID_1">%1$d</xliff:g> নং পৃষ্ঠা"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"প্রসারিত করুন"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bs-rBA/strings.xml b/packages/SystemUI/res/values-bs-rBA/strings.xml
index 0e91713..5ba720c 100644
--- a/packages/SystemUI/res/values-bs-rBA/strings.xml
+++ b/packages/SystemUI/res/values-bs-rBA/strings.xml
@@ -652,6 +652,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Otvori postavke za: <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Urediti raspored postavki."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Stranica <xliff:g id="ID_1">%1$d</xliff:g> od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Proširi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index d258943..927e75b 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Obre la configuració per a <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Edita l\'ordre de la configuració."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Pàgina <xliff:g id="ID_1">%1$d</xliff:g> (<xliff:g id="ID_2">%2$d</xliff:g> en total)"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Desplega"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 845c8b5..69629a9 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -654,6 +654,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Otevřít nastavení aplikace <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Upravit pořadí nastavení."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Stránka <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Rozbalit"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index bb340a6..b0a828f 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Åbn <xliff:g id="ID_1">%s</xliff:g>-indstillinger."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Rediger rækkefølgen af indstillinger."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Side <xliff:g id="ID_1">%1$d</xliff:g> af <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Udvid"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index faabbfa..b3a457d 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Einstellungen für <xliff:g id="ID_1">%s</xliff:g> öffnen."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Reihenfolge der Einstellungen bearbeiten."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Seite <xliff:g id="ID_1">%1$d</xliff:g> von <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Maximieren"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 0f5f447..050d7f8 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Άνοιγμα ρυθμίσεων <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Επεξεργασία σειράς ρυθμίσεων."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Σελίδα <xliff:g id="ID_1">%1$d</xliff:g> από <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Ανάπτυξη"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 14d2a19..9aca3cc 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Open <xliff:g id="ID_1">%s</xliff:g> settings."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Edit order of settings."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Expand"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 14d2a19..9aca3cc 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Open <xliff:g id="ID_1">%s</xliff:g> settings."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Edit order of settings."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Expand"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 14d2a19..9aca3cc 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Open <xliff:g id="ID_1">%s</xliff:g> settings."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Edit order of settings."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Expand"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 3b92a3a..90c2ca1 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Abrir configuración de <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Editar orden de configuración"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Expandir"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 600d7d5..0a45ac1 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Abrir ajustes de <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Cambiar el orden de los ajustes."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Mostrar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 884eac5..84fadb2 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Ava teenuse <xliff:g id="ID_1">%s</xliff:g> seaded."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Muuda seadete järjestust."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Leht <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Laiendamine"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index b5e6696..d6578a2 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Ireki <xliff:g id="ID_1">%s</xliff:g> ezarpenak."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Editatu ezarpenen ordena."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g> orria"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Zabaldu"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 866904c..e00c5b5 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"باز کردن تنظیمات <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ویرایش ترتیب تنظیمات."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"صفحه <xliff:g id="ID_1">%1$d</xliff:g> از <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"بزرگ کردن"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 144704d..08572fe 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Avaa kohteen <xliff:g id="ID_1">%s</xliff:g> asetukset."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Muokkaa asetusten järjestystä."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Sivu <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Laajenna"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index b5c227e..830bfa7 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Ouvrir les paramètres <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Modifier l\'ordre des paramètres."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Page <xliff:g id="ID_1">%1$d</xliff:g> sur <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Développer"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 008d061..3b138c2 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Ouvrir les paramètres <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Modifier l\'ordre des paramètres."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Page <xliff:g id="ID_1">%1$d</xliff:g> sur <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Développer"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index cca60a8..579335d 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Abrir a configuración de <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Editar a orde das opcións de configuración."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Páxina <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Despregar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index 558a90a..48a6d0f 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> સેટિંગ્સ ખોલો."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"સેટિંગ્સનો ક્રમ સંપાદિત કરો."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> માંથી <xliff:g id="ID_1">%1$d</xliff:g> પૃષ્ઠ"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"વિસ્તૃત કરો"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 1fff051..f482caaa 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> सेटिंग खोलें."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"सेटिंग का क्रम संपादित करें."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"पृष्ठ <xliff:g id="ID_2">%2$d</xliff:g> में से <xliff:g id="ID_1">%1$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"विस्तृत करें"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index ae7e0c4..7ff63bd 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Otvaranje postavki za <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Uređivanje redoslijeda postavki."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Stranica <xliff:g id="ID_1">%1$d</xliff:g> od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Proširivanje"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index ec62e1c..bd0805b 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"A(z) <xliff:g id="ID_1">%s</xliff:g> beállításainak megnyitása."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Beállítások sorrendjének szerkesztése."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>. oldal, összesen: <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Kibontás"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 80cb977..2f5358e 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Բացել <xliff:g id="ID_1">%s</xliff:g> կարգավորումները:"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Խմբագրել կարգավորումների հերթականությունը:"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Էջ <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Ընդարձակել"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 93960e8..6444ae5 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Buka setelan <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Edit urutan setelan."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Halaman <xliff:g id="ID_1">%1$d</xliff:g> dari <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Luaskan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 71ef4bb..7e08a01 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Opna <xliff:g id="ID_1">%s</xliff:g> stillingar."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Breyta röð stillinga."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Blaðsíða <xliff:g id="ID_1">%1$d</xliff:g> af <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Stækka"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 12d0ed6..8ffb10d 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Apri le impostazioni <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Modifica l\'ordine delle impostazioni."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> di <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Espandi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index f565603..a6d7983 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -652,6 +652,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"פתיחת הגדרות של <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"עריכת סדר ההגדרות."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"דף <xliff:g id="ID_1">%1$d</xliff:g> מתוך <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"הרחב"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 37bea17..6ef9f73 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> の設定を開きます。"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"設定の順序を編集します。"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"ページ <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"展開"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index 15f5d63..695279d 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> პარამეტრების გახსნა."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"პარამეტრების მიმდევრობის რედაქტირება."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"გვერდი <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>-დან"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"გაშლა"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 477ab82..ba71182 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> параметрлерін ашу."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Параметрлер тәртібін өзгерту."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> ішінен <xliff:g id="ID_1">%1$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Жаю"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 45771d9..b222555 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"បើការកំណត់ <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"កែលំដាប់ការកំណត់"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"ទំព័រ <xliff:g id="ID_1">%1$d</xliff:g> នៃ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ពង្រីក"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 87c48dd..b2a6f27 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಕ್ರಮವನ್ನು ಎಡಿಟ್ ಮಾಡಿ."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> ರಲ್ಲಿ <xliff:g id="ID_1">%1$d</xliff:g> ಪುಟ"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ವಿಸ್ತೃತಗೊಳಿಸು"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 3e0cdfe..cefb57a 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> 설정 열기"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"설정 순서 수정"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g>페이지 중 <xliff:g id="ID_1">%1$d</xliff:g>페이지"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"펼치기"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index 2d8be38..e013c6b 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> жөндөөлөрүн ачуу."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Жөндөөлөрдүн иретин өзгөртүү."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> ичинен <xliff:g id="ID_1">%1$d</xliff:g>-бет"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Жайып көрсөтүү"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 93098ad..94e789a 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"ເປີດການຕັ້ງຄ່າ <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ແກ້ໄຂລຳດັບການຕັ້ງຄ່າ."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g> ຈາກທັງໝົດ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ຂະຫຍາຍ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 02e614c..5af5979 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -652,6 +652,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Atidaryti „<xliff:g id="ID_1">%s</xliff:g>“ nustatymus."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Redaguoti nustatymų tvarką."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g> psl. iš <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Išskleisti"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 82f5f22..503c8c8 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Atvērt <xliff:g id="ID_1">%s</xliff:g> iestatījumus."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Rediģēt iestatījumu secību."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>. lpp. no <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Izvērst"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index fcaa26f..62fc350 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Отворете ги поставките на <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Уредете го редоследот на поставките."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Страница <xliff:g id="ID_1">%1$d</xliff:g> од <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Проширете"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index 44d76d5..9e7d2f2 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ക്രമീകരണം തുറക്കുക."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ക്രമീകരണ ക്രമം എഡിറ്റുചെയ്യുക."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"പേജ് <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"വികസിപ്പിക്കുക"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 1c7bc38..04fb4c2 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> тохиргоог нээнэ үү."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Тохиргооны дарааллыг өөрчилнө үү."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g>-н <xliff:g id="ID_1">%1$d</xliff:g>-р хуудас"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Дэлгэх"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index 71d6677..a21019a 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> सेटिंग्ज उघडा."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"सेटिंग्जचा क्रम संपादित करा."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"पृष्ठ <xliff:g id="ID_2">%2$d</xliff:g> पैकी <xliff:g id="ID_1">%1$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"विस्तृत करा"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index bc9d79f..ce94295 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Buka tetapan <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Edit susunan tetapan."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Halaman <xliff:g id="ID_1">%1$d</xliff:g> daripada <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Kembangkan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 37c002c..8ab1de0 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ဆက်တင်များကို ဖွင့်ပါ။"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ဆက်တင်များ၏ အစီအစဉ်ကို တည်းဖြတ်ပါ။"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"စာမျက်နှာ <xliff:g id="ID_2">%2$d</xliff:g> အနက်မှ စာမျက်နှာ <xliff:g id="ID_1">%1$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ချဲ့ရန်"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 025c270..c6312e1 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Åpne <xliff:g id="ID_1">%s</xliff:g>-innstillingene."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Endre rekkefølgen på innstillingene."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Side <xliff:g id="ID_1">%1$d</xliff:g> av <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Vis"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 3ed5782..f87223c 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> सम्बन्धी सेटिङहरूलाई खोल्नुहोस्।"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"सेटिङहरूको क्रमलाई सम्पादन गर्नुहोस्।"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> मध्ये पृष्ठ <xliff:g id="ID_1">%1$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"विस्तृत गर्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 13ef0f6..7c12b5fd 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g>-instellingen openen."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Volgorde van instellingen bewerken."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Uitvouwen"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 553b604..f25662b 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ।"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ਸੈਟਿੰਗਾਂ ਦੇ ਕ੍ਰਮ ਦਾ ਸੰਪਾਦਨ ਕਰੋ।"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> ਦਾ <xliff:g id="ID_1">%1$d</xliff:g> ਪੰਨਾ"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ਵਿਸਤਾਰ ਕਰੋ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 5c7ea4f..d037cda 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Abrir as definições de <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Editar a ordem das definições."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Expandir"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 0d42645..e33ebb5 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -652,6 +652,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Deschideți setările <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Editați ordinea setărilor."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> din <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Extindeți"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 9adc9fe..0fbb0bd 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -654,6 +654,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Открыть настройки <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Изменить порядок быстрых настроек."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Страница <xliff:g id="ID_1">%1$d</xliff:g> из <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Развернуть"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index 6739ea1..c4d2378 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> සැකසීම් විවෘත කරන්න."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"සැකසීම්වල අනුපිළිවෙළ සංංස්කරණය කරන්න."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> න් <xliff:g id="ID_1">%1$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"දිග හරින්න"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 44881bd..23daf91 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -654,6 +654,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Otvoriť nastavenia <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Upraviť poradie nastavení"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Strana <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Rozbaliť"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index cc11704..90985e7 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -654,6 +654,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Odpri nastavitve za <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Uredi vrstni red nastavitev."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>. stran od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Razširi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index 55598bd..9616c07 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Hap cilësimet e <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Modifiko rendin e cilësimeve."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Faqja <xliff:g id="ID_1">%1$d</xliff:g> nga <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Zgjero"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 22bd853..667c65d 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Отвори подешавања за <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Измени редослед подешавања."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>. страна од <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Прошири"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 87acdb3..0a95017 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Öppna <xliff:g id="ID_1">%s</xliff:g>-inställningarna."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Ändra ordning på inställningarna."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Sida <xliff:g id="ID_1">%1$d</xliff:g> av <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Utöka"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index cc4391ce..afc5ee4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Fungua mipangilio ya <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Badilisha orodha ya mipangilio."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Ukurasa wa <xliff:g id="ID_1">%1$d</xliff:g> kati ya <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Panua"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 2d3c27f..99b8439 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> அமைப்புகளைத் திற."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"அமைப்புகளின் வரிசை முறையைத் திருத்து."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"பக்கம் <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"விரி"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index 9fa3f86..c1aeb09 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> సెట్టింగ్‌లను తెరవండి."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"సెట్టింగ్‌ల క్రమాన్ని సవరించండి."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g>లో <xliff:g id="ID_1">%1$d</xliff:g>వ పేజీ"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"విస్తరింపజేయి"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f59e69b..0c461b4 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"เปิดการตั้งค่า <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"แก้ไขลำดับการตั้งค่า"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"หน้า <xliff:g id="ID_1">%1$d</xliff:g> จาก <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"ขยาย"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 2d978c4..caa9f97 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Buksan ang mga setting ng <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"I-edit ang pagkakasunud-sunod ng mga setting."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Page <xliff:g id="ID_1">%1$d</xliff:g> ng <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Palawakin"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index e3827f7..030c97f 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ayarlarını aç."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Ayarların sırasını düzenle."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Sayfa <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Genişlet"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index fd627cc..c086fff 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -654,6 +654,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Відкрити налаштування <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Змінити порядок налаштувань."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Сторінка <xliff:g id="ID_1">%1$d</xliff:g> з <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Розгорнути"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index 415c5b6..ff46ad3 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ترتیبات کھولیں۔"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ترتیبات کی ترتیب میں ترمیم کریں۔"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"صفحہ <xliff:g id="ID_1">%1$d</xliff:g> از <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"پھیلائیں"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index aaea5e5..f2d2661 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> sozlamalarini ochish."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Sozlamalar tartibini o‘zgartirish."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_1">%1$d</xliff:g>-sahifa, jami: <xliff:g id="ID_2">%2$d</xliff:g> ta sahifa"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Yoyish"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 4ae3370..48b1e4f 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Mở cài đặt <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Chỉnh sửa thứ tự cài đặt."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Trang <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Mở rộng"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 7c7bc4a..94c3af1 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"打开<xliff:g id="ID_1">%s</xliff:g>设置。"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"修改设置顺序。"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"第 <xliff:g id="ID_1">%1$d</xliff:g> 页,共 <xliff:g id="ID_2">%2$d</xliff:g> 页"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"展开"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index e41a24b..5785af9 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -650,6 +650,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"開啟<xliff:g id="ID_1">%s</xliff:g>設定頁面。"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"編輯設定次序。"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁 (共 <xliff:g id="ID_2">%2$d</xliff:g> 頁)"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"展開"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 51e27a3..6562b39 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"開啟「<xliff:g id="ID_1">%s</xliff:g>」設定。"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"編輯設定順序。"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"展開"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 887da2e..e3c419b 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -648,6 +648,5 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Vula izilungiselelo ze-<xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Hlela uhlelo lwezilungiselelo."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Ikhasi <xliff:g id="ID_1">%1$d</xliff:g> kwangu-<xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <!-- no translation found for pip_phone_expand (5889780005575693909) -->
-    <skip />
+    <string name="pip_phone_expand" msgid="5889780005575693909">"Nweba"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9eea375..0f5d37e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -48,6 +48,12 @@
     <!-- Whether or not we show the number in the bar. -->
     <bool name="config_statusBarShowNumber">false</bool>
 
+    <!-- Vibrator pattern for camera gesture launch. -->
+    <integer-array translatable="false" name="config_cameraLaunchGestureVibePattern">
+        <item>0</item>
+        <item>400</item>
+    </integer-array>
+
     <!-- How many icons may be shown at once in the system bar. Includes any
          slots that may be reused for things like IME control. -->
     <integer name="config_maxNotificationIcons">5</integer>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 37a7e38..331d09e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1698,20 +1698,35 @@
         not appear on production builds ever. -->
     <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string>
 
-    <!-- PIP tap once to break through to the activity. Non-translatable since it should
+    <!-- PIP tap once to break through to the activity title. Non-translatable since it should
         not appear on production builds ever. -->
     <string name="pip_tap_through_title" translatable="false">Tap to interact</string>
 
-    <!-- PIP tap once to break through to the activity. Non-translatable since it should
+    <!-- PIP tap once to break through to the activity description. Non-translatable since it should
         not appear on production builds ever. -->
     <string name="pip_tap_through_summary" translatable="false">Tap once to interact with the activity</string>
 
-    <!-- PIP snap to closest edge. Non-translatable since it should
+    <!-- PIP snap to closest edge title. Non-translatable since it should
         not appear on production builds ever. -->
     <string name="pip_snap_mode_edge_title" translatable="false">Snap to closest edge</string>
 
-    <!-- PIP snap to closest edge. Non-translatable since it should
+    <!-- PIP snap to closest edge description. Non-translatable since it should
         not appear on production builds ever. -->
     <string name="pip_snap_mode_edge_summary" translatable="false">Snap to the closest edge</string>
 
+    <!-- PIP allow minimize title. Non-translatable since it should
+        not appear on production builds ever. -->
+    <string name="pip_allow_minimize_title" translatable="false">Allow PIP to minimize</string>
+
+    <!-- PIP allow minimize description. Non-translatable since it should
+        not appear on production builds ever. -->
+    <string name="pip_allow_minimize_summary" translatable="false">Allow PIP to minimize slightly offscreen</string>
+
+    <!-- Tuner string -->
+    <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
+    <!-- Tuner string -->
+    <string name="theme" translatable="false">Theme</string>
+    <!-- Tuner string -->
+    <string name="default_theme" translatable="false">Default</string>
+
 </resources>
diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml
index ce636cd..18cb930 100644
--- a/packages/SystemUI/res/xml/other_settings.xml
+++ b/packages/SystemUI/res/xml/other_settings.xml
@@ -23,5 +23,10 @@
             android:key="power_notification_controls"
             android:title="@string/tuner_full_importance_settings"
             android:fragment="com.android.systemui.tuner.PowerNotificationControlsFragment"/>
+e
+    <com.android.systemui.tuner.ThemePreference
+        android:key="theme"
+        android:title="@string/theme"
+        android:summary="%s" />
 
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index f09d6e9..74d5d6c 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -149,6 +149,12 @@
             android:summary="@string/pip_snap_mode_edge_summary"
             sysui:defValue="false" />
 
+        <com.android.systemui.tuner.TunerSwitch
+            android:key="pip_allow_minimize"
+            android:title="@string/pip_allow_minimize_title"
+            android:summary="@string/pip_allow_minimize_summary"
+            sysui:defValue="false" />
+
     </PreferenceScreen>
 
     <PreferenceScreen
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index e1d6a94..c4698c3 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -32,8 +32,8 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Handler;
-import android.os.Looper;
 import android.provider.Settings;
+
 import com.android.systemui.statusbar.policy.BatteryController;
 
 public class BatteryMeterDrawable extends Drawable implements
@@ -182,13 +182,13 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
         updateShowPercent();
-        mBatteryController.addStateChangedCallback(this);
+        mBatteryController.addCallback(this);
     }
 
     public void stopListening() {
         mListening = false;
         mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
-        mBatteryController.removeStateChangedCallback(this);
+        mBatteryController.removeCallback(this);
     }
 
     public void disableShowPercent() {
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 4f3ffde..ef1c25d 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.os.Handler;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.View;
@@ -73,7 +72,7 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mBatteryController.addStateChangedCallback(this);
+        mBatteryController.addCallback(this);
         mDrawable.startListening();
         TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
     }
@@ -81,7 +80,7 @@
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mBatteryController.removeStateChangedCallback(this);
+        mBatteryController.removeCallback(this);
         mDrawable.stopListening();
         TunerService.get(getContext()).removeTunable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 99e7876..6802fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -29,9 +29,11 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyboard.KeyboardUI;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.media.RingtonePlayer;
+import com.android.systemui.pip.PipUI;
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
@@ -42,7 +44,6 @@
 import com.android.systemui.statusbar.SystemBars;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.pip.PipUI;
 import com.android.systemui.usb.StorageNotification;
 import com.android.systemui.volume.VolumeUI;
 
@@ -61,6 +62,7 @@
      * The classes of the stuff to start.
      */
     private final Class<?>[] SERVICES = new Class[] {
+            FragmentService.class,
             TunerService.class,
             KeyguardViewMediator.class,
             Recents.class,
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
new file mode 100644
index 0000000..5f27b74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -0,0 +1,241 @@
+/*
+ * 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.fragments;
+
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.app.FragmentController;
+import android.app.FragmentHostCallback;
+import android.app.FragmentManager;
+import android.app.FragmentManager.FragmentLifecycleCallbacks;
+import android.app.FragmentManagerNonConfig;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.plugins.PluginManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class FragmentHostManager {
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Context mContext;
+    private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
+    private final View mRootView;
+    private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges();
+    private final FragmentService mManager;
+
+    private FragmentController mFragments;
+    private FragmentLifecycleCallbacks mLifecycleCallbacks;
+
+    FragmentHostManager(Context context, FragmentService manager, View rootView) {
+        mContext = PluginManager.getInstance(context).getAllPluginContext(context);
+        mManager = manager;
+        mRootView = rootView;
+        mConfigChanges.applyNewConfig(context.getResources());
+        createFragmentHost(null);
+    }
+
+    private void createFragmentHost(Parcelable savedState) {
+        mFragments = FragmentController.createController(new HostCallbacks());
+        mFragments.attachHost(null);
+        // TODO: Remove non-staticness from FragmentLifecycleCallbacks (hopefully).
+        mLifecycleCallbacks = mFragments.getFragmentManager().new FragmentLifecycleCallbacks() {
+            @Override
+            public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v,
+                    Bundle savedInstanceState) {
+                FragmentHostManager.this.onFragmentViewCreated(f);
+            }
+
+            @Override
+            public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
+                FragmentHostManager.this.onFragmentViewDestroyed(f);
+            }
+        };
+        mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
+                true);
+        if (savedState != null) {
+            mFragments.restoreAllState(savedState, (FragmentManagerNonConfig) null);
+        }
+        // For now just keep all fragments in the resumed state.
+        mFragments.dispatchCreate();
+        mFragments.dispatchStart();
+        mFragments.dispatchResume();
+    }
+
+    private Parcelable destroyFragmentHost() {
+        mFragments.dispatchPause();
+        Parcelable p = mFragments.saveAllState();
+        mFragments.dispatchStop();
+        mFragments.dispatchDestroy();
+        mFragments.getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
+        return p;
+    }
+
+    public void addTagListener(String tag, FragmentListener listener) {
+        ArrayList<FragmentListener> listeners = mListeners.get(tag);
+        if (listeners == null) {
+            listeners = new ArrayList<>();
+            mListeners.put(tag, listeners);
+        }
+        listeners.add(listener);
+        Fragment current = getFragmentManager().findFragmentByTag(tag);
+        if (current != null && current.getView() != null) {
+            listener.onFragmentViewCreated(tag, current);
+        }
+    }
+
+    // Shouldn't generally be needed, included for completeness sake.
+    public void removeTagListener(String tag, FragmentListener listener) {
+        ArrayList<FragmentListener> listeners = mListeners.get(tag);
+        if (listeners != null && listeners.remove(listener) && listeners.size() == 0) {
+            mListeners.remove(tag);
+        }
+    }
+
+    private void onFragmentViewCreated(Fragment fragment) {
+        String tag = fragment.getTag();
+
+        ArrayList<FragmentListener> listeners = mListeners.get(tag);
+        if (listeners != null) {
+            listeners.forEach((listener) -> listener.onFragmentViewCreated(tag, fragment));
+        }
+    }
+
+    private void onFragmentViewDestroyed(Fragment fragment) {
+        String tag = fragment.getTag();
+
+        ArrayList<FragmentListener> listeners = mListeners.get(tag);
+        if (listeners != null) {
+            listeners.forEach((listener) -> listener.onFragmentViewDestroyed(tag, fragment));
+        }
+    }
+
+    /**
+     * Called when the configuration changed, return true if the fragments
+     * should be recreated.
+     */
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+            // Save the old state.
+            Parcelable p = destroyFragmentHost();
+            // Generate a new fragment host and restore its state.
+            createFragmentHost(p);
+        } else {
+            mFragments.dispatchConfigurationChanged(newConfig);
+        }
+    }
+
+    private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        // TODO: Do something?
+    }
+
+    private View findViewById(int id) {
+        return mRootView.findViewById(id);
+    }
+
+    /**
+     * Note: Values from this shouldn't be cached as they can change after config changes.
+     */
+    public FragmentManager getFragmentManager() {
+        return mFragments.getFragmentManager();
+    }
+
+    public interface FragmentListener {
+        void onFragmentViewCreated(String tag, Fragment fragment);
+
+        // The facts of lifecycle
+        // When a fragment is destroyed, you should not talk to it any longer.
+        default void onFragmentViewDestroyed(String tag, Fragment fragment) {
+        }
+    }
+
+    public static FragmentHostManager get(View view) {
+        try {
+            return ((SystemUIApplication) view.getContext().getApplicationContext())
+                    .getComponent(FragmentService.class).getFragmentHostManager(view);
+        } catch (ClassCastException e) {
+            // TODO: Some auto handling here?
+            throw e;
+        }
+    }
+
+    class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
+        public HostCallbacks() {
+            super(mContext, FragmentHostManager.this.mHandler, 0);
+        }
+
+        @Override
+        public FragmentHostManager onGetHost() {
+            return FragmentHostManager.this;
+        }
+
+        @Override
+        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+            FragmentHostManager.this.dump(prefix, fd, writer, args);
+        }
+
+        @Override
+        public boolean onShouldSaveFragmentState(Fragment fragment) {
+            return true; // True for now.
+        }
+
+        @Override
+        public LayoutInflater onGetLayoutInflater() {
+            return LayoutInflater.from(mContext);
+        }
+
+        @Override
+        public boolean onUseFragmentManagerInflaterFactory() {
+            return true;
+        }
+
+        @Override
+        public boolean onHasWindowAnimations() {
+            return false;
+        }
+
+        @Override
+        public int onGetWindowAnimations() {
+            return 0;
+        }
+
+        @Override
+        public void onAttachFragment(Fragment fragment) {
+        }
+
+        @Override
+        @Nullable
+        public View onFindViewById(int id) {
+            return FragmentHostManager.this.findViewById(id);
+        }
+
+        @Override
+        public boolean onHasView() {
+            return true;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
new file mode 100644
index 0000000..85cde10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -0,0 +1,82 @@
+/*
+ * 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.fragments;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIApplication;
+
+/**
+ * Holds a map of root views to FragmentHostStates and generates them as needed.
+ * Also dispatches the configuration changes to all current FragmentHostStates.
+ */
+public class FragmentService extends SystemUI {
+
+    private static final String TAG = "FragmentService";
+
+    private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>();
+    private final Handler mHandler = new Handler();
+
+    @Override
+    public void start() {
+        putComponent(FragmentService.class, this);
+    }
+
+    public FragmentHostManager getFragmentHostManager(View view) {
+        View root = view.getRootView();
+        FragmentHostState state = mHosts.get(root);
+        if (state == null) {
+            state = new FragmentHostState(root);
+            mHosts.put(root, state);
+        }
+        return state.getFragmentHostManager();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        for (FragmentHostState state : mHosts.values()) {
+            state.sendConfigurationChange(newConfig);
+        }
+    }
+
+    private class FragmentHostState {
+        private final View mView;
+
+        private FragmentHostManager mFragmentHostManager;
+
+        public FragmentHostState(View view) {
+            mView = view;
+            mFragmentHostManager = new FragmentHostManager(mContext, FragmentService.this, mView);
+        }
+
+        public void sendConfigurationChange(Configuration newConfig) {
+            mHandler.post(() -> handleSendConfigurationChange(newConfig));
+        }
+
+        public FragmentHostManager getFragmentHostManager() {
+            return mFragmentHostManager;
+        }
+
+        private void handleSendConfigurationChange(Configuration newConfig) {
+            mFragmentHostManager.onConfigurationChanged(newConfig);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
new file mode 100644
index 0000000..e107fcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -0,0 +1,86 @@
+/*
+ * 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.fragments;
+
+import android.app.Fragment;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.plugins.FragmentBase;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
+public class PluginFragmentListener implements PluginListener<Plugin> {
+
+    private static final String TAG = "PluginFragmentListener";
+
+    private final FragmentHostManager mFragmentHostManager;
+    private final PluginManager mPluginManager;
+    private final Class<? extends Fragment> mDefaultClass;
+    private final int mId;
+    private final String mTag;
+    private final Class<? extends FragmentBase> mExpectedInterface;
+
+    public PluginFragmentListener(View view, String tag, int id,
+            Class<? extends Fragment> defaultFragment,
+            Class<? extends FragmentBase> expectedInterface) {
+        mFragmentHostManager = FragmentHostManager.get(view);
+        mPluginManager = PluginManager.getInstance(view.getContext());
+        mExpectedInterface = expectedInterface;
+        mTag = tag;
+        mDefaultClass = defaultFragment;
+        mId = id;
+    }
+
+    public void startListening(String action, int version) {
+        try {
+            setFragment(mDefaultClass.newInstance());
+        } catch (InstantiationException | IllegalAccessException e) {
+            Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
+        }
+        mPluginManager.addPluginListener(action, this, version, false /* Only allow one */);
+    }
+
+    public void stopListening() {
+        mPluginManager.removePluginListener(this);
+    }
+
+    private void setFragment(Fragment fragment) {
+        mFragmentHostManager.getFragmentManager().beginTransaction()
+                .replace(mId, fragment, mTag)
+                .commit();
+    }
+
+    @Override
+    public void onPluginConnected(Plugin plugin) {
+        try {
+            mExpectedInterface.cast(plugin);
+            setFragment((Fragment) plugin);
+        } catch (ClassCastException e) {
+            Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
+                    + mExpectedInterface.getName(), e);
+        }
+    }
+
+    @Override
+    public void onPluginDisconnected(Plugin plugin) {
+        try {
+            setFragment(mDefaultClass.newInstance());
+        } catch (InstantiationException | IllegalAccessException e) {
+            Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9ae341a..8f1a943 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -285,6 +285,7 @@
     private LockPatternUtils mLockPatternUtils;
     private boolean mKeyguardDonePending = false;
     private boolean mHideAnimationRun = false;
+    private boolean mHideAnimationRunning = false;
 
     private SoundPool mLockSounds;
     private int mLockSoundId;
@@ -515,9 +516,7 @@
                 return;
             }
 
-            if (!mKeyguardDonePending) {
-                KeyguardViewMediator.this.handleKeyguardDone(true /* authenticated */);
-            }
+            tryKeyguardDone(true);
             if (strongAuth) {
                 mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
             }
@@ -545,7 +544,8 @@
 
             mKeyguardDonePending = true;
             mHideAnimationRun = true;
-            mStatusBarKeyguardViewManager.startPreHideAnimation(null /* finishRunnable */);
+            mHideAnimationRunning = true;
+            mStatusBarKeyguardViewManager.startPreHideAnimation(mHideAnimationFinishedRunnable);
             mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_PENDING_TIMEOUT,
                     KEYGUARD_DONE_PENDING_TIMEOUT_MS);
             if (strongAuth) {
@@ -565,9 +565,11 @@
         public void readyForKeyguardDone() {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#readyForKeyguardDone");
             if (mKeyguardDonePending) {
+                mKeyguardDonePending = false;
+
                 // Somebody has called keyguardDonePending before, which means that we are
                 // authenticated
-                KeyguardViewMediator.this.handleKeyguardDone(true /* authenticated */);
+                tryKeyguardDone(true);
             }
             Trace.endSection();
         }
@@ -643,9 +645,7 @@
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure...
         setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
-        updateInputRestrictedLocked();
-        mTrustManager.reportKeyguardShowingChanged();
+                KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
 
         mStatusBarKeyguardViewManager =
                 SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
@@ -1257,7 +1257,7 @@
      */
     public void handleDismiss(boolean allowWhileOccluded) {
         if (mShowing && (allowWhileOccluded || !mOccluded)) {
-            mStatusBarKeyguardViewManager.dismiss();
+            mStatusBarKeyguardViewManager.dismissAndCollapse();
         }
     }
 
@@ -1493,6 +1493,16 @@
         }
     };
 
+    private void tryKeyguardDone(boolean authenticated) {
+        if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
+            handleKeyguardDone(authenticated);
+        } else if (!mHideAnimationRun) {
+            mHideAnimationRun = true;
+            mHideAnimationRunning = true;
+            mStatusBarKeyguardViewManager.startPreHideAnimation(mHideAnimationFinishedRunnable);
+        }
+    }
+
     /**
      * @see #keyguardDone
      * @see #KEYGUARD_DONE
@@ -1679,6 +1689,11 @@
         }
     };
 
+    private final Runnable mHideAnimationFinishedRunnable = () -> {
+        mHideAnimationRunning = false;
+        tryKeyguardDone(true);
+    };
+
     /**
      * Handle message sent by {@link #hideLocked()}
      * @see #HIDE
@@ -1697,16 +1712,10 @@
                 return;
             }
             mHiding = true;
-            if (mShowing && !mOccluded) {
-                if (!mHideAnimationRun) {
-                    mStatusBarKeyguardViewManager.startPreHideAnimation(mKeyguardGoingAwayRunnable);
-                } else {
-                    mKeyguardGoingAwayRunnable.run();
-                }
-            } else {
 
-                // Don't try to rely on WindowManager - if Keyguard wasn't showing, window
-                // manager won't start the exit animation.
+            if (mShowing && !mOccluded) {
+                mKeyguardGoingAwayRunnable.run();
+            } else {
                 handleStartKeyguardExitAnimation(
                         SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                         mHideAnimation.getDuration());
@@ -1806,7 +1815,7 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
             setShowingLocked(true);
-            mStatusBarKeyguardViewManager.verifyUnlock();
+            mStatusBarKeyguardViewManager.dismissAndCollapse();
         }
         Trace.endSection();
     }
@@ -1969,7 +1978,11 @@
     }
 
     private void setShowingLocked(boolean showing) {
-        if (showing != mShowing) {
+        setShowingLocked(showing, false /* forceCallbacks */);
+    }
+
+    private void setShowingLocked(boolean showing, boolean forceCallbacks) {
+        if (showing != mShowing || forceCallbacks) {
             mShowing = showing;
             int size = mKeyguardStateCallbacks.size();
             for (int i = size - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java
new file mode 100644
index 0000000..e8e8a4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java
@@ -0,0 +1,42 @@
+/*
+ * 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.pip.phone;
+
+/**
+ * A generic interface for a touch gesture.
+ */
+public abstract class PipTouchGesture {
+
+    /**
+     * Handle the touch down.
+     */
+    void onDown(PipTouchState touchState) {}
+
+    /**
+     * Handle the touch move, and return whether the event was consumed.
+     */
+    boolean onMove(PipTouchState touchState) {
+        return false;
+    }
+
+    /**
+     * Handle the touch up, and return whether the gesture was consumed.
+     */
+    boolean onUp(PipTouchState touchState) {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index a359380..b24d199 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -30,9 +31,9 @@
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
@@ -43,7 +44,6 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import com.android.internal.os.BackgroundThread;
@@ -64,10 +64,19 @@
     private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss";
     private static final String TUNER_KEY_TAP_THROUGH = "pip_tap_through";
     private static final String TUNER_KEY_SNAP_MODE_EDGE = "pip_snap_mode_edge";
+    private static final String TUNER_KEY_ALLOW_MINIMIZE = "pip_allow_minimize";
 
     private static final int SNAP_STACK_DURATION = 225;
     private static final int DISMISS_STACK_DURATION = 375;
     private static final int EXPAND_STACK_DURATION = 225;
+    private static final int MINIMIZE_STACK_MAX_DURATION = 200;
+
+    // The fraction of the stack width to show when minimized
+    private static final float MINIMIZED_VISIBLE_FRACTION = 0.25f;
+    // The fraction of the stack width that the user has to drag offscreen to minimize the PIP
+    private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.15f;
+    // The fraction of the stack width that the user has to move when flinging to dismiss the PIP
+    private static final float DISMISS_FLING_DISTANCE_FRACTION = 0.3f;
 
     private final Context mContext;
     private final IActivityManager mActivityManager;
@@ -83,10 +92,16 @@
     private final PipSnapAlgorithm mSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
 
+    // Allow swiping offscreen to dismiss the PIP
     private boolean mEnableSwipeToDismiss = true;
+    // Allow dragging the PIP to a location to close it
     private boolean mEnableDragToDismiss = true;
+    // Allow tapping on the PIP to show additional controls
     private boolean mEnableTapThrough = false;
+    // Allow snapping the PIP to the closest edge and not the corners of the screen
     private boolean mEnableSnapToEdge = false;
+    // Allow the PIP to be "docked" slightly offscreen
+    private boolean mEnableMinimizing = false;
 
     private final Rect mPinnedStackBounds = new Rect();
     private final Rect mBoundedPinnedStackBounds = new Rect();
@@ -99,16 +114,16 @@
         }
     };
 
-    private final PointF mDownTouch = new PointF();
-    private final PointF mLastTouch = new PointF();
-    private boolean mIsDragging;
-    private boolean mIsSwipingToDismiss;
+    // Behaviour states
     private boolean mIsTappingThrough;
-    private int mActivePointerId;
+    private boolean mIsMinimized;
 
+    // Touch state
+    private final PipTouchState mTouchState;
     private final FlingAnimationUtils mFlingAnimationUtils;
-    private VelocityTracker mVelocityTracker;
+    private final PipTouchGesture[] mGestures;
 
+    // Temporary vars
     private final Rect mTmpBounds = new Rect();
 
     /**
@@ -183,13 +198,19 @@
         mMenuController.addListener(mMenuListener);
         mDismissViewController = new PipDismissViewController(context);
         mSnapAlgorithm = new PipSnapAlgorithm(mContext);
+        mTouchState = new PipTouchState(mViewConfig);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+        mGestures = new PipTouchGesture[]{
+                mDragToDismissGesture, mSwipeToDismissGesture, mTapThroughGesture, mMinimizeGesture,
+                mDefaultMovementGesture
+        };
         mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
         registerInputConsumer();
 
         // Register any tuner settings changes
         TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS,
-            TUNER_KEY_DRAG_TO_DISMISS, TUNER_KEY_TAP_THROUGH, TUNER_KEY_SNAP_MODE_EDGE);
+            TUNER_KEY_DRAG_TO_DISMISS, TUNER_KEY_TAP_THROUGH, TUNER_KEY_SNAP_MODE_EDGE,
+                TUNER_KEY_ALLOW_MINIMIZE);
     }
 
     @Override
@@ -198,6 +219,8 @@
             // Reset back to default
             mEnableSwipeToDismiss = true;
             mEnableDragToDismiss = true;
+            mEnableMinimizing = false;
+            setMinimizedState(false);
             mEnableTapThrough = false;
             mIsTappingThrough = false;
             mEnableSnapToEdge = false;
@@ -211,6 +234,9 @@
             case TUNER_KEY_DRAG_TO_DISMISS:
                 mEnableDragToDismiss = Integer.parseInt(newValue) != 0;
                 break;
+            case TUNER_KEY_ALLOW_MINIMIZE:
+                mEnableMinimizing = Integer.parseInt(newValue) != 0;
+                break;
             case TUNER_KEY_TAP_THROUGH:
                 mEnableTapThrough = Integer.parseInt(newValue) != 0;
                 mIsTappingThrough = false;
@@ -233,6 +259,9 @@
             return true;
         }
 
+        // Update the touch state
+        mTouchState.onTouchEvent(ev);
+
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
                 // Cancel any existing animations on the pinned stack
@@ -241,173 +270,58 @@
                 }
 
                 updateBoundedPinnedStackBounds(true /* updatePinnedStackBounds */);
-                initOrResetVelocityTracker();
-                mVelocityTracker.addMovement(ev);
-                mActivePointerId = ev.getPointerId(0);
-                mLastTouch.set(ev.getX(), ev.getY());
-                mDownTouch.set(mLastTouch);
-                mIsDragging = false;
+                for (PipTouchGesture gesture : mGestures) {
+                    gesture.onDown(mTouchState);
+                }
                 try {
                     mPinnedStackController.setInInteractiveMode(true);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Could not set dragging state", e);
                 }
-                if (mEnableDragToDismiss) {
-                    // TODO: Consider setting a timer such at after X time, we show the dismiss
-                    //       target if the user hasn't already dragged some distance
-                    mDismissViewController.createDismissTarget();
-                }
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
-                // Update the velocity tracker
-                mVelocityTracker.addMovement(ev);
-
-                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                float x = ev.getX(activePointerIndex);
-                float y = ev.getY(activePointerIndex);
-                float left = mPinnedStackBounds.left + (x - mLastTouch.x);
-                float top = mPinnedStackBounds.top + (y - mLastTouch.y);
-
-                if (!mIsDragging) {
-                    // Check if the pointer has moved far enough
-                    float movement = PointF.length(mDownTouch.x - x, mDownTouch.y - y);
-                    if (movement > mViewConfig.getScaledTouchSlop()) {
-                        mIsDragging = true;
-                        mIsTappingThrough = false;
-                        mMenuController.hideMenu();
-                        if (mEnableSwipeToDismiss) {
-                            // TODO: this check can have some buffer so that we only start swiping
-                            //       after a significant move out of bounds
-                            mIsSwipingToDismiss = !(mBoundedPinnedStackBounds.left <= left &&
-                                    left <= mBoundedPinnedStackBounds.right) &&
-                                    Math.abs(mDownTouch.x - x) > Math.abs(y - mLastTouch.y);
-                        }
-                        if (mEnableDragToDismiss) {
-                            mDismissViewController.showDismissTarget();
-                        }
+                for (PipTouchGesture gesture : mGestures) {
+                    if (gesture.onMove(mTouchState)) {
+                        break;
                     }
                 }
-
-                if (mIsSwipingToDismiss) {
-                    // Ignore the vertical movement
-                    mTmpBounds.set(mPinnedStackBounds);
-                    mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top);
-                    if (!mTmpBounds.equals(mPinnedStackBounds)) {
-                        mPinnedStackBounds.set(mTmpBounds);
-                        mMotionHelper.resizeToBounds(mPinnedStackBounds);
-                    }
-                } else if (mIsDragging) {
-                    // Move the pinned stack
-                    if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
-                        left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
-                                mBoundedPinnedStackBounds.right, left));
-                        top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
-                                mBoundedPinnedStackBounds.bottom, top));
-                    }
-                    mTmpBounds.set(mPinnedStackBounds);
-                    mTmpBounds.offsetTo((int) left, (int) top);
-                    if (!mTmpBounds.equals(mPinnedStackBounds)) {
-                        mPinnedStackBounds.set(mTmpBounds);
-                        mMotionHelper.resizeToBounds(mPinnedStackBounds);
-                    }
-                }
-                mLastTouch.set(ev.getX(), ev.getY());
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_UP: {
-                // Update the velocity tracker
-                mVelocityTracker.addMovement(ev);
-
-                int pointerIndex = ev.getActionIndex();
-                int pointerId = ev.getPointerId(pointerIndex);
-                if (pointerId == mActivePointerId) {
-                    // Select a new active pointer id and reset the movement state
-                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
-                    mActivePointerId = ev.getPointerId(newPointerIndex);
-                    mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
-                }
                 break;
             }
             case MotionEvent.ACTION_UP: {
-                // Update the velocity tracker
-                mVelocityTracker.addMovement(ev);
-                mVelocityTracker.computeCurrentVelocity(1000,
-                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
-                float velocityX = mVelocityTracker.getXVelocity();
-                float velocityY = mVelocityTracker.getYVelocity();
-                float velocity = PointF.length(velocityX, velocityY);
-
                 // Update the movement bounds again if the state has changed since the user started
                 // dragging (ie. when the IME shows)
                 updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);
 
-                if (mIsSwipingToDismiss) {
-                    if (Math.abs(velocityX) > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-                        flingToDismiss(velocityX);
-                    } else {
-                        animateToClosestSnapTarget();
+                for (PipTouchGesture gesture : mGestures) {
+                    if (gesture.onUp(mTouchState)) {
+                        break;
                     }
-                } else if (mIsDragging) {
-                    if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-                        flingToSnapTarget(velocity, velocityX, velocityY);
-                    } else {
-                        int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                        int x = (int) ev.getX(activePointerIndex);
-                        int y = (int) ev.getY(activePointerIndex);
-                        Rect dismissBounds = mEnableDragToDismiss
-                                ? mDismissViewController.getDismissBounds()
-                                : null;
-                        if (dismissBounds != null && dismissBounds.contains(x, y)) {
-                            animateDismissPinnedStack(dismissBounds);
-                        } else {
-                            animateToClosestSnapTarget();
-                        }
-                    }
-                } else {
-                    if (mEnableTapThrough) {
-                        if (!mIsTappingThrough) {
-                            mMenuController.showMenu();
-                            mIsTappingThrough = true;
-                        }
-                    } else {
-                        expandPinnedStackToFullscreen();
-                    }
-                }
-                if (mEnableDragToDismiss) {
-                    mDismissViewController.destroyDismissTarget();
                 }
 
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
-                mIsDragging = false;
-                mIsSwipingToDismiss = false;
                 try {
                     mPinnedStackController.setInInteractiveMode(false);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Could not set dragging state", e);
                 }
-                recycleVelocityTracker();
                 break;
             }
         }
         return !mIsTappingThrough;
     }
 
-    private void initOrResetVelocityTracker() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        } else {
-            mVelocityTracker.clear();
-        }
-    }
-
-    private void recycleVelocityTracker() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
+    /**
+     * @return whether the current touch state is a horizontal drag offscreen.
+     */
+    private boolean isDraggingOffscreen(PipTouchState touchState) {
+        PointF lastDelta = touchState.getLastTouchDelta();
+        PointF downDelta = touchState.getDownTouchDelta();
+        float left = mPinnedStackBounds.left + lastDelta.x;
+        return !(mBoundedPinnedStackBounds.left <= left && left <= mBoundedPinnedStackBounds.right)
+                && Math.abs(downDelta.x) > Math.abs(downDelta.y);
     }
 
     /**
@@ -449,6 +363,74 @@
     }
 
     /**
+     * Sets the minimized state and notifies the controller.
+     */
+    private void setMinimizedState(boolean isMinimized) {
+        mIsMinimized = isMinimized;
+        try {
+            mPinnedStackController.setIsMinimized(isMinimized);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not set minimized state", e);
+        }
+    }
+
+    /**
+     * @return whether the given {@param pinnedStackBounds} indicates the PIP should be minimized.
+     */
+    private boolean shouldMinimizedPinnedStack() {
+        Point displaySize = new Point();
+        mContext.getDisplay().getRealSize(displaySize);
+        if (mPinnedStackBounds.left < 0) {
+            float offscreenFraction = (float) -mPinnedStackBounds.left / mPinnedStackBounds.width();
+            return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
+        } else if (mPinnedStackBounds.right > displaySize.x) {
+            float offscreenFraction = (float) (mPinnedStackBounds.right - displaySize.x) /
+                    mPinnedStackBounds.width();
+            return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Flings the minimized PIP to the closest minimized snap target.
+     */
+    private void flingToMinimizedSnapTarget(float velocityY) {
+        Rect movementBounds = new Rect(mPinnedStackBounds.left, mBoundedPinnedStackBounds.top,
+                mPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom);
+        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mPinnedStackBounds,
+                0 /* velocityX */, velocityY);
+        if (!mPinnedStackBounds.equals(toBounds)) {
+            mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
+                    toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener);
+            mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
+                    distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
+                    velocityY);
+            mPinnedStackBoundsAnimator.start();
+        }
+    }
+
+    /**
+     * Animates the PIP to the minimized state, slightly offscreen.
+     */
+    private void animateToClosestMinimizedTarget() {
+        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
+                mPinnedStackBounds);
+        Point displaySize = new Point();
+        mContext.getDisplay().getRealSize(displaySize);
+        int visibleWidth = (int) (MINIMIZED_VISIBLE_FRACTION * mPinnedStackBounds.width());
+        if (mPinnedStackBounds.left < 0) {
+            toBounds.offsetTo(-toBounds.width() + visibleWidth, toBounds.top);
+        } else if (mPinnedStackBounds.right > displaySize.x) {
+            toBounds.offsetTo(displaySize.x - visibleWidth, toBounds.top);
+        }
+        mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
+                toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN,
+                mUpdatePinnedStackBoundsListener);
+        mPinnedStackBoundsAnimator.start();
+    }
+
+    /**
      * Flings the PIP to the closest snap target.
      */
     private void flingToSnapTarget(float velocity, float velocityX, float velocityY) {
@@ -478,12 +460,26 @@
     }
 
     /**
+     * @return whether the velocity is coincident with the current pinned stack bounds to be
+     *         considered a fling to dismiss.
+     */
+    private boolean isFlingToDismiss(float velocityX) {
+        Point displaySize = new Point();
+        mContext.getDisplay().getRealSize(displaySize);
+        return (mPinnedStackBounds.right > displaySize.x && velocityX > 0) ||
+                (mPinnedStackBounds.left < 0 && velocityX < 0);
+    }
+
+    /**
      * Flings the PIP to dismiss it offscreen.
      */
     private void flingToDismiss(float velocityX) {
+        Point displaySize = new Point();
+        mContext.getDisplay().getRealSize(displaySize);
         float offsetX = velocityX > 0
-            ? mBoundedPinnedStackBounds.right + 2 * mPinnedStackBounds.width()
-            : mBoundedPinnedStackBounds.left - 2 * mPinnedStackBounds.width();
+                ? displaySize.x + mPinnedStackBounds.width()
+                : -mPinnedStackBounds.width();
+
         Rect toBounds = new Rect(mPinnedStackBounds);
         toBounds.offsetTo((int) offsetX, toBounds.top);
         if (!mPinnedStackBounds.equals(toBounds)) {
@@ -495,13 +491,7 @@
             mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    BackgroundThread.getHandler().post(() -> {
-                        try {
-                            mActivityManager.removeStack(PINNED_STACK_ID);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Failed to remove PIP", e);
-                        }
-                    });
+                    BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack);
                 }
             });
             mPinnedStackBoundsAnimator.start();
@@ -521,13 +511,7 @@
         mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                BackgroundThread.getHandler().post(() -> {
-                    try {
-                        mActivityManager.removeStack(PINNED_STACK_ID);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to remove PIP", e);
-                    }
-                });
+                BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack);
             }
         });
         mPinnedStackBoundsAnimator.start();
@@ -549,6 +533,27 @@
     }
 
     /**
+     * Tries to the move the pinned stack to the given {@param bounds}.
+     */
+    private void movePinnedStack(Rect bounds) {
+        if (!bounds.equals(mPinnedStackBounds)) {
+            mPinnedStackBounds.set(bounds);
+            mMotionHelper.resizeToBounds(mPinnedStackBounds);
+        }
+    }
+
+    /**
+     * Dismisses the pinned stack.
+     */
+    private void dismissPinnedStack() {
+        try {
+            mActivityManager.removeStack(PINNED_STACK_ID);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to remove PIP", e);
+        }
+    }
+
+    /**
      * Updates the movement bounds of the pinned stack.
      */
     private void updateBoundedPinnedStackBounds(boolean updatePinnedStackBounds) {
@@ -572,4 +577,231 @@
     private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
         return PointF.length(r1.left - r2.left, r1.top - r2.top);
     }
+
+    /**
+     * Gesture controlling dragging over a target to dismiss the PIP.
+     */
+    private PipTouchGesture mDragToDismissGesture = new PipTouchGesture() {
+        @Override
+        public void onDown(PipTouchState touchState) {
+            if (mEnableDragToDismiss) {
+                // TODO: Consider setting a timer such at after X time, we show the dismiss
+                //       target if the user hasn't already dragged some distance
+                mDismissViewController.createDismissTarget();
+            }
+        }
+
+        @Override
+        boolean onMove(PipTouchState touchState) {
+            if (mEnableDragToDismiss && touchState.startedDragging()) {
+                mDismissViewController.showDismissTarget();
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onUp(PipTouchState touchState) {
+            if (mEnableDragToDismiss) {
+                try {
+                    if (touchState.isDragging()) {
+                        Rect dismissBounds = mDismissViewController.getDismissBounds();
+                        PointF lastTouch = touchState.getLastTouchPosition();
+                        if (dismissBounds.contains((int) lastTouch.x, (int) lastTouch.y)) {
+                            animateDismissPinnedStack(dismissBounds);
+                            return true;
+                        }
+                    }
+                } finally {
+                    mDismissViewController.destroyDismissTarget();
+                }
+            }
+            return false;
+        }
+    };
+
+    /**** Gestures ****/
+
+    /**
+     * Gesture controlling swiping offscreen to dismiss the PIP.
+     */
+    private PipTouchGesture mSwipeToDismissGesture = new PipTouchGesture() {
+        @Override
+        boolean onMove(PipTouchState touchState) {
+            if (mEnableSwipeToDismiss) {
+                boolean isDraggingOffscreen = isDraggingOffscreen(touchState);
+
+                if (touchState.startedDragging() && isDraggingOffscreen) {
+                    // Reset the minimized state once we drag horizontally
+                    setMinimizedState(false);
+                }
+
+                if (isDraggingOffscreen) {
+                    // Move the pinned stack, but ignore the vertical movement
+                    float left = mPinnedStackBounds.left + touchState.getLastTouchDelta().x;
+                    mTmpBounds.set(mPinnedStackBounds);
+                    mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top);
+                    if (!mTmpBounds.equals(mPinnedStackBounds)) {
+                        mPinnedStackBounds.set(mTmpBounds);
+                        mMotionHelper.resizeToBounds(mPinnedStackBounds);
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onUp(PipTouchState touchState) {
+            if (mEnableSwipeToDismiss && touchState.isDragging()) {
+                PointF vel = touchState.getVelocity();
+                PointF downDelta = touchState.getDownTouchDelta();
+                float minFlingVel = mFlingAnimationUtils.getMinVelocityPxPerSecond();
+                float flingVelScale = mEnableMinimizing ? 3f : 2f;
+                if (Math.abs(vel.x) > (flingVelScale * minFlingVel)) {
+                    // Determine if this gesture is actually a fling to dismiss
+                    if (isFlingToDismiss(vel.x) && Math.abs(downDelta.x) >=
+                            (DISMISS_FLING_DISTANCE_FRACTION * mPinnedStackBounds.width())) {
+                        flingToDismiss(vel.x);
+                    } else {
+                        flingToSnapTarget(vel.length(), vel.x, vel.y);
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    /**
+     * Gesture controlling dragging the PIP slightly offscreen to minimize it.
+     */
+    private PipTouchGesture mMinimizeGesture = new PipTouchGesture() {
+        @Override
+        boolean onMove(PipTouchState touchState) {
+            if (mEnableMinimizing) {
+                boolean isDraggingOffscreen = isDraggingOffscreen(touchState);
+                if (touchState.startedDragging() && isDraggingOffscreen) {
+                    // Reset the minimized state once we drag horizontally
+                    setMinimizedState(false);
+                }
+
+                if (isDraggingOffscreen) {
+                    // Move the pinned stack, but ignore the vertical movement
+                    float left = mPinnedStackBounds.left + touchState.getLastTouchDelta().x;
+                    mTmpBounds.set(mPinnedStackBounds);
+                    mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top);
+                    if (!mTmpBounds.equals(mPinnedStackBounds)) {
+                        mPinnedStackBounds.set(mTmpBounds);
+                        mMotionHelper.resizeToBounds(mPinnedStackBounds);
+                    }
+                    return true;
+                } else if (mIsMinimized && touchState.isDragging()) {
+                    // Move the pinned stack, but ignore the horizontal movement
+                    PointF lastDelta = touchState.getLastTouchDelta();
+                    float top = mPinnedStackBounds.top + lastDelta.y;
+                    top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
+                            mBoundedPinnedStackBounds.bottom, top));
+                    mTmpBounds.set(mPinnedStackBounds);
+                    mTmpBounds.offsetTo(mPinnedStackBounds.left, (int) top);
+                    movePinnedStack(mTmpBounds);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onUp(PipTouchState touchState) {
+            if (mEnableMinimizing) {
+                if (touchState.isDragging()) {
+                    if (isDraggingOffscreen(touchState)) {
+                        if (shouldMinimizedPinnedStack()) {
+                            setMinimizedState(true);
+                            animateToClosestMinimizedTarget();
+                            return true;
+                        }
+                    } else if (mIsMinimized) {
+                        PointF vel = touchState.getVelocity();
+                        if (vel.length() > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+                            flingToMinimizedSnapTarget(vel.y);
+                        } else {
+                            animateToClosestMinimizedTarget();
+                        }
+                        return true;
+                    }
+                } else if (mIsMinimized) {
+                    setMinimizedState(false);
+                    animateToClosestSnapTarget();
+                    return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    /**
+     * Gesture controlling tapping on the PIP to show an overlay.
+     */
+    private PipTouchGesture mTapThroughGesture = new PipTouchGesture() {
+        @Override
+        boolean onMove(PipTouchState touchState) {
+            if (mEnableTapThrough && touchState.startedDragging()) {
+                mIsTappingThrough = false;
+                mMenuController.hideMenu();
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onUp(PipTouchState touchState) {
+            if (mEnableTapThrough && !touchState.isDragging() && !mIsTappingThrough) {
+                mMenuController.showMenu();
+                mIsTappingThrough = true;
+                return true;
+            }
+            return false;
+        }
+    };
+
+    /**
+     * Gesture controlling normal movement of the PIP.
+     */
+    private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
+        @Override
+        boolean onMove(PipTouchState touchState) {
+            if (touchState.isDragging()) {
+                // Move the pinned stack freely
+                PointF lastDelta = touchState.getLastTouchDelta();
+                float left = mPinnedStackBounds.left + lastDelta.x;
+                float top = mPinnedStackBounds.top + lastDelta.y;
+                if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
+                    left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
+                            mBoundedPinnedStackBounds.right, left));
+                    top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
+                            mBoundedPinnedStackBounds.bottom, top));
+                }
+                mTmpBounds.set(mPinnedStackBounds);
+                mTmpBounds.offsetTo((int) left, (int) top);
+                movePinnedStack(mTmpBounds);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onUp(PipTouchState touchState) {
+            if (touchState.isDragging()) {
+                PointF vel = mTouchState.getVelocity();
+                float velocity = PointF.length(vel.x, vel.y);
+                if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+                    flingToSnapTarget(velocity, vel.x, vel.y);
+                } else {
+                    animateToClosestSnapTarget();
+                }
+            } else {
+                expandPinnedStackToFullscreen();
+            }
+            return true;
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
new file mode 100644
index 0000000..80af5a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -0,0 +1,176 @@
+/*
+ * 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.pip.phone;
+
+import android.app.IActivityManager;
+import android.graphics.PointF;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+/**
+ * This keeps track of the touch state throughout the current touch gesture.
+ */
+public class PipTouchState {
+
+    private ViewConfiguration mViewConfig;
+
+    private VelocityTracker mVelocityTracker;
+    private final PointF mDownTouch = new PointF();
+    private final PointF mDownDelta = new PointF();
+    private final PointF mLastTouch = new PointF();
+    private final PointF mLastDelta = new PointF();
+    private final PointF mVelocity = new PointF();
+    private boolean mIsDragging = false;
+    private boolean mStartedDragging = false;
+    private int mActivePointerId;
+
+    public PipTouchState(ViewConfiguration viewConfig) {
+        mViewConfig = viewConfig;
+    }
+
+    /**
+     * Processess a given touch event and updates the state.
+     */
+    public void onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                // Initialize the velocity tracker
+                initOrResetVelocityTracker();
+                mActivePointerId = ev.getPointerId(0);
+                mLastTouch.set(ev.getX(), ev.getY());
+                mDownTouch.set(mLastTouch);
+                mIsDragging = false;
+                mStartedDragging = false;
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                // Update the velocity tracker
+                mVelocityTracker.addMovement(ev);
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                float x = ev.getX(pointerIndex);
+                float y = ev.getY(pointerIndex);
+                mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
+                mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
+
+                boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
+                if (!mIsDragging) {
+                    if (hasMovedBeyondTap) {
+                        mIsDragging = true;
+                        mStartedDragging = true;
+                    }
+                } else {
+                    mStartedDragging = false;
+                }
+                mLastTouch.set(x, y);
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                // Update the velocity tracker
+                mVelocityTracker.addMovement(ev);
+
+                int pointerIndex = ev.getActionIndex();
+                int pointerId = ev.getPointerId(pointerIndex);
+                if (pointerId == mActivePointerId) {
+                    // Select a new active pointer id and reset the movement state
+                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+                    mActivePointerId = ev.getPointerId(newPointerIndex);
+                    mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                // Update the velocity tracker
+                mVelocityTracker.addMovement(ev);
+                mVelocityTracker.computeCurrentVelocity(1000,
+                        mViewConfig.getScaledMaximumFlingVelocity());
+                mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+
+                // Fall through to clean up
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                recycleVelocityTracker();
+                break;
+            }
+        }
+    }
+
+    /**
+     * @return the velocity of the active touch pointer at the point it is lifted off the screen.
+     */
+    public PointF getVelocity() {
+        return mVelocity;
+    }
+
+    /**
+     * @return the last touch position of the active pointer.
+     */
+    public PointF getLastTouchPosition() {
+        return mLastTouch;
+    }
+
+    /**
+     * @return the movement delta between the last handled touch event and the previous touch
+     *         position.
+     */
+    public PointF getLastTouchDelta() {
+        return mLastDelta;
+    }
+
+    /**
+     * @return the movement delta between the last handled touch event and the down touch
+     *         position.
+     */
+    public PointF getDownTouchDelta() {
+        return mDownDelta;
+    }
+
+    /**
+     * @return whether the user has started dragging.
+     */
+    public boolean isDragging() {
+        return mIsDragging;
+    }
+
+    /**
+     * @return whether the user has started dragging just in the last handled touch event.
+     */
+    public boolean startedDragging() {
+        return mStartedDragging;
+    }
+
+    private void initOrResetVelocityTracker() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        } else {
+            mVelocityTracker.clear();
+        }
+    }
+
+    private void recycleVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index e1cd143..dc42adc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -20,7 +20,7 @@
 import android.view.View.OnLayoutChangeListener;
 import android.widget.TextView;
 
-import com.android.systemui.plugins.qs.QSContainer;
+import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.PagedTileLayout.PageListener;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSTile.Host.Callback;
@@ -47,7 +47,7 @@
     private final ArrayList<View> mTopFiveQs = new ArrayList<>();
     private final QuickQSPanel mQuickQsPanel;
     private final QSPanel mQsPanel;
-    private final QSContainer mQsContainer;
+    private final QS mQs;
 
     private PagedTileLayout mPagedLayout;
 
@@ -67,12 +67,15 @@
     private float mLastPosition;
     private QSTileHost mHost;
 
-    public QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
-        mQsContainer = container;
+    public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanel panel) {
+        mQs = qs;
         mQuickQsPanel = quickPanel;
         mQsPanel = panel;
         mQsPanel.addOnAttachStateChangeListener(this);
-        container.addOnLayoutChangeListener(this);
+        qs.getView().addOnLayoutChangeListener(this);
+        if (mQsPanel.isAttachedToWindow()) {
+            onViewAttachedToWindow(null);
+        }
         QSTileLayout tileLayout = mQsPanel.getTileLayout();
         if (tileLayout instanceof PagedTileLayout) {
             mPagedLayout = ((PagedTileLayout) tileLayout);
@@ -102,7 +105,7 @@
 
     @Override
     public void onViewAttachedToWindow(View v) {
-        TunerService.get(mQsContainer.getContext()).addTunable(this, ALLOW_FANCY_ANIMATION,
+        TunerService.get(mQs.getContext()).addTunable(this, ALLOW_FANCY_ANIMATION,
                 MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES);
     }
 
@@ -111,7 +114,7 @@
         if (mHost != null) {
             mHost.removeCallback(this);
         }
-        TunerService.get(mQsContainer.getContext()).removeTunable(this);
+        TunerService.get(mQs.getContext()).removeTunable(this);
     }
 
     @Override
@@ -124,7 +127,7 @@
         } else if (MOVE_FULL_ROWS.equals(key)) {
             mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
         } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
-            mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQsContainer.getContext());
+            mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQs.getContext());
             clearAnimationState();
         }
         updateAnimators();
@@ -166,13 +169,14 @@
             }
             final TextView label = ((QSTileView) tileView).getLabel();
             final View tileIcon = tileView.getIcon().getIconView();
+            View view = mQs.getView();
             if (count < mNumQuickTiles && mAllowFancy) {
                 // Quick tiles.
                 QSTileBaseView quickTileView = mQuickQsPanel.getTileView(tile);
 
                 lastX = loc1[0];
-                getRelativePosition(loc1, quickTileView.getIcon().getIconView(), mQsContainer);
-                getRelativePosition(loc2, tileIcon, mQsContainer);
+                getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view);
+                getRelativePosition(loc2, tileIcon, view);
                 final int xDiff = loc2[0] - loc1[0];
                 final int yDiff = loc2[1] - loc1[1];
                 lastXDiff = loc1[0] - lastX;
@@ -198,7 +202,7 @@
                 // This makes the extra icons seems as if they are coming from positions in the
                 // quick panel.
                 loc1[0] += lastXDiff;
-                getRelativePosition(loc2, tileIcon, mQsContainer);
+                getRelativePosition(loc2, tileIcon, view);
                 final int xDiff = loc2[0] - loc1[0];
                 final int yDiff = loc2[1] - loc1[1];
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f345172..f4da5ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -16,53 +16,34 @@
 
 package com.android.systemui.qs;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 
-import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer;
+import com.android.systemui.plugins.qs.QS.HeightListener;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.phone.QuickStatusBarHeader;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
  * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
- *
- * Also manages animations for the QS Header and Panel.
  */
-public class QSContainerImpl extends QSContainer {
-    private static final String TAG = "QSContainer";
-    private static final boolean DEBUG = false;
+public class QSContainerImpl extends FrameLayout {
 
     private final Point mSizePoint = new Point();
-    private final Rect mQsBounds = new Rect();
 
     private int mHeightOverride = -1;
-    protected QSPanel mQSPanel;
-    private QSDetail mQSDetail;
-    protected QuickStatusBarHeader mHeader;
+    protected View mQSPanel;
+    private View mQSDetail;
+    protected View mHeader;
     protected float mQsExpansion;
-    private boolean mQsExpanded;
-    private boolean mHeaderAnimating;
-    private boolean mKeyguardShowing;
-    private boolean mStackScrollerOverscrolling;
-
-    private long mDelay;
-    private QSAnimator mQSAnimator;
     private QSCustomizer mQSCustomizer;
-    private HeightListener mPanelView;
-    private boolean mListening;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -71,31 +52,10 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
-        mQSDetail = (QSDetail) findViewById(R.id.qs_detail);
-        mHeader = (QuickStatusBarHeader) findViewById(R.id.header);
-        mQSDetail.setQsPanel(mQSPanel, mHeader);
-        mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel),
-                mQSPanel);
+        mQSPanel = findViewById(R.id.quick_settings_panel);
+        mQSDetail = findViewById(R.id.qs_detail);
+        mHeader = findViewById(R.id.header);
         mQSCustomizer = (QSCustomizer) findViewById(R.id.qs_customize);
-        mQSCustomizer.setQsContainer(this);
-    }
-
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        super.onRtlPropertiesChanged(layoutDirection);
-        mQSAnimator.onRtlChanged();
-    }
-
-    public void setHost(QSTileHost qsh) {
-        mQSPanel.setHost(qsh, mQSCustomizer);
-        mHeader.setQSPanel(mQSPanel);
-        mQSDetail.setHost(qsh);
-        mQSAnimator.setHost(qsh);
-    }
-
-    public void setPanelView(HeightListener panelView) {
-        mPanelView = panelView;
     }
 
     @Override
@@ -111,8 +71,8 @@
         super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
 
-        // QSCustomizer is always be the height of the screen, but do this after
-        // other measuring to avoid changing the height of the QSContainer.
+        // QSCustomizer will always be the height of the screen, but do this after
+        // other measuring to avoid changing the height of the QS.
         getDisplay().getRealSize(mSizePoint);
         mQSCustomizer.measure(widthMeasureSpec,
                 MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
@@ -124,10 +84,6 @@
         updateBottom();
     }
 
-    public boolean isCustomizing() {
-        return mQSCustomizer.isCustomizing();
-    }
-
     /**
      * Overrides the height of this view (post-layout), so that the content is clipped to that
      * height and the background is set to that height.
@@ -139,41 +95,7 @@
         updateBottom();
     }
 
-    @Override
-    public void setContainer(ViewGroup container) {
-        if (container instanceof NotificationsQuickSettingsContainer) {
-            mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
-        }
-    }
-
-    /**
-     * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
-     * during closing the detail panel, this already returns the smaller height.
-     */
-    public int getDesiredHeight() {
-        if (isCustomizing()) {
-            return getHeight();
-        }
-        if (mQSDetail.isClosingDetail()) {
-            int panelHeight = ((LayoutParams) mQSPanel.getLayoutParams()).topMargin
-                    + mQSPanel.getMeasuredHeight();
-            return panelHeight + getPaddingBottom();
-        } else {
-            return getMeasuredHeight();
-        }
-    }
-
-    public void notifyCustomizeChanged() {
-        // The customize state changed, so our height changed.
-        updateBottom();
-        mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
-        mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
-        // Let the panel know the position changed and it needs to update where notifications
-        // and whatnot are.
-        mPanelView.onQsHeightChanged();
-    }
-
-    private void updateBottom() {
+    void updateBottom() {
         int height = calculateContainerHeight();
         setBottom(getTop() + height);
         mQSDetail.setBottom(getTop() + height);
@@ -182,162 +104,12 @@
     protected int calculateContainerHeight() {
         int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
         return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
-                : (int) (mQsExpansion * (heightOverride - mHeader.getCollapsedHeight()))
-                + mHeader.getCollapsedHeight();
+                : (int) (mQsExpansion * (heightOverride - mHeader.getHeight()))
+                + mHeader.getHeight();
     }
 
-    private void updateQsState() {
-        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
-        mQSPanel.setExpanded(mQsExpanded);
-        mQSDetail.setExpanded(mQsExpanded);
-        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
-                ? View.VISIBLE
-                : View.INVISIBLE);
-        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
-                || (mQsExpanded && !mStackScrollerOverscrolling));
-        mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    public BaseStatusBarHeader getHeader() {
-        return mHeader;
-    }
-
-    public QSPanel getQsPanel() {
-        return mQSPanel;
-    }
-
-    public QSCustomizer getCustomizer() {
-        return mQSCustomizer;
-    }
-
-    public boolean isShowingDetail() {
-        return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
-    }
-
-    public void setHeaderClickable(boolean clickable) {
-        if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
-        mHeader.setClickable(clickable);
-    }
-
-    public void setExpanded(boolean expanded) {
-        if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
-        mQsExpanded = expanded;
-        mQSPanel.setListening(mListening && mQsExpanded);
-        updateQsState();
-    }
-
-    public void setKeyguardShowing(boolean keyguardShowing) {
-        if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
-        mKeyguardShowing = keyguardShowing;
-        mQSAnimator.setOnKeyguard(keyguardShowing);
-        updateQsState();
-    }
-
-    public void setOverscrolling(boolean stackScrollerOverscrolling) {
-        if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
-        mStackScrollerOverscrolling = stackScrollerOverscrolling;
-        updateQsState();
-    }
-
-    public void setListening(boolean listening) {
-        if (DEBUG) Log.d(TAG, "setListening " + listening);
-        mListening = listening;
-        mHeader.setListening(listening);
-        mQSPanel.setListening(mListening && mQsExpanded);
-    }
-
-    public void setHeaderListening(boolean listening) {
-        mHeader.setListening(listening);
-    }
-
-    public void setQsExpansion(float expansion, float headerTranslation) {
-        if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
+    public void setExpansion(float expansion) {
         mQsExpansion = expansion;
-        final float translationScaleY = expansion - 1;
-        if (!mHeaderAnimating) {
-            setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight())
-                    : headerTranslation);
-        }
-        mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
-        mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
-        mQSDetail.setFullyExpanded(expansion == 1);
-        mQSAnimator.setPosition(expansion);
         updateBottom();
-
-        // Set bounds on the QS panel so it doesn't run over the header.
-        mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
-        mQsBounds.right = mQSPanel.getWidth();
-        mQsBounds.bottom = mQSPanel.getHeight();
-        mQSPanel.setClipBounds(mQsBounds);
-    }
-
-    public void animateHeaderSlidingIn(long delay) {
-        if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
-        // If the QS is already expanded we don't need to slide in the header as it's already
-        // visible.
-        if (!mQsExpanded) {
-            mHeaderAnimating = true;
-            mDelay = delay;
-            getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
-        }
-    }
-
-    public void animateHeaderSlidingOut() {
-        if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
-        mHeaderAnimating = true;
-        animate().y(-mHeader.getHeight())
-                .setStartDelay(0)
-                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        animate().setListener(null);
-                        mHeaderAnimating = false;
-                        updateQsState();
-                    }
-                })
-                .start();
-    }
-
-    @Override
-    public void closeDetail() {
-        mQSPanel.closeDetail();
-    }
-
-    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
-            = new ViewTreeObserver.OnPreDrawListener() {
-        @Override
-        public boolean onPreDraw() {
-            getViewTreeObserver().removeOnPreDrawListener(this);
-            animate()
-                    .translationY(0f)
-                    .setStartDelay(mDelay)
-                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .setListener(mAnimateHeaderSlidingInListener)
-                    .start();
-            setY(-mHeader.getHeight());
-            return true;
-        }
-    };
-
-    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
-            = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mHeaderAnimating = false;
-            updateQsState();
-        }
-    };
-
-    public int getQsMinExpansionHeight() {
-        return mHeader.getHeight();
-    }
-
-    @Override
-    public void hideImmediately() {
-        animate().cancel();
-        setY(-mHeader.getHeight());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 2b9320b..5027144 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -35,9 +35,9 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.BaseStatusBarHeader;
-import com.android.systemui.plugins.qs.QSContainer.Callback;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
+import com.android.systemui.plugins.qs.QS.Callback;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.statusbar.phone.QSTileHost;
 
 public class QSDetail extends LinearLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
new file mode 100644
index 0000000..c8f1670
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -0,0 +1,300 @@
+/*
+ * 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.qs;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout.LayoutParams;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.phone.QuickStatusBarHeader;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+public class QSFragment extends Fragment implements QS {
+    private static final String TAG = "QS";
+    private static final boolean DEBUG = false;
+
+    private final Rect mQsBounds = new Rect();
+    private boolean mQsExpanded;
+    private boolean mHeaderAnimating;
+    private boolean mKeyguardShowing;
+    private boolean mStackScrollerOverscrolling;
+
+    private long mDelay;
+
+    private QSAnimator mQSAnimator;
+    private HeightListener mPanelView;
+    protected QuickStatusBarHeader mHeader;
+    private QSCustomizer mQSCustomizer;
+    protected QSPanel mQSPanel;
+    private QSDetail mQSDetail;
+    private boolean mListening;
+    private QSContainerImpl mContainer;
+    private int mLayoutDirection;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.qs_panel, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mQSPanel = (QSPanel) view.findViewById(R.id.quick_settings_panel);
+        mQSDetail = (QSDetail) view.findViewById(R.id.qs_detail);
+        mHeader = (QuickStatusBarHeader) view.findViewById(R.id.header);
+        mContainer = (QSContainerImpl) view;
+
+        mQSDetail.setQsPanel(mQSPanel, mHeader);
+        mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel),
+                mQSPanel);
+        mQSCustomizer = (QSCustomizer) view.findViewById(R.id.qs_customize);
+        mQSCustomizer.setQs(this);
+    }
+
+    public void setPanelView(HeightListener panelView) {
+        mPanelView = panelView;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (newConfig.getLayoutDirection() != mLayoutDirection) {
+            mLayoutDirection = newConfig.getLayoutDirection();
+            mQSAnimator.onRtlChanged();
+        }
+    }
+
+    @Override
+    public void setContainer(ViewGroup container) {
+        if (container instanceof NotificationsQuickSettingsContainer) {
+            mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
+        }
+    }
+
+    public boolean isCustomizing() {
+        return mQSCustomizer.isCustomizing();
+    }
+
+    public void setHost(QSTileHost qsh) {
+        mQSPanel.setHost(qsh, mQSCustomizer);
+        mHeader.setQSPanel(mQSPanel);
+        mQSDetail.setHost(qsh);
+        mQSAnimator.setHost(qsh);
+    }
+
+    private void updateQsState() {
+        final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
+                || mHeaderAnimating;
+        mQSPanel.setExpanded(mQsExpanded);
+        mQSDetail.setExpanded(mQsExpanded);
+        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
+                ? View.VISIBLE
+                : View.INVISIBLE);
+        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
+                || (mQsExpanded && !mStackScrollerOverscrolling));
+        mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    public BaseStatusBarHeader getHeader() {
+        return mHeader;
+    }
+
+    public QSPanel getQsPanel() {
+        return mQSPanel;
+    }
+
+    public QSCustomizer getCustomizer() {
+        return mQSCustomizer;
+    }
+
+    public boolean isShowingDetail() {
+        return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
+    }
+
+    public void setHeaderClickable(boolean clickable) {
+        if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
+        mHeader.setClickable(clickable);
+    }
+
+    public void setExpanded(boolean expanded) {
+        if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
+        mQsExpanded = expanded;
+        mQSPanel.setListening(mListening && mQsExpanded);
+        updateQsState();
+    }
+
+    public void setKeyguardShowing(boolean keyguardShowing) {
+        if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
+        mKeyguardShowing = keyguardShowing;
+        mQSAnimator.setOnKeyguard(keyguardShowing);
+        updateQsState();
+    }
+
+    public void setOverscrolling(boolean stackScrollerOverscrolling) {
+        if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
+        mStackScrollerOverscrolling = stackScrollerOverscrolling;
+        updateQsState();
+    }
+
+    public void setListening(boolean listening) {
+        if (DEBUG) Log.d(TAG, "setListening " + listening);
+        mListening = listening;
+        mHeader.setListening(listening);
+        mQSPanel.setListening(mListening && mQsExpanded);
+    }
+
+    public void setHeaderListening(boolean listening) {
+        mHeader.setListening(listening);
+    }
+
+    public void setQsExpansion(float expansion, float headerTranslation) {
+        if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
+        mContainer.setExpansion(expansion);
+        final float translationScaleY = expansion - 1;
+        if (!mHeaderAnimating) {
+            getView().setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight())
+                    : headerTranslation);
+        }
+        mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
+        mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
+        mQSDetail.setFullyExpanded(expansion == 1);
+        mQSAnimator.setPosition(expansion);
+
+        // Set bounds on the QS panel so it doesn't run over the header.
+        mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
+        mQsBounds.right = mQSPanel.getWidth();
+        mQsBounds.bottom = mQSPanel.getHeight();
+        mQSPanel.setClipBounds(mQsBounds);
+    }
+
+    public void animateHeaderSlidingIn(long delay) {
+        if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
+        // If the QS is already expanded we don't need to slide in the header as it's already
+        // visible.
+        if (!mQsExpanded) {
+            mHeaderAnimating = true;
+            mDelay = delay;
+            getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
+        }
+    }
+
+    public void animateHeaderSlidingOut() {
+        if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
+        mHeaderAnimating = true;
+        getView().animate().y(-mHeader.getHeight())
+                .setStartDelay(0)
+                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        getView().animate().setListener(null);
+                        mHeaderAnimating = false;
+                        updateQsState();
+                    }
+                })
+                .start();
+    }
+
+    @Override
+    public void closeDetail() {
+        mQSPanel.closeDetail();
+    }
+
+    public void notifyCustomizeChanged() {
+        // The customize state changed, so our height changed.
+        mContainer.updateBottom();
+        mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
+        mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
+        // Let the panel know the position changed and it needs to update where notifications
+        // and whatnot are.
+        mPanelView.onQsHeightChanged();
+    }
+
+    /**
+     * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
+     * during closing the detail panel, this already returns the smaller height.
+     */
+    public int getDesiredHeight() {
+        if (mQSCustomizer.isCustomizing()) {
+            return getView().getHeight();
+        }
+        if (mQSDetail.isClosingDetail()) {
+            int panelHeight = ((LayoutParams) mQSPanel.getLayoutParams()).topMargin
+                    + mQSPanel.getMeasuredHeight();
+            return panelHeight + getView().getPaddingBottom();
+        } else {
+            return getView().getMeasuredHeight();
+        }
+    }
+
+    @Override
+    public void setHeightOverride(int desiredHeight) {
+        mContainer.setHeightOverride(desiredHeight);
+    }
+
+    public int getQsMinExpansionHeight() {
+        return mHeader.getHeight();
+    }
+
+    @Override
+    public void hideImmediately() {
+        getView().animate().cancel();
+        getView().setY(-mHeader.getHeight());
+    }
+
+    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
+            = new ViewTreeObserver.OnPreDrawListener() {
+        @Override
+        public boolean onPreDraw() {
+            getView().getViewTreeObserver().removeOnPreDrawListener(this);
+            getView().animate()
+                    .translationY(0f)
+                    .setStartDelay(mDelay)
+                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .setListener(mAnimateHeaderSlidingInListener)
+                    .start();
+            getView().setY(-mHeader.getHeight());
+            return true;
+        }
+    };
+
+    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
+            = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mHeaderAnimating = false;
+            updateQsState();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 49307d8..e55ff70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -30,8 +30,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile.Host.Callback;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.external.CustomTile;
@@ -60,7 +60,7 @@
     protected boolean mExpanded;
     protected boolean mListening;
 
-    private QSContainer.Callback mCallback;
+    private QS.Callback mCallback;
     private BrightnessController mBrightnessController;
     protected QSTileHost mHost;
 
@@ -171,7 +171,7 @@
         return mBrightnessView;
     }
 
-    public void setCallback(QSContainer.Callback callback) {
+    public void setCallback(QS.Callback callback) {
         mCallback = callback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 6856575..4341d17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -31,7 +31,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile.State;
 import com.android.systemui.qs.external.TileServices;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index f663c75..9bbead4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -37,7 +37,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer;
+import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
@@ -69,7 +69,7 @@
     private Toolbar mToolbar;
     private boolean mCustomizing;
     private NotificationsQuickSettingsContainer mNotifQsContainer;
-    private QSContainer mQsContainer;
+    private QS mQs;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
@@ -127,8 +127,8 @@
         mNotifQsContainer = notificationsQsContainer;
     }
 
-    public void setQsContainer(QSContainer qsContainer) {
-        mQsContainer = qsContainer;
+    public void setQs(QS qs) {
+        mQs = qs;
     }
 
     public void show(int x, int y) {
@@ -169,7 +169,7 @@
 
     private void setCustomizing(boolean customizing) {
         mCustomizing = customizing;
-        mQsContainer.notifyCustomizeChanged();
+        mQs.notifyCustomizeChanged();
     }
 
     public boolean isCustomizing() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 91a0eb0..342df5e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -143,6 +143,7 @@
     }
 
     public void handleDestroy() {
+        setBindAllowed(false);
         mServices.getContext().unregisterReceiver(mUninstallReceiver);
         mStateManager.handleDestroy();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 6bc94b2..015a4c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -306,6 +306,11 @@
         }
     }
 
+    public void destroy() {
+        mServices.values().forEach(service -> service.handleDestroy());
+        mContext.unregisterReceiver(mRequestListeningReceiver);
+    }
+
     private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index e5a555a..fc1c1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -20,8 +20,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.RelativeSizeSpan;
@@ -40,7 +38,7 @@
 import com.android.settingslib.graph.UsageView;
 import com.android.systemui.BatteryMeterDrawable;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.BatteryController;
 
@@ -80,9 +78,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mBatteryController.addStateChangedCallback(this);
+            mBatteryController.addCallback(this);
         } else {
-            mBatteryController.removeStateChangedCallback(this);
+            mBatteryController.removeCallback(this);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 53010af..f83b279 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -32,7 +32,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSTile;
@@ -67,9 +67,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addStateChangedCallback(mCallback);
+            mController.addCallback(mCallback);
         } else {
-            mController.removeStateChangedCallback(mCallback);
+            mController.removeCallback(mCallback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 4abd84a..8afa91e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -28,7 +28,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 2183565..1406c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -29,7 +29,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSIconView;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.SignalTileView;
@@ -68,9 +68,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addSignalCallback(mSignalCallback);
+            mController.addCallback(mSignalCallback);
         } else {
-            mController.removeSignalCallback(mSignalCallback);
+            mController.removeCallback(mSignalCallback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 0ff81e5..65432dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -44,9 +44,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mDataSaverController.addListener(this);
+            mDataSaverController.addCallback(this);
         } else {
-            mDataSaverController.remListener(this);
+            mDataSaverController.removeCallback(this);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 544ee91..198375d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -37,7 +37,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SysUIToast;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.volume.ZenModePanel;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 8fdce65..5b1638f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -45,13 +45,13 @@
     public FlashlightTile(Host host) {
         super(host);
         mFlashlightController = host.getFlashlightController();
-        mFlashlightController.addListener(this);
+        mFlashlightController.addCallback(this);
     }
 
     @Override
     protected void handleDestroy() {
         super.handleDestroy();
-        mFlashlightController.removeListener(this);
+        mFlashlightController.removeCallback(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index b888fc8..dcee659 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs.tiles;
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.UserManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 4b89075..8a9a696 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -58,10 +58,10 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addSettingsChangedCallback(mCallback);
+            mController.addCallback(mCallback);
             mKeyguard.addCallback(mCallback);
         } else {
-            mController.removeSettingsChangedCallback(mCallback);
+            mController.removeCallback(mCallback);
             mKeyguard.removeCallback(mCallback);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index eb4c510..cec5f15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -61,9 +61,9 @@
     public void setListening(boolean listening) {
         if (mController == null) return;
         if (listening) {
-            mController.addRotationLockControllerCallback(mCallback);
+            mController.addCallback(mCallback);
         } else {
-            mController.removeRotationLockControllerCallback(mCallback);
+            mController.removeCallback(mCallback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index e79e519..246c23e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -22,7 +22,7 @@
 import android.util.Pair;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -67,9 +67,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mUserInfoController.addListener(this);
+            mUserInfoController.addCallback(this);
         } else {
-            mUserInfoController.remListener(this);
+            mUserInfoController.removeCallback(this);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 9ce748f6..3876c88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -31,7 +31,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSIconView;
@@ -70,9 +70,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addSignalCallback(mSignalCallback);
+            mController.addCallback(mSignalCallback);
         } else {
-            mController.removeSignalCallback(mSignalCallback);
+            mController.removeCallback(mSignalCallback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
index b88ee76..e804b52 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
@@ -15,12 +15,13 @@
  */
 package com.android.systemui.recents.grid;
 
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
 import android.app.Activity;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -59,7 +60,6 @@
 import com.android.systemui.recents.views.TaskView;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -70,11 +70,12 @@
 
     private TaskStack mTaskStack;
     private List<Task> mTasks = new ArrayList<>();
-    private List<View> mTaskViews = new ArrayList<>();
+    private List<TaskView> mTaskViews = new ArrayList<>();
     private FrameLayout mRecentsView;
     private TextView mEmptyView;
     private View mClearAllButton;
-    private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private int mLastDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private int mLastDisplayDensity;
     private Rect mDisplayRect = new Rect();
     private LayoutInflater mInflater;
     private boolean mTouchExplorationEnabled;
@@ -86,8 +87,10 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         mInflater = LayoutInflater.from(this);
-        mDisplayOrientation = Utilities.getAppConfiguration(this).orientation;
+        Configuration appConfiguration = Utilities.getAppConfiguration(this);
         mDisplayRect = ssp.getDisplayRect();
+        mLastDisplayOrientation = appConfiguration.orientation;
+        mLastDisplayDensity = appConfiguration.densityDpi;
         mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
 
         mRecentsView = (FrameLayout) findViewById(R.id.recents_view);
@@ -122,16 +125,29 @@
         return (TaskView) mInflater.inflate(R.layout.recents_task_view, mRecentsView, false);
     }
 
-    private void clearTaskViews() {
+    private void removeTaskViews() {
         for (View taskView : mTaskViews) {
             ViewGroup parent = (ViewGroup) taskView.getParent();
             if (parent != null) {
                 parent.removeView(taskView);
             }
         }
+    }
+
+    private void clearTaskViews() {
+        removeTaskViews();
         mTaskViews.clear();
     }
 
+    private TaskView getChildViewForTask(Task task) {
+        for (TaskView tv : mTaskViews) {
+            if (tv.getTask() == task) {
+                return tv;
+            }
+        }
+        return null;
+    }
+
     private void updateControlVisibility() {
         boolean empty = (mTasks.size() == 0);
         mClearAllButton.setVisibility(empty ? View.INVISIBLE : View.VISIBLE);
@@ -160,9 +176,7 @@
         loadOpts.numVisibleTaskThumbnails = numVisibleTasks;
         loader.loadTasks(this, plan, loadOpts);
 
-        List<Task> stackTasks = mTaskStack.getStackTasks();
-        Collections.reverse(stackTasks);
-        mTasks = stackTasks;
+        mTasks = mTaskStack.getStackTasks();
 
         updateControlVisibility();
 
@@ -170,7 +184,8 @@
         for (int i = 0; i < mTasks.size(); i++) {
             Task task = mTasks.get(i);
             TaskView taskView = createView();
-            taskView.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect);
+            taskView.onTaskBound(task, mTouchExplorationEnabled, mLastDisplayOrientation,
+                    mDisplayRect);
             Recents.getTaskLoader().loadTaskData(task);
             taskView.setTouchEnabled(true);
             // Show dismiss button right away.
@@ -211,6 +226,22 @@
     }
 
     @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        // Notify of the config change.
+        Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
+        mDisplayRect = Recents.getSystemServices().getDisplayRect();
+        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+        mRecentsView.requestLayout();
+        int numStackTasks = mTaskStack.getStackTaskCount();
+        EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
+                mLastDisplayOrientation != newDeviceConfiguration.orientation,
+                mLastDisplayDensity != newDeviceConfiguration.densityDpi, numStackTasks > 0));
+        mLastDisplayOrientation = newDeviceConfiguration.orientation;
+        mLastDisplayDensity = newDeviceConfiguration.densityDpi;
+    }
+
+    @Override
     public boolean onPreDraw() {
         mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
         int width = mRecentsView.getWidth();
@@ -218,9 +249,13 @@
 
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
             mTasks.size(), width, height, false /* allowLineOfThree */, 30 /* padding */);
+        removeTaskViews();
         for (int i = 0; i < rects.size(); i++) {
             Rect rect = rects.get(i);
-            View taskView = mTaskViews.get(i);
+            // We keep the same ordering in the model as other Recents flavors (older tasks are
+            // first in the stack) so that the logic can be similar, but we reverse the order
+            // when placing views on the screen so that most recent tasks are displayed first.
+            View taskView = mTaskViews.get(rects.size() - 1 - i);
             taskView.setLayoutParams(new FrameLayout.LayoutParams(rect.width(), rect.height()));
             taskView.setTranslationX(rect.left);
             taskView.setTranslationY(rect.top);
@@ -235,20 +270,46 @@
         startActivity(startMain);
     }
 
+    /** Launches the task that recents was launched from if possible. */
+    boolean launchPreviousTask() {
+        if (mRecentsView != null) {
+            Task task = mTaskStack.getLaunchTarget();
+            if (task != null) {
+                TaskView taskView = getChildViewForTask(task);
+                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
+                        INVALID_STACK_ID, false));
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Dismisses recents back to the launch target task. */
+    boolean dismissRecentsToLaunchTargetTaskOrHome() {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.isRecentsActivityVisible()) {
+            // If we can launch the task that Recents was launched from, do that, otherwise go home.
+            if (launchPreviousTask()) return true;
+            dismissRecentsToHome();
+        }
+        return false;
+    }
+
     /**** EventBus events ****/
 
     public final void onBusEvent(HideRecentsEvent event) {
         if (event.triggeredFromAltTab) {
-            // Do nothing for now.
+            dismissRecentsToLaunchTargetTaskOrHome();
         } else if (event.triggeredFromHomeKey) {
             dismissRecentsToHome();
+        } else {
+            // Fall through tap on the background view but not on any of the tasks.
+            dismissRecentsToHome();
         }
     }
 
     public final void onBusEvent(ToggleRecentsEvent event) {
-        // Always go back home for simplicity for now. If recents is entered from another app, this
-        // code will eventually need to go back to the original app.
-        dismissRecentsToHome();
+        dismissRecentsToLaunchTargetTaskOrHome();
     }
 
     public final void onBusEvent(DismissTaskViewEvent event) {
@@ -306,7 +367,19 @@
     }
 
     public final void onBusEvent(LaunchNextTaskRequestEvent event) {
-        // Always go back home for simplicity for now. Quick switch will be supported soon.
+        if (mTaskStack.getTaskCount() > 0) {
+            Task launchTask = mTaskStack.getNextLaunchTarget();
+            TaskView launchTaskView = getChildViewForTask(launchTask);
+            if (launchTaskView != null) {
+                EventBus.getDefault().send(new LaunchTaskEvent(launchTaskView,
+                        launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */));
+                MetricsLogger.action(this, MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
+                        launchTask.key.getComponent().toString());
+                return;
+            }
+        }
+        // We couldn't find a matching task view, or there are no tasks. Just hide recents back
+        // to home.
         EventBus.getDefault().send(new HideRecentsEvent(false, true));
     }
 
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 745f5a5..178cb9f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -849,6 +849,24 @@
         return null;
     }
 
+    /**
+     * Returns the task in stack tasks which should be launched next if Recents are toggled
+     * again, or null if there is no task to be launched.
+     */
+    public Task getNextLaunchTarget() {
+        int taskCount = getTaskCount();
+        if (taskCount == 0) {
+            return null;
+        }
+        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+        if (launchTaskIndex != -1) {
+            launchTaskIndex = Math.max(0, launchTaskIndex - 1);
+        } else {
+            launchTaskIndex = getTaskCount() - 1;
+        }
+        return getStackTasks().get(launchTaskIndex);
+    }
+
     /** Returns the index of this task in this current task stack */
     public int indexOfStackTask(Task t) {
         return mStackTaskList.indexOf(t);
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 77c61f8..8c94c35 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1683,17 +1683,11 @@
             return;
         }
 
-        int launchTaskIndex = mStack.indexOfStackTask(mStack.getLaunchTarget());
-        if (launchTaskIndex != -1) {
-            launchTaskIndex = Math.max(0, launchTaskIndex - 1);
-        } else {
-            launchTaskIndex = mStack.getTaskCount() - 1;
-        }
-        if (launchTaskIndex != -1) {
+        final Task launchTask = mStack.getNextLaunchTarget();
+        if (launchTask != null) {
             // Stop all animations
             cancelAllTaskViewAnimations();
 
-            final Task launchTask = mStack.getStackTasks().get(launchTaskIndex);
             float curScroll = mStackScroller.getStackScroll();
             float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(launchTask);
             float absScrollDiff = Math.abs(targetScroll - curScroll);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 74caa53..68d5cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -151,8 +151,8 @@
             mBlockEthernet = blockEthernet;
             mBlockWifi = blockWifi;
             // Re-register to get new callbacks.
-            mNC.removeSignalCallback(this);
-            mNC.addSignalCallback(this);
+            mNC.removeCallback(this);
+            mNC.addCallback(this);
         }
     }
 
@@ -224,7 +224,7 @@
 
         apply();
         applyIconTint();
-        mNC.addSignalCallback(this);
+        mNC.addCallback(this);
     }
 
     @Override
@@ -232,7 +232,7 @@
         mMobileSignalGroup.removeAllViews();
         TunerService.get(mContext).removeTunable(this);
         mSC.removeCallback(this);
-        mNC.removeSignalCallback(this);
+        mNC.removeCallback(this);
 
         super.onDetachedFromWindow();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
index 4977202..fc39648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
@@ -99,7 +99,7 @@
     }
 
     @Override
-    public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
         mChangeCallbacks.add(cb);
 
         // There is no way to know if the phone is plugged in or charging via bluetooth, so pass
@@ -109,7 +109,7 @@
     }
 
     @Override
-    public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
         mChangeCallbacks.remove(cb);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index baff680..1c8c317 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -30,7 +30,7 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
+import com.android.systemui.plugins.qs.QS.ActivityStarter;
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index 66030b9..a3e1b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -94,12 +94,12 @@
         filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
         mContext.registerReceiver(this, filter);
 
-        mController.addStateChangedCallback(this);
+        mController.addCallback(this);
     }
 
     public void stopListening() {
         mContext.unregisterReceiver(this);
-        mController.removeStateChangedCallback(this);
+        mController.removeCallback(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index b742479..a011162 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -42,7 +42,7 @@
             host.getHotspotController().addCallback(mHotspotCallback);
         }
         if (!Prefs.getBoolean(context, Key.QS_DATA_SAVER_ADDED, false)) {
-            host.getNetworkController().getDataSaverController().addListener(mDataSaverListener);
+            host.getNetworkController().getDataSaverController().addCallback(mDataSaverListener);
         }
         if (!Prefs.getBoolean(context, Key.QS_INVERT_COLORS_ADDED, false)) {
             mColorsSetting = new SecureSetting(mContext, mHandler,
@@ -69,7 +69,10 @@
     }
 
     public void destroy() {
-        // TODO: Remove any registered listeners.
+        mColorsSetting.setListening(false);
+        mHost.getHotspotController().removeCallback(mHotspotCallback);
+        mHost.getNetworkController().getDataSaverController().removeCallback(mDataSaverListener);
+        mHost.getManagedProfileController().removeCallback(mProfileCallback);
     }
 
     private final ManagedProfileController.Callback mProfileCallback =
@@ -105,7 +108,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mHost.getNetworkController().getDataSaverController().remListener(
+                        mHost.getNetworkController().getDataSaverController().removeCallback(
                                 mDataSaverListener);
                     }
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index dfb06d7..dbae6b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -66,7 +66,7 @@
 import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
+import com.android.systemui.plugins.qs.QS.ActivityStarter;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.KeyguardIndicationController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index d94def9..e4c778c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -190,9 +190,9 @@
         }
         mBatteryListening = listening;
         if (mBatteryListening) {
-            mBatteryController.addStateChangedCallback(this);
+            mBatteryController.addCallback(this);
         } else {
-            mBatteryController.removeStateChangedCallback(this);
+            mBatteryController.removeCallback(this);
         }
     }
 
@@ -214,7 +214,7 @@
     }
 
     public void setUserInfoController(UserInfoController userInfoController) {
-        userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
+        userInfoController.addCallback(new UserInfoController.OnUserInfoChangedListener() {
             @Override
             public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
                 mMultiUserAvatar.setImageDrawable(picture);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java
index df4566b..dd7f3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java
@@ -46,7 +46,7 @@
             BatteryController batteryController) {
         mIconController = iconController;
         mBatteryController = batteryController;
-        batteryController.addStateChangedCallback(this);
+        batteryController.addCallback(this);
     }
 
     public void setFingerprintUnlockController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 951b096..1a46815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -24,11 +24,14 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
+import com.android.systemui.statusbar.policy.CallbackController;
+
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
-public class ManagedProfileController {
+public class ManagedProfileController implements CallbackController<Callback> {
 
     private final List<Callback> mCallbacks = new ArrayList<>();
 
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 af9454c..4d4f9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -30,7 +30,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
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 cda0bfe..69d76e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -21,6 +21,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
+import android.app.Fragment;
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.pm.ResolveInfo;
@@ -34,6 +35,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
@@ -42,15 +44,15 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
-import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.AutoReinflateContainer.InflateListener;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.qs.QSContainer;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -70,7 +72,7 @@
         ExpandableView.OnHeightChangedListener,
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
         KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
-        HeadsUpManager.OnHeadsUpChangedListener, QSContainer.HeightListener {
+        HeadsUpManager.OnHeadsUpChangedListener, QS.HeightListener {
 
     private static final boolean DEBUG = false;
 
@@ -92,8 +94,8 @@
     private KeyguardAffordanceHelper mAfforanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
-    protected QSContainer mQsContainer;
-    private AutoReinflateContainer mQsAutoReinflateContainer;
+    private QS mQs;
+    private FrameLayout mQsFrame;
     private KeyguardStatusView mKeyguardStatusView;
     private TextView mClockView;
     private View mReserveNotificationSpace;
@@ -236,31 +238,19 @@
         mKeyguardBottomArea.setAffordanceHelper(mAfforanceHelper);
         mLastOrientation = getResources().getConfiguration().orientation;
 
-        mQsAutoReinflateContainer =
-                (AutoReinflateContainer) findViewById(R.id.qs_auto_reinflate_container);
-        mQsAutoReinflateContainer.addInflateListener(new InflateListener() {
-            @Override
-            public void onInflated(View v) {
-                mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container);
-                mQsContainer.setPanelView(NotificationPanelView.this);
-                mQsContainer.getHeader().getExpandView()
-                        .setOnClickListener(NotificationPanelView.this);
+        mQsFrame = (FrameLayout) findViewById(R.id.qs_frame);
+    }
 
-                // recompute internal state when qspanel height changes
-                mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
-                    @Override
-                    public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                            int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                        final int height = bottom - top;
-                        final int oldHeight = oldBottom - oldTop;
-                        if (height != oldHeight) {
-                            onQsHeightChanged();
-                        }
-                    }
-                });
-                mNotificationStackScroller.setQsContainer(mQsContainer);
-            }
-        });
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        FragmentHostManager.get(this).addTagListener(QS.TAG, mFragmentListener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        FragmentHostManager.get(this).removeTagListener(QS.TAG, mFragmentListener);
     }
 
     @Override
@@ -288,12 +278,12 @@
         int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
         int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
         FrameLayout.LayoutParams lp =
-                (FrameLayout.LayoutParams) mQsAutoReinflateContainer.getLayoutParams();
+                (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
         if (lp.width != panelWidth) {
             lp.width = panelWidth;
             lp.gravity = panelGravity;
-            mQsAutoReinflateContainer.setLayoutParams(lp);
-            mQsContainer.post(mUpdateHeader);
+            mQsFrame.setLayoutParams(lp);
+            mQs.getView().post(mUpdateHeader);
         }
 
         lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
@@ -314,8 +304,10 @@
 
         // Calculate quick setting heights.
         int oldMaxHeight = mQsMaxExpansionHeight;
-        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQsContainer.getQsMinExpansionHeight();
-        mQsMaxExpansionHeight = mQsContainer.getDesiredHeight();
+        if (mQs != null) {
+            mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+            mQsMaxExpansionHeight = mQs.getDesiredHeight();
+        }
         positionClockAndNotifications();
         if (mQsExpanded && mQsFullyExpanded) {
             mQsExpansionHeight = mQsMaxExpansionHeight;
@@ -337,8 +329,8 @@
         // the desired height so when closing the QS detail, it stays smaller after the size change
         // animation is finished but the detail view is still being animated away (this animation
         // takes longer than the size change animation).
-        if (mQsSizeChangeAnimator == null) {
-            mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
+        if (mQsSizeChangeAnimator == null && mQs != null) {
+            mQs.setHeightOverride(mQs.getDesiredHeight());
         }
         updateMaxHeadsUpTranslation();
     }
@@ -357,7 +349,7 @@
                 requestScrollerTopPaddingUpdate(false /* animate */);
                 requestPanelHeightUpdate();
                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
-                mQsContainer.setHeightOverride(height);
+                mQs.setHeightOverride(height);
             }
         });
         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
@@ -377,7 +369,7 @@
         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         int stackScrollerPadding;
         if (mStatusBarState != StatusBarState.KEYGUARD) {
-            stackScrollerPadding = mQsContainer.getHeader().getHeight() + mQsPeekHeight;
+            stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight;
             mTopPaddingAdjustment = 0;
         } else {
             mClockPositionAlgorithm.setup(
@@ -490,7 +482,8 @@
 
     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
         mQsExpansionEnabled = qsExpansionEnabled;
-        mQsContainer.setHeaderClickable(qsExpansionEnabled);
+        if (mQs == null) return;
+        mQs.setHeaderClickable(qsExpansionEnabled);
     }
 
     @Override
@@ -571,7 +564,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (mBlockTouches || mQsContainer.isCustomizing()) {
+        if (mBlockTouches || mQs.isCustomizing()) {
             return false;
         }
         initDownStates(event);
@@ -731,7 +724,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (mBlockTouches || mQsContainer.isCustomizing()) {
+        if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
             return false;
         }
         initDownStates(event);
@@ -804,10 +797,10 @@
     }
 
     private boolean isInQsArea(float x, float y) {
-        return (x >= mQsAutoReinflateContainer.getX()
-                && x <= mQsAutoReinflateContainer.getX() + mQsAutoReinflateContainer.getWidth())
+        return (x >= mQsFrame.getX()
+                && x <= mQsFrame.getX() + mQsFrame.getWidth())
                 && (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
-                || y <= mQsContainer.getY() + mQsContainer.getHeight());
+                || y <= mQs.getView().getY() + mQs.getView().getHeight());
     }
 
     private boolean isOpenQsEvent(MotionEvent event) {
@@ -962,7 +955,8 @@
 
     private void setOverScrolling(boolean overscrolling) {
         mStackScrollerOverscrolling = overscrolling;
-        mQsContainer.setOverscrolling(overscrolling);
+        if (mQs == null) return;
+        mQs.setOverscrolling(overscrolling);
     }
 
     private void onQsExpansionStarted() {
@@ -1000,24 +994,28 @@
 
         mStatusBarState = statusBarState;
         mKeyguardShowing = keyguardShowing;
-        mQsContainer.setKeyguardShowing(mKeyguardShowing);
+        if (mQs != null) {
+            mQs.setKeyguardShowing(mKeyguardShowing);
+        }
 
         if (oldState == StatusBarState.KEYGUARD
                 && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
             animateKeyguardStatusBarOut();
             long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
                     ? 0 : mStatusBar.calculateGoingToFullShadeDelay();
-            mQsContainer.animateHeaderSlidingIn(delay);
+            mQs.animateHeaderSlidingIn(delay);
         } else if (oldState == StatusBarState.SHADE_LOCKED
                 && statusBarState == StatusBarState.KEYGUARD) {
             animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            mQsContainer.animateHeaderSlidingOut();
+            mQs.animateHeaderSlidingOut();
         } else {
             mKeyguardStatusBar.setAlpha(1f);
             mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
             if (keyguardShowing && oldState != mStatusBarState) {
                 mKeyguardBottomArea.onKeyguardShowingChanged();
-                mQsContainer.hideImmediately();
+                if (mQs != null) {
+                    mQs.hideImmediately();
+                }
             }
         }
         if (keyguardShowing) {
@@ -1163,7 +1161,6 @@
     }
 
     private void updateQsState() {
-        mQsContainer.setExpanded(mQsExpanded);
         mNotificationStackScroller.setQsExpanded(mQsExpanded);
         mNotificationStackScroller.setScrollingEnabled(
                 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
@@ -1176,6 +1173,8 @@
         if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
             mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
         }
+        if (mQs == null) return;
+        mQs.setExpanded(mQsExpanded);
     }
 
     private void setQsExpansion(float height) {
@@ -1222,11 +1221,12 @@
     }
 
     protected void updateQsExpansion() {
-        mQsContainer.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
+        if (mQs == null) return;
+        mQs.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
     }
 
     private String getKeyguardOrLockScreenString() {
-        if (mQsContainer.isCustomizing()) {
+        if (mQs.isCustomizing()) {
             return getContext().getString(R.string.accessibility_desc_quick_settings_edit);
         } else if (mStatusBarState == StatusBarState.KEYGUARD) {
             return getContext().getString(R.string.accessibility_desc_lock_screen);
@@ -1357,9 +1357,9 @@
         if (!mQsExpansionEnabled || mCollapsedOnDown) {
             return false;
         }
-        View header = mKeyguardShowing ? mKeyguardStatusBar : mQsContainer.getHeader();
-        boolean onHeader = x >= mQsAutoReinflateContainer.getX()
-                && x <= mQsAutoReinflateContainer.getX() + mQsAutoReinflateContainer.getWidth()
+        View header = mKeyguardShowing ? mKeyguardStatusBar : mQs.getHeader();
+        final boolean onHeader = x >= mQsFrame.getX()
+                && x <= mQsFrame.getX() + mQsFrame.getWidth()
                 && y >= header.getTop() && y <= header.getBottom();
         if (mQsExpanded) {
             return onHeader || (yDiff < 0 && isInQsArea(x, y));
@@ -1621,7 +1621,8 @@
         }
         // Since there are QS tiles in the header now, we need to make sure we start listening
         // immediately so they can be up to date.
-        mQsContainer.setHeaderListening(true);
+        if (mQs == null) return;
+        mQs.setHeaderListening(true);
     }
 
     @Override
@@ -1659,8 +1660,9 @@
     }
 
     private void setListening(boolean listening) {
-        mQsContainer.setListening(listening);
         mKeyguardStatusBar.setListening(listening);
+        if (mQs == null) return;
+        mQs.setListening(listening);
     }
 
     @Override
@@ -1748,7 +1750,7 @@
     }
 
     public void onQsHeightChanged() {
-        mQsMaxExpansionHeight = mQsContainer.getDesiredHeight();
+        mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
         if (mQsExpanded && mQsFullyExpanded) {
             mQsExpansionHeight = mQsMaxExpansionHeight;
             requestScrollerTopPaddingUpdate(false /* animate */);
@@ -2018,11 +2020,11 @@
     }
 
     public boolean isQsDetailShowing() {
-        return mQsContainer.isShowingDetail();
+        return mQs.isShowingDetail();
     }
 
     public void closeQsDetail() {
-        mQsContainer.closeDetail();
+        mQs.closeDetail();
     }
 
     @Override
@@ -2110,7 +2112,7 @@
     private final Runnable mUpdateHeader = new Runnable() {
         @Override
         public void run() {
-            mQsContainer.getHeader().updateEverything();
+            mQs.getHeader().updateEverything();
         }
     };
 
@@ -2257,7 +2259,7 @@
 
     protected void setVerticalPanelTranslation(float translation) {
         mNotificationStackScroller.setTranslationX(translation);
-        mQsAutoReinflateContainer.setTranslationX(translation);
+        mQsFrame.setTranslationX(translation);
     }
 
     protected void updateExpandedHeight(float expandedHeight) {
@@ -2355,4 +2357,38 @@
     public void setGroupManager(NotificationGroupManager groupManager) {
         mGroupManager = groupManager;
     }
+
+    private final FragmentListener mFragmentListener = new FragmentListener() {
+        @Override
+        public void onFragmentViewCreated(String tag, Fragment fragment) {
+            mQs = (QS) fragment;
+            mQs.setPanelView(NotificationPanelView.this);
+            mQs.getHeader().getExpandView().setOnClickListener(NotificationPanelView.this);
+            mQs.setHeaderClickable(mQsExpansionEnabled);
+            mQs.setKeyguardShowing(mKeyguardShowing);
+            mQs.setOverscrolling(mStackScrollerOverscrolling);
+
+            // recompute internal state when qspanel height changes
+            mQs.getView().addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        final int height = bottom - top;
+                        final int oldHeight = oldBottom - oldTop;
+                        if (height != oldHeight) {
+                            onQsHeightChanged();
+                        }
+                    });
+            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
+            updateQsExpansion();
+        }
+
+        @Override
+        public void onFragmentViewDestroyed(String tag, Fragment fragment) {
+            // Manual handling of fragment lifecycle is only required because this bridges
+            // non-fragment and fragment code. Once we are using a fragment for the notification
+            // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
+            if (fragment == mQs) {
+                mQs = null;
+            }
+        }
+    };
 }
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 8b1fcd6..c85584e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.app.Fragment;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
@@ -25,18 +26,17 @@
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
-import com.android.systemui.AutoReinflateContainer;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.plugins.qs.QS;
 
 /**
  * The container with notification stack scroller and quick settings inside.
  */
 public class NotificationsQuickSettingsContainer extends FrameLayout
-        implements ViewStub.OnInflateListener, AutoReinflateContainer.InflateListener {
+        implements ViewStub.OnInflateListener, FragmentHostManager.FragmentListener {
 
-
-    private AutoReinflateContainer mQsContainer;
+    private FrameLayout mQsFrame;
     private View mUserSwitcher;
     private View mStackScroller;
     private View mKeyguardStatusBar;
@@ -54,8 +54,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mQsContainer = (AutoReinflateContainer) findViewById(R.id.qs_auto_reinflate_container);
-        mQsContainer.addInflateListener(this);
+        mQsFrame = (FrameLayout) findViewById(R.id.qs_frame);
         mStackScroller = findViewById(R.id.notification_stack_scroller);
         mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
@@ -65,9 +64,21 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        FragmentHostManager.get(this).addTagListener(QS.TAG, this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        FragmentHostManager.get(this).removeTagListener(QS.TAG, this);
+    }
+
+    @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        reloadWidth(mQsContainer);
+        reloadWidth(mQsFrame);
         reloadWidth(mStackScroller);
     }
 
@@ -91,11 +102,11 @@
         boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
 
         final boolean qsBottom = mQsExpanded && !mCustomizerAnimating;
-        View stackQsTop = qsBottom ? mStackScroller : mQsContainer;
-        View stackQsBottom = !qsBottom ? mStackScroller : mQsContainer;
+        View stackQsTop = qsBottom ? mStackScroller : mQsFrame;
+        View stackQsBottom = !qsBottom ? mStackScroller : mQsFrame;
         // Invert the order of the scroll view and user switcher such that the notifications receive
         // touches first but the panel gets drawn above.
-        if (child == mQsContainer) {
+        if (child == mQsFrame) {
             return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher
                     : statusBarVisible ? mKeyguardStatusBar
                     : userSwitcherVisible ? mUserSwitcher
@@ -129,8 +140,8 @@
     }
 
     @Override
-    public void onInflated(View v) {
-        QSContainer container = (QSContainer) v;
+    public void onFragmentViewCreated(String tag, Fragment fragment) {
+        QS container = (QS) fragment;
         container.setContainer(this);
     }
 
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 599fcba..31a93f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -90,7 +90,6 @@
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
@@ -126,8 +125,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.AutoReinflateContainer.InflateListener;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogConstants;
@@ -141,11 +138,13 @@
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.PluginFragmentListener;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
-import com.android.systemui.plugins.qs.QSContainer.BaseStatusBarHeader;
-import com.android.systemui.plugins.qs.QSContainer;
-import com.android.systemui.qs.QSContainerImpl;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
+import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.recents.events.EventBus;
@@ -561,6 +560,7 @@
     private int mLastCameraLaunchSource;
     private PowerManager.WakeLock mGestureWakeLock;
     private Vibrator mVibrator;
+    private long[] mCameraLaunchGestureVibePattern;
 
     // Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
     private int mLastLoggedStateFingerprint;
@@ -874,7 +874,7 @@
         mLocationController = new LocationControllerImpl(mContext,
                 mHandlerThread.getLooper()); // will post a notification
         mBatteryController = createBatteryController();
-        mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
+        mBatteryController.addCallback(new BatteryStateChangeCallback() {
             @Override
             public void onPowerSaveChanged(boolean isPowerSave) {
                 mHandler.post(mCheckBarModes);
@@ -922,9 +922,11 @@
         }
 
         // Set up the quick settings tile panel
-        AutoReinflateContainer container = (AutoReinflateContainer) mStatusBarWindow.findViewById(
-                R.id.qs_auto_reinflate_container);
+        View container = mStatusBarWindow.findViewById(R.id.qs_frame);
         if (container != null) {
+            FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
+            new PluginFragmentListener(container, QS.TAG, R.id.qs_frame, QSFragment.class, QS.class)
+                    .startListening(QS.ACTION, QS.VERSION);
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, mHotspotController,
@@ -933,21 +935,17 @@
                     mSecurityController, mBatteryController, mIconController,
                     mNextAlarmController);
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
-            container.addInflateListener(new InflateListener() {
-                @Override
-                public void onInflated(View v) {
-                    QSContainer qsContainer = (QSContainer) v.findViewById(
-                            R.id.quick_settings_container);
-                    if (qsContainer instanceof QSContainerImpl) {
-                        ((QSContainerImpl) qsContainer).setHost(qsh);
-                        mQSPanel = ((QSContainerImpl) qsContainer).getQsPanel();
-                        mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
-                        mKeyguardStatusBar.setQSPanel(mQSPanel);
-                    }
-                    mHeader = qsContainer.getHeader();
-                    initSignalCluster(mHeader);
-                    mHeader.setActivityStarter(PhoneStatusBar.this);
+            fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
+                QS qs = (QS) f;
+                if (qs instanceof QSFragment) {
+                    ((QSFragment) qs).setHost(qsh);
+                    mQSPanel = ((QSFragment) qs).getQsPanel();
+                    mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
+                    mKeyguardStatusBar.setQSPanel(mQSPanel);
                 }
+                mHeader = qs.getHeader();
+                initSignalCluster(mHeader);
+                mHeader.setActivityStarter(PhoneStatusBar.this);
             });
         }
 
@@ -996,6 +994,12 @@
         mGestureWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
                 "GestureWakeLock");
         mVibrator = mContext.getSystemService(Vibrator.class);
+        int[] pattern = mContext.getResources().getIntArray(
+                R.array.config_cameraLaunchGestureVibePattern);
+        mCameraLaunchGestureVibePattern = new long[pattern.length];
+        for (int i = 0; i < pattern.length; i++) {
+            mCameraLaunchGestureVibePattern[i] = pattern[i];
+        }
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -1030,7 +1034,7 @@
             if (emergencyViewStub != null) {
                 ((ViewStub) emergencyViewStub).inflate();
             }
-            mNetworkController.addSignalCallback(new NetworkController.SignalCallback() {
+            mNetworkController.addCallback(new NetworkController.SignalCallback() {
                 @Override
                 public void setIsAirplaneMode(NetworkController.IconState icon) {
                     recomputeDisableFlags(true /* animate */);
@@ -3552,8 +3556,14 @@
                 AsyncTask.execute(runnable);
             }
             if (dismissShade) {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                        true /* delayed*/);
+                if (mExpandedVisible) {
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                            true /* delayed*/);
+                } else {
+
+                    // Do it after DismissAction has been processed to conserve the needed ordering.
+                    mHandler.post(this::runPostCollapseRunnables);
+                }
             }
             return deferred;
         }, cancelAction, afterKeyguardGone);
@@ -3631,12 +3641,10 @@
         dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
     }
 
-    public void dismissKeyguard() {
-        mStatusBarKeyguardViewManager.dismiss();
-    }
-
     private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
+        afterKeyguardGone |= mStatusBarKeyguardViewManager.isShowing()
+                && mStatusBarKeyguardViewManager.isOccluded();
         if (mStatusBarKeyguardViewManager.isShowing()) {
             mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
                     afterKeyguardGone);
@@ -3974,9 +3982,9 @@
                 (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
         final SignalClusterView signalClusterQs =
                 (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
-        mNetworkController.removeSignalCallback(signalCluster);
-        mNetworkController.removeSignalCallback(signalClusterKeyguard);
-        mNetworkController.removeSignalCallback(signalClusterQs);
+        mNetworkController.removeCallback(signalCluster);
+        mNetworkController.removeCallback(signalClusterKeyguard);
+        mNetworkController.removeCallback(signalClusterQs);
         if (mQSPanel != null && mQSPanel.getHost() != null) {
             mQSPanel.getHost().destroy();
         }
@@ -4249,7 +4257,7 @@
                     }
                 }, delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
             }
-        } else {
+        } else if (!mNotificationPanel.isCollapsing()) {
             instantCollapseNotificationPanel();
         }
         updateKeyguardState(staying, false /* fromShadeLocked */);
@@ -4876,7 +4884,7 @@
 
     private void vibrateForCameraGesture() {
         // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
-        mVibrator.vibrate(new long[]{0, 400}, -1 /* repeat */);
+        mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
     }
 
     public void onScreenTurnedOn() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 7187ec2..032c86b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -47,7 +47,6 @@
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
@@ -110,7 +109,7 @@
         mCast = cast;
         mHotspot = hotspot;
         mBluetooth = bluetooth;
-        mBluetooth.addStateChangedCallback(this);
+        mBluetooth.addCallback(this);
         mNextAlarm = nextAlarm;
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mUserInfoController = userInfoController;
@@ -131,7 +130,7 @@
         mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
         mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
 
-        mRotationLockController.addRotationLockControllerCallback(this);
+        mRotationLockController.addCallback(this);
 
         // listen for broadcasts
         IntentFilter filter = new IntentFilter();
@@ -162,7 +161,7 @@
         // Alarm clock
         mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
         mIconController.setIconVisibility(mSlotAlarmClock, false);
-        mNextAlarm.addStateChangedCallback(mNextAlarmCallback);
+        mNextAlarm.addCallback(mNextAlarmCallback);
 
         // zen
         mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null);
@@ -193,7 +192,7 @@
         mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
                 context.getString(R.string.accessibility_data_saver_on));
         mIconController.setIconVisibility(mSlotDataSaver, false);
-        mDataSaver.addListener(this);
+        mDataSaver.addCallback(this);
     }
 
     public void setStatusBarKeyguardViewManager(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index da698d8..fc15477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -113,6 +113,7 @@
     private final AutoTileManager mAutoTiles;
     private final ManagedProfileController mProfileController;
     private final NextAlarmController mNextAlarmController;
+    private final HandlerThread mHandlerThread;
     private View mHeader;
     private int mCurrentUser;
 
@@ -144,10 +145,10 @@
         mNextAlarmController = nextAlarmController;
         mProfileController = new ManagedProfileController(this);
 
-        final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
+        mHandlerThread = new HandlerThread(QSTileHost.class.getSimpleName(),
                 Process.THREAD_PRIORITY_BACKGROUND);
-        ht.start();
-        mLooper = ht.getLooper();
+        mHandlerThread.start();
+        mLooper = mHandlerThread.getLooper();
 
         mServices = new TileServices(this, mLooper);
 
@@ -169,8 +170,11 @@
     }
 
     public void destroy() {
+        mHandlerThread.quitSafely();
+        mTiles.values().forEach(tile -> tile.destroy());
         mAutoTiles.destroy();
         TunerService.get(mContext).removeTunable(this);
+        mServices.destroy();
     }
 
     @Override
@@ -321,12 +325,11 @@
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
         int currentUser = ActivityManager.getCurrentUser();
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
-        for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
-            if (!tileSpecs.contains(tile.getKey())) {
-                if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
-                tile.getValue().destroy();
-            }
-        }
+        mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
+                tile -> {
+                    if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
+                    tile.getValue().destroy();
+                });
         final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
         for (String tileSpec : tileSpecs) {
             QSTile<?> tile = mTiles.get(tileSpec);
@@ -342,9 +345,13 @@
                 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
                     tile = createTile(tileSpec);
-                    if (tile != null && tile.isAvailable()) {
-                        tile.setTileSpec(tileSpec);
-                        newTiles.put(tileSpec, tile);
+                    if (tile != null) {
+                        if (tile.isAvailable()) {
+                            tile.setTileSpec(tileSpec);
+                            newTiles.put(tileSpec, tile);
+                        } else {
+                            tile.destroy();
+                        }
                     }
                 } catch (Throwable t) {
                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 65c6347..28aed87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -37,10 +37,10 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
-import com.android.systemui.plugins.qs.QSContainer.BaseStatusBarHeader;
+import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
 import com.android.systemui.qs.QSPanel;
-import com.android.systemui.plugins.qs.QSContainer.Callback;
+import com.android.systemui.plugins.qs.QS.Callback;
 import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.qs.TouchAnimator;
 import com.android.systemui.qs.TouchAnimator.Builder;
@@ -238,7 +238,7 @@
     @Override
     protected void onDetachedFromWindow() {
         setListening(false);
-        mHost.getUserInfoController().remListener(this);
+        mHost.getUserInfoController().removeCallback(this);
         mHost.getNetworkController().removeEmergencyListener(this);
         super.onDetachedFromWindow();
     }
@@ -290,9 +290,9 @@
 
     private void updateListeners() {
         if (mListening) {
-            mNextAlarmController.addStateChangedCallback(this);
+            mNextAlarmController.addCallback(this);
         } else {
-            mNextAlarmController.removeStateChangedCallback(this);
+            mNextAlarmController.removeCallback(this);
         }
     }
 
@@ -368,7 +368,7 @@
     }
 
     public void setUserInfoController(UserInfoController userInfoController) {
-        userInfoController.addListener(this);
+        userInfoController.addCallback(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f5c5e56..69decd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -155,8 +155,8 @@
             if (!afterKeyguardGone) {
                 mBouncer.showWithDismissAction(r, cancelAction);
             } else {
-                mBouncer.show(false /* resetSecuritySelection */);
                 mAfterKeyguardGoneAction = r;
+                mBouncer.show(false /* resetSecuritySelection */);
             }
         }
         updateStates();
@@ -235,10 +235,6 @@
         mDeviceWillWakeUp = !mDeviceInteractive;
     }
 
-    public void verifyUnlock() {
-        dismiss();
-    }
-
     public void setNeedsInput(boolean needsInput) {
         mStatusBarWindowManager.setKeyguardNeedsInput(needsInput);
     }
@@ -333,6 +329,7 @@
                 }
             });
         } else {
+            executeAfterKeyguardGoneAction();
             if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING) {
                 mFingerprintUnlockController.startKeyguardFadingAway();
                 mPhoneStatusBar.setKeyguardFadingAway(startTime, 0, 240);
@@ -372,7 +369,6 @@
             mStatusBarWindowManager.setKeyguardShowing(false);
             mBouncer.hide(true /* destroyView */);
             mViewMediatorCallback.keyguardGone();
-            executeAfterKeyguardGoneAction();
             updateStates();
         }
     }
@@ -423,6 +419,10 @@
     /**
      * Dismisses the keyguard by going to the next screen or making it gone.
      */
+    public void dismissAndCollapse() {
+        mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, true, false, true);
+    }
+
     public void dismiss() {
         showBouncer();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 559436b..19dcf03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.policy;
 
 import com.android.systemui.DemoMode;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-public interface BatteryController extends DemoMode {
+public interface BatteryController extends DemoMode,
+        CallbackController<BatteryStateChangeCallback> {
     /**
      * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
      */
@@ -37,9 +39,6 @@
      */
     boolean isPowerSave();
 
-    void addStateChangedCallback(BatteryStateChangeCallback cb);
-    void removeStateChangedCallback(BatteryStateChangeCallback cb);
-
     /**
      * A listener that will be notified whenever a change in battery level or power save mode
      * has occurred.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 6726c92..fc86ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -89,7 +89,7 @@
     }
 
     @Override
-    public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.add(cb);
         }
@@ -99,7 +99,7 @@
     }
 
     @Override
-    public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.remove(cb);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 08675c4..4c1c378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -17,13 +17,11 @@
 package com.android.systemui.statusbar.policy;
 
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.statusbar.policy.BluetoothController.Callback;
 
 import java.util.Collection;
 
-public interface BluetoothController {
-    void addStateChangedCallback(Callback callback);
-    void removeStateChangedCallback(Callback callback);
-
+public interface BluetoothController extends CallbackController<Callback> {
     boolean isBluetoothSupported();
     boolean isBluetoothEnabled();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 4f880b4..15c4afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -105,13 +105,13 @@
     }
 
     @Override
-    public void addStateChangedCallback(Callback cb) {
+    public void addCallback(Callback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void removeStateChangedCallback(Callback cb) {
+    public void removeCallback(Callback cb) {
         mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
new file mode 100644
index 0000000..9042ca6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
@@ -0,0 +1,20 @@
+/*
+ * 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.statusbar.policy;
+
+public interface CallbackController<T> {
+    void addCallback(T listener);
+    void removeCallback(T listener);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index 7713e57..6988af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.policy;
 
+import com.android.systemui.statusbar.policy.CastController.Callback;
+
 import java.util.Set;
 
-public interface CastController {
-    void addCallback(Callback callback);
-    void removeCallback(Callback callback);
+public interface CastController extends CallbackController<Callback> {
     void setDiscovering(boolean request);
     void setCurrentUserId(int currentUserId);
     Set<CastDevice> getCastDevices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
index 0fc71d3..e5f1e68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
@@ -21,9 +21,11 @@
 import android.os.Looper;
 import android.os.RemoteException;
 
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
 import java.util.ArrayList;
 
-public class DataSaverController {
+public class DataSaverController implements CallbackController<Listener> {
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final ArrayList<Listener> mListeners = new ArrayList<>();
@@ -41,7 +43,7 @@
         }
     }
 
-    public void addListener(Listener listener) {
+    public void addCallback(Listener listener) {
         synchronized (mListeners) {
             mListeners.add(listener);
             if (mListeners.size() == 1) {
@@ -51,7 +53,7 @@
         listener.onDataSaverChanged(isDataSaverEnabled());
     }
 
-    public void remListener(Listener listener) {
+    public void removeCallback(Listener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
             if (mListeners.size() == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 4e9fc76..0f77b03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -27,6 +27,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -35,7 +37,7 @@
 /**
  * Manages the flashlight.
  */
-public class FlashlightController {
+public class FlashlightController implements CallbackController<FlashlightListener> {
 
     private static final String TAG = "FlashlightController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -112,7 +114,7 @@
         return mTorchAvailable;
     }
 
-    public void addListener(FlashlightListener l) {
+    public void addCallback(FlashlightListener l) {
         synchronized (mListeners) {
             if (mCameraId == null) {
                 tryInitCamera();
@@ -122,7 +124,7 @@
         }
     }
 
-    public void removeListener(FlashlightListener l) {
+    public void removeCallback(FlashlightListener l) {
         synchronized (mListeners) {
             cleanUpListenersLocked(l);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 4622ea4..daf9d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
-public interface HotspotController {
-    void addCallback(Callback callback);
-    void removeCallback(Callback callback);
+import com.android.systemui.statusbar.policy.HotspotController.Callback;
+
+public interface HotspotController extends CallbackController<Callback> {
     boolean isHotspotEnabled();
     void setHotspotEnabled(boolean enabled);
     boolean isHotspotSupported();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index 44816f9..fafbdd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -24,10 +24,12 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
 
 import java.util.ArrayList;
 
-public final class KeyguardMonitor extends KeyguardUpdateMonitorCallback {
+public class KeyguardMonitor extends KeyguardUpdateMonitorCallback
+        implements CallbackController<Callback> {
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
index 29a8981..9a5f1b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.policy;
 
-public interface LocationController {
+import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback;
+
+public interface LocationController extends CallbackController<LocationSettingsChangeCallback> {
     boolean isLocationEnabled();
     boolean setLocationEnabled(boolean enabled);
-    void addSettingsChangedCallback(LocationSettingsChangeCallback cb);
-    void removeSettingsChangedCallback(LocationSettingsChangeCallback cb);
 
     /**
      * A callback for change in location settings (the user has enabled/disabled location).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 8d84be4..cc61605 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -82,12 +82,12 @@
     /**
      * Add a callback to listen for changes in location settings.
      */
-    public void addSettingsChangedCallback(LocationSettingsChangeCallback cb) {
+    public void addCallback(LocationSettingsChangeCallback cb) {
         mSettingsChangeCallbacks.add(cb);
         mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
     }
 
-    public void removeSettingsChangedCallback(LocationSettingsChangeCallback cb) {
+    public void removeCallback(LocationSettingsChangeCallback cb) {
         mSettingsChangeCallbacks.remove(cb);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 5f1b871..082fe82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -21,14 +21,15 @@
 import android.telephony.SubscriptionInfo;
 import com.android.settingslib.net.DataUsageController;
 import com.android.settingslib.wifi.AccessPoint;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
 import java.util.List;
 
-public interface NetworkController {
+public interface NetworkController extends CallbackController<SignalCallback> {
 
     boolean hasMobileDataFeature();
-    void addSignalCallback(SignalCallback cb);
-    void removeSignalCallback(SignalCallback cb);
+    void addCallback(SignalCallback cb);
+    void removeCallback(SignalCallback cb);
     void setWifiEnabled(boolean enabled);
     void onUserSwitched(int newUserId);
     AccessPointController getAccessPointController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 37e6a2a..1a9756f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -322,7 +322,7 @@
         mCallbackHandler.setEmergencyCallsOnly(mIsEmergency);
     }
 
-    public void addSignalCallback(SignalCallback cb) {
+    public void addCallback(SignalCallback cb) {
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
@@ -336,7 +336,7 @@
     }
 
     @Override
-    public void removeSignalCallback(SignalCallback cb) {
+    public void removeCallback(SignalCallback cb) {
         mCallbackHandler.setListening(cb, false);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index 787acc5..28935bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -23,11 +23,14 @@
 import android.content.IntentFilter;
 import android.os.UserHandle;
 
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-public class NextAlarmController extends BroadcastReceiver {
+public class NextAlarmController extends BroadcastReceiver
+        implements CallbackController<NextAlarmChangeCallback> {
 
     private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
 
@@ -48,12 +51,12 @@
         pw.print("  mNextAlarm="); pw.println(mNextAlarm);
     }
 
-    public void addStateChangedCallback(NextAlarmChangeCallback cb) {
+    public void addCallback(NextAlarmChangeCallback cb) {
         mChangeCallbacks.add(cb);
         cb.onNextAlarmChanged(mNextAlarm);
     }
 
-    public void removeStateChangedCallback(NextAlarmChangeCallback cb) {
+    public void removeCallback(NextAlarmChangeCallback cb) {
         mChangeCallbacks.remove(cb);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index 93c4691..722874b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui.statusbar.policy;
 
-public interface RotationLockController extends Listenable {
+import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+
+public interface RotationLockController extends Listenable,
+        CallbackController<RotationLockControllerCallback> {
     int getRotationLockOrientation();
     boolean isRotationLockAffordanceVisible();
     boolean isRotationLocked();
     void setRotationLocked(boolean locked);
-    void addRotationLockControllerCallback(RotationLockControllerCallback callback);
-    void removeRotationLockControllerCallback(RotationLockControllerCallback callback);
 
     public interface RotationLockControllerCallback {
         void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index c3bcd94..4f96496 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -42,12 +42,12 @@
         setListening(true);
     }
 
-    public void addRotationLockControllerCallback(RotationLockControllerCallback callback) {
+    public void addCallback(RotationLockControllerCallback callback) {
         mCallbacks.add(callback);
         notifyChanged(callback);
     }
 
-    public void removeRotationLockControllerCallback(RotationLockControllerCallback callback) {
+    public void removeCallback(RotationLockControllerCallback callback) {
         mCallbacks.remove(callback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 014afae..43ced48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -15,7 +15,9 @@
  */
 package com.android.systemui.statusbar.policy;
 
-public interface SecurityController {
+import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+
+public interface SecurityController extends CallbackController<SecurityControllerCallback> {
     /** Whether the device has device owner, even if not on this user. */
     boolean isDeviceManaged();
     boolean hasProfileOwner();
@@ -29,9 +31,6 @@
     String getProfileVpnName();
     void onUserSwitched(int newUserId);
 
-    void addCallback(SecurityControllerCallback callback);
-    void removeCallback(SecurityControllerCallback callback);
-
     public interface SecurityControllerCallback {
         void onStateChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index 4a6e215..17b22df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -38,10 +37,11 @@
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 
 import java.util.ArrayList;
 
-public final class UserInfoController {
+public class UserInfoController implements CallbackController<OnUserInfoChangedListener> {
 
     private static final String TAG = "UserInfoController";
 
@@ -67,12 +67,12 @@
                 null, null);
     }
 
-    public void addListener(OnUserInfoChangedListener callback) {
+    public void addCallback(OnUserInfoChangedListener callback) {
         mCallbacks.add(callback);
         callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
     }
 
-    public void remListener(OnUserInfoChangedListener callback) {
+    public void removeCallback(OnUserInfoChangedListener callback) {
         mCallbacks.remove(callback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index eda46c5..9d9c908 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -49,15 +49,16 @@
 import android.widget.BaseAdapter;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.GuestResumeSessionReceiver;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.plugins.qs.QSContainer.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.tiles.UserDetailView;
-import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
+import com.android.systemui.plugins.qs.QS.ActivityStarter;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.io.FileDescriptor;
@@ -640,28 +641,43 @@
         refreshUsers(UserHandle.USER_ALL);
     }
 
+    @VisibleForTesting
+    public void addAdapter(WeakReference<BaseUserAdapter> adapter) {
+        mAdapters.add(adapter);
+    }
+
+    @VisibleForTesting
+    public KeyguardMonitor getKeyguardMonitor() {
+        return mKeyguardMonitor;
+    }
+
+    @VisibleForTesting
+    public ArrayList<UserRecord> getUsers() {
+        return mUsers;
+    }
+
     public static abstract class BaseUserAdapter extends BaseAdapter {
 
         final UserSwitcherController mController;
 
         protected BaseUserAdapter(UserSwitcherController controller) {
             mController = controller;
-            controller.mAdapters.add(new WeakReference<>(this));
+            controller.addAdapter(new WeakReference<>(this));
         }
 
         @Override
         public int getCount() {
-            boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
-                    && mController.mKeyguardMonitor.isSecure()
-                    && !mController.mKeyguardMonitor.canSkipBouncer();
+            boolean secureKeyguardShowing = mController.getKeyguardMonitor().isShowing()
+                    && mController.getKeyguardMonitor().isSecure()
+                    && !mController.getKeyguardMonitor().canSkipBouncer();
             if (!secureKeyguardShowing) {
-                return mController.mUsers.size();
+                return mController.getUsers().size();
             }
             // The lock screen is secure and showing. Filter out restricted records.
-            final int N = mController.mUsers.size();
+            final int N = mController.getUsers().size();
             int count = 0;
             for (int i = 0; i < N; i++) {
-                if (mController.mUsers.get(i).isRestricted) {
+                if (mController.getUsers().get(i).isRestricted) {
                     break;
                 } else {
                     count++;
@@ -672,7 +688,7 @@
 
         @Override
         public UserRecord getItem(int position) {
-            return mController.mUsers.get(position);
+            return mController.getUsers().get(position);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 0e91b0b..bcdb62d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -22,9 +22,9 @@
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ZenRule;
 
-public interface ZenModeController {
-    void addCallback(Callback callback);
-    void removeCallback(Callback callback);
+import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+
+public interface ZenModeController extends CallbackController<Callback> {
     void setZen(int zen, Uri conditionId, String reason);
     int getZen();
     ZenRule getManualRule();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
new file mode 100644
index 0000000..e5bb3d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
@@ -0,0 +1,77 @@
+/*
+ * 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.tuner;
+
+import android.app.AlertDialog;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.support.v7.preference.ListPreference;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+import com.android.systemui.R;
+
+import libcore.util.Objects;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class ThemePreference extends ListPreference {
+
+    public ThemePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void onAttached() {
+        super.onAttached();
+        File file = new File("/vendor/overlay");
+        ArrayList<String> options = Lists.newArrayList(file.list());
+        String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
+        if (TextUtils.isEmpty(def)) {
+            def = getContext().getString(R.string.default_theme);
+        }
+        if (!options.contains(def)) {
+            options.add(0, def);
+        }
+        String[] list = options.toArray(new String[options.size()]);
+        setVisible(options.size() > 1);
+        setEntries(list);
+        setEntryValues(list);
+        updateValue();
+    }
+
+    private void updateValue() {
+        setValue(getContext().getSystemService(UiModeManager.class).getTheme());
+    }
+
+    @Override
+    protected void notifyChanged() {
+        super.notifyChanged();
+        if (!Objects.equal(getValue(),
+                getContext().getSystemService(UiModeManager.class).getTheme())) {
+            new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.change_theme_reboot)
+                    .setPositiveButton(com.android.internal.R.string.global_action_restart, (d, i)
+                            -> getContext().getSystemService(UiModeManager.class)
+                            .setTheme(getValue()))
+                    .setNegativeButton(android.R.string.cancel, (d, i) -> updateValue())
+                    .show();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index fd6500f..f835e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -102,7 +102,9 @@
                 TunerService.showResetRequest(getContext(), new Runnable() {
                     @Override
                     public void run() {
-                        getActivity().finish();
+                        if (getActivity() != null) {
+                            getActivity().finish();
+                        }
                     }
                 });
                 return true;
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index b5584f3..6e17cf4 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
index fccb2a2..f3be945 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
@@ -16,26 +16,24 @@
 
 package com.android.keyguard;
 
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import static junit.framework.Assert.*;
-import static org.mockito.Mockito.mock;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class KeyguardMessageAreaTest extends SysuiTestCase {
-    private Context mContext = InstrumentationRegistry.getTargetContext();
     private Handler mHandler = new Handler(Looper.getMainLooper());
     private KeyguardMessageArea mMessageArea;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 5cb5e68..cb0f7a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -17,7 +17,7 @@
 package com.android.systemui;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.anyString;
@@ -28,27 +28,24 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class BatteryMeterDrawableTest {
+public class BatteryMeterDrawableTest extends SysuiTestCase {
 
-    private Context mContext;
     private Resources mResources;
     private BatteryMeterDrawable mBatteryMeter;
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
         mResources = mContext.getResources();
         mBatteryMeter = new BatteryMeterDrawable(mContext, 0);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
new file mode 100644
index 0000000..f87336c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
@@ -0,0 +1,194 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.app.FragmentController;
+import android.app.FragmentHostCallback;
+import android.app.FragmentManagerNonConfig;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcelable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Base class for fragment class tests.  Just adding one for any fragment will push it through
+ * general lifecycle events and ensure no basic leaks are happening.  This class also implements
+ * the host for subclasses, so they can push it into desired states and do any unit testing
+ * required.
+ */
+public abstract class FragmentTestCase extends LeakCheckedTest {
+
+    private static final int VIEW_ID = 42;
+    private final Class<? extends Fragment> mCls;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private FrameLayout mView;
+    protected FragmentController mFragments;
+    protected Fragment mFragment;
+
+    public FragmentTestCase(Class<? extends Fragment> cls) {
+        mCls = cls;
+    }
+
+    @Before
+    public void setupFragment() throws IllegalAccessException, InstantiationException {
+        mView = new FrameLayout(mContext);
+        mView.setId(VIEW_ID);
+        mHandlerThread = new HandlerThread("FragmentTestThread");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mFragment = mCls.newInstance();
+        postAndWait(() -> {
+            mFragments = FragmentController.createController(new HostCallbacks());
+            mFragments.attachHost(null);
+            mFragments.getFragmentManager().beginTransaction()
+                    .replace(VIEW_ID, mFragment)
+                    .commit();
+        });
+    }
+
+    @After
+    public void tearDown() {
+        if (mFragments != null) {
+            // Set mFragments to null to let it know not to destroy.
+            postAndWait(() -> mFragments.dispatchDestroy());
+        }
+        mHandlerThread.quit();
+    }
+
+    @Test
+    public void testCreateDestroy() {
+        postAndWait(() -> mFragments.dispatchCreate());
+        destroyFragments();
+    }
+
+    @Test
+    public void testStartStop() {
+        postAndWait(() -> mFragments.dispatchStart());
+        postAndWait(() -> mFragments.dispatchStop());
+    }
+
+    @Test
+    public void testResumePause() {
+        postAndWait(() -> mFragments.dispatchResume());
+        postAndWait(() -> mFragments.dispatchPause());
+    }
+
+    @Test
+    public void testRecreate() {
+        postAndWait(() -> mFragments.dispatchResume());
+        postAndWait(() -> {
+            mFragments.dispatchPause();
+            Parcelable p = mFragments.saveAllState();
+            mFragments.dispatchDestroy();
+
+            mFragments = FragmentController.createController(new HostCallbacks());
+            mFragments.attachHost(null);
+            mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
+            mFragments.dispatchResume();
+        });
+    }
+
+    @Test
+    public void testMultipleResumes() {
+        postAndWait(() -> mFragments.dispatchResume());
+        postAndWait(() -> mFragments.dispatchStop());
+        postAndWait(() -> mFragments.dispatchResume());
+    }
+
+    protected void destroyFragments() {
+        postAndWait(() -> mFragments.dispatchDestroy());
+        mFragments = null;
+    }
+
+    protected void postAndWait(Runnable r) {
+        mHandler.post(r);
+        waitForFragments();
+    }
+
+    protected void waitForFragments() {
+        waitForIdleSync(mHandler);
+    }
+
+    private View findViewById(int id) {
+        return mView.findViewById(id);
+    }
+
+    private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> {
+        public HostCallbacks() {
+            super(getTrackedContext(), FragmentTestCase.this.mHandler, 0);
+        }
+
+        @Override
+        public FragmentTestCase onGetHost() {
+            return FragmentTestCase.this;
+        }
+
+        @Override
+        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        }
+
+        @Override
+        public boolean onShouldSaveFragmentState(Fragment fragment) {
+            return true; // True for now.
+        }
+
+        @Override
+        public LayoutInflater onGetLayoutInflater() {
+            return LayoutInflater.from(mContext);
+        }
+
+        @Override
+        public boolean onUseFragmentManagerInflaterFactory() {
+            return true;
+        }
+
+        @Override
+        public boolean onHasWindowAnimations() {
+            return false;
+        }
+
+        @Override
+        public int onGetWindowAnimations() {
+            return 0;
+        }
+
+        @Override
+        public void onAttachFragment(Fragment fragment) {
+        }
+
+        @Nullable
+        @Override
+        public View onFindViewById(int id) {
+            return FragmentTestCase.this.findViewById(id);
+        }
+
+        @Override
+        public boolean onHasView() {
+            return true;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
new file mode 100644
index 0000000..d64669d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for tests to check if receivers are left registered, services bound, or other
+ * listeners listening.
+ */
+public class LeakCheckedTest extends SysuiTestCase {
+    private static final String TAG = "LeakCheckedTest";
+
+    private final Map<String, Tracker> mTrackers = new HashMap<>();
+    private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
+    private TrackingContext mTrackedContext;
+
+    @Rule
+    public TestWatcher successWatcher = new TestWatcher() {
+        @Override
+        protected void succeeded(Description description) {
+            verify();
+        }
+    };
+
+    @Before
+    public void setup() {
+        mTrackedContext = new TrackingContext(mContext);
+        addSupportedLeakCheckers();
+    }
+
+    public <T> T getLeakChecker(Class<T> cls) {
+        T obj = (T) mLeakCheckers.get(cls);
+        if (obj == null) {
+            Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
+        }
+        return obj;
+    }
+
+    public Context getTrackedContext() {
+        return mTrackedContext;
+    }
+
+    private Tracker getTracker(String tag) {
+        Tracker t = mTrackers.get(tag);
+        if (t == null) {
+            t = new Tracker();
+            mTrackers.put(tag, t);
+        }
+        return t;
+    }
+
+    public void verify() {
+        mTrackers.values().forEach(Tracker::verify);
+    }
+
+    public static class Tracker {
+        private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
+
+        LeakInfo getLeakInfo(Object object) {
+            LeakInfo leakInfo = mObjects.get(object);
+            if (leakInfo == null) {
+                leakInfo = new LeakInfo();
+                mObjects.put(object, leakInfo);
+            }
+            return leakInfo;
+        }
+
+        private void verify() {
+            mObjects.values().forEach(LeakInfo::verify);
+        }
+    }
+
+    public static class LeakInfo {
+        private List<Throwable> mThrowables = new ArrayList<>();
+
+        private LeakInfo() {
+        }
+
+        private void addAllocation(Throwable t) {
+            // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
+            mThrowables.add(t);
+        }
+
+        private void clearAllocations() {
+            mThrowables.clear();
+        }
+
+        public void verify() {
+            if (mThrowables.size() == 0) return;
+            Log.e(TAG, "Listener or binding not properly released");
+            for (Throwable t : mThrowables) {
+                Log.e(TAG, "Allocation found", t);
+            }
+            StringWriter writer = new StringWriter();
+            mThrowables.get(0).printStackTrace(new PrintWriter(writer));
+            Assert.fail("Listener or binding not properly released\n"
+                    + writer.toString());
+        }
+    }
+
+    private void addSupportedLeakCheckers() {
+        addListening("bluetooth", BluetoothController.class);
+        addListening("location", LocationController.class);
+        addListening("rotation", RotationLockController.class);
+        addListening("zen", ZenModeController.class);
+        addListening("cast", CastController.class);
+        addListening("hotspot", HotspotController.class);
+        addListening("flashlight", FlashlightController.class);
+        addListening("user", UserInfoController.class);
+        addListening("keyguard", KeyguardMonitor.class);
+        addListening("battery", BatteryController.class);
+        addListening("security", SecurityController.class);
+        addListening("profile", ManagedProfileController.class);
+        addListening("alarm", NextAlarmController.class);
+        NetworkController network = addListening("network", NetworkController.class);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker("emergency").getLeakInfo(invocation.getArguments()[0])
+                        .addAllocation(new Throwable());
+                return null;
+            }
+        }).when(network).addEmergencyListener(any());
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker("emergency").getLeakInfo(invocation.getArguments()[0]).clearAllocations();
+                return null;
+            }
+        }).when(network).removeEmergencyListener(any());
+        DataSaverController datasaver = addListening("datasaver", DataSaverController.class);
+        when(network.getDataSaverController()).thenReturn(datasaver);
+    }
+
+    private <T extends CallbackController> T addListening(final String tag, Class<T> cls) {
+        T mock = mock(cls);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker(tag).getLeakInfo(invocation.getArguments()[0])
+                        .addAllocation(new Throwable());
+                return null;
+            }
+        }).when(mock).addCallback(any());
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
+                return null;
+            }
+        }).when(mock).removeCallback(any());
+        mLeakCheckers.put(cls, mock);
+        return mock;
+    }
+
+    class TrackingContext extends ContextWrapper {
+        public TrackingContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
+            return super.registerReceiver(receiver, filter);
+        }
+
+        @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+                String broadcastPermission, Handler scheduler) {
+            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
+            return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+        }
+
+        @Override
+        public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+                IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
+            return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+                    scheduler);
+        }
+
+        @Override
+        public void unregisterReceiver(BroadcastReceiver receiver) {
+            getTracker("receiver").getLeakInfo(receiver).clearAllocations();
+            super.unregisterReceiver(receiver);
+        }
+
+        @Override
+        public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
+            return super.bindService(service, conn, flags);
+        }
+
+        @Override
+        public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+                Handler handler, UserHandle user) {
+            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
+            return super.bindServiceAsUser(service, conn, flags, handler, user);
+        }
+
+        @Override
+        public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+                UserHandle user) {
+            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
+            return super.bindServiceAsUser(service, conn, flags, user);
+        }
+
+        @Override
+        public void unbindService(ServiceConnection conn) {
+            getTracker("service").getLeakInfo(conn).clearAllocations();
+            super.unbindService(conn);
+        }
+
+        @Override
+        public void registerComponentCallbacks(ComponentCallbacks callback) {
+            getTracker("component").getLeakInfo(callback).addAllocation(new Throwable());
+            super.registerComponentCallbacks(callback);
+        }
+
+        @Override
+        public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+            getTracker("component").getLeakInfo(callback).clearAllocations();
+            super.unregisterComponentCallbacks(callback);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index d943eb6..5dac8e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -20,6 +20,10 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
+
+import com.android.systemui.utils.TestableContext;
+
+import org.junit.After;
 import org.junit.Before;
 
 /**
@@ -28,11 +32,16 @@
 public class SysuiTestCase {
 
     private Handler mHandler;
-    protected Context mContext;
+    protected TestableContext mContext;
 
     @Before
     public void SysuiSetup() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
+        mContext = new TestableContext(InstrumentationRegistry.getTargetContext());
+    }
+
+    @After
+    public void cleanup() throws Exception {
+        mContext.getSettingsProvider().clearOverrides(this);
     }
 
     protected Context getContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 39b6412..5c87fb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -17,9 +17,11 @@
 package com.android.systemui.power;
 
 import static android.test.MoreAsserts.assertNotEqual;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
@@ -29,9 +31,11 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,7 +43,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class PowerNotificationWarningsTest {
+public class PowerNotificationWarningsTest extends SysuiTestCase {
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
@@ -47,7 +51,7 @@
     public void setUp() throws Exception {
         // Test Instance.
         mPowerNotificationWarnings = new PowerNotificationWarnings(
-                InstrumentationRegistry.getTargetContext(), mMockNotificationManager, null);
+                mContext, mMockNotificationManager, null);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
new file mode 100644
index 0000000..6ceaead
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.qs;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.FragmentTestCase;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class QSFragmentTest extends FragmentTestCase {
+
+    public QSFragmentTest() {
+        super(QSFragment.class);
+    }
+
+    @Test
+    public void testListening() {
+        QSFragment qs = (QSFragment) mFragment;
+        postAndWait(() -> mFragments.dispatchResume());
+        UserSwitcherController userSwitcher = mock(UserSwitcherController.class);
+        KeyguardMonitor keyguardMonitor = getLeakChecker(KeyguardMonitor.class);
+        when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
+        when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
+        QSTileHost host = new QSTileHost(getTrackedContext(),
+                mock(PhoneStatusBar.class),
+                getLeakChecker(BluetoothController.class),
+                getLeakChecker(LocationController.class),
+                getLeakChecker(RotationLockController.class),
+                getLeakChecker(NetworkController.class),
+                getLeakChecker(ZenModeController.class),
+                getLeakChecker(HotspotController.class),
+                getLeakChecker(CastController.class),
+                getLeakChecker(FlashlightController.class),
+                userSwitcher,
+                getLeakChecker(UserInfoController.class),
+                keyguardMonitor,
+                getLeakChecker(SecurityController.class),
+                getLeakChecker(BatteryController.class),
+                mock(StatusBarIconController.class),
+                getLeakChecker(NextAlarmController.class));
+        qs.setHost(host);
+        Handler h = new Handler(host.getLooper());
+
+        qs.setListening(true);
+        waitForIdleSync(h);
+
+        qs.setListening(false);
+        waitForIdleSync(h);
+
+        host.destroy();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 8eecfcf..5401c30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -26,11 +27,12 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,13 +40,13 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class TileLayoutTest {
-    private Context mContext = InstrumentationRegistry.getTargetContext();
-    private final TileLayout mTileLayout = new TileLayout(mContext);
+public class TileLayoutTest extends SysuiTestCase {
+    private TileLayout mTileLayout;
     private int mLayoutSizeForOneTile;
 
     @Before
     public void setUp() throws Exception {
+        mTileLayout = new TileLayout(mContext);
         // Layout needs to leave space for the tile margins. Three times the margin size is
         // sufficient for any number of columns.
         mLayoutSizeForOneTile =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index d7ff04f..782a489 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -16,7 +16,7 @@
 package com.android.systemui.qs.external;
 
 import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
@@ -25,12 +25,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Service;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
 import android.content.pm.ServiceInfo;
@@ -38,19 +35,16 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.service.quicksettings.TileService;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.ArraySet;
-import android.util.Log;
+
+import com.android.systemui.SysuiTestCase;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -61,7 +55,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class TileLifecycleManagerTest {
+public class TileLifecycleManagerTest extends SysuiTestCase {
     private static final int TEST_FAIL_TIMEOUT = 5000;
 
     private final Context mMockContext = Mockito.mock(Context.class);
@@ -78,8 +72,7 @@
     @Before
     public void setUp() throws Exception {
         setPackageEnabled(true);
-        mTileServiceComponentName = new ComponentName(
-                InstrumentationRegistry.getTargetContext(), "FakeTileService.class");
+        mTileServiceComponentName = new ComponentName(mContext, "FakeTileService.class");
 
         // Stub.asInterface will just return itself.
         when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 6c9cfe0..136e7c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -35,7 +35,7 @@
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.SubscriptionDefaults;
 import com.android.systemui.SysuiTestCase;
-import org.junit.After;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.TestWatcher;
@@ -118,7 +118,7 @@
 
         // Trigger blank callbacks to always get the current state (some tests don't trigger
         // changes from default state).
-        mNetworkController.addSignalCallback(mock(SignalCallback.class));
+        mNetworkController.addCallback(mock(SignalCallback.class));
         mNetworkController.addEmergencyListener(null);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
new file mode 100644
index 0000000..34f2e01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
@@ -0,0 +1,131 @@
+/*
+ * 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.utils;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.util.ArraySet;
+
+import com.google.android.collect.Maps;
+
+import java.util.Map;
+
+/**
+ * Alternative to a MockContentResolver that falls back to real providers.
+ */
+public class FakeContentResolver extends ContentResolver {
+
+    private final Map<String, ContentProvider> mProviders = Maps.newHashMap();
+    private final ContentResolver mParent;
+    private final ArraySet<ContentProvider> mInUse = new ArraySet<>();
+    private boolean mFallbackToExisting;
+
+    public FakeContentResolver(Context context) {
+        super(context);
+        mParent = context.getContentResolver();
+        mFallbackToExisting = true;
+    }
+
+    /**
+     * Sets whether existing providers should be returned when a mock does not exist.
+     * The default is true.
+     */
+    public void setFallbackToExisting(boolean fallbackToExisting) {
+        mFallbackToExisting = fallbackToExisting;
+    }
+
+    /**
+     * Adds access to a provider based on its authority
+     *
+     * @param name The authority name associated with the provider.
+     * @param provider An instance of {@link android.content.ContentProvider} or one of its
+     * subclasses, or null.
+     */
+    public void addProvider(String name, ContentProvider provider) {
+        mProviders.put(name, provider);
+    }
+
+    @Override
+    protected IContentProvider acquireProvider(Context context, String name) {
+        final ContentProvider provider = mProviders.get(name);
+        if (provider != null) {
+            return provider.getIContentProvider();
+        } else {
+            return mFallbackToExisting ? mParent.acquireProvider(name) : null;
+        }
+    }
+
+    @Override
+    protected IContentProvider acquireExistingProvider(Context context, String name) {
+        final ContentProvider provider = mProviders.get(name);
+        if (provider != null) {
+            return provider.getIContentProvider();
+        } else {
+            return mFallbackToExisting ? mParent.acquireExistingProvider(
+                    new Uri.Builder().authority(name).build()) : null;
+        }
+    }
+
+    @Override
+    public boolean releaseProvider(IContentProvider provider) {
+        if (!mFallbackToExisting) return true;
+        if (mInUse.contains(provider)) {
+            mInUse.remove(provider);
+            return true;
+        }
+        return mParent.releaseProvider(provider);
+    }
+
+    @Override
+    protected IContentProvider acquireUnstableProvider(Context c, String name) {
+        final ContentProvider provider = mProviders.get(name);
+        if (provider != null) {
+            return provider.getIContentProvider();
+        } else {
+            return mFallbackToExisting ? mParent.acquireUnstableProvider(name) : null;
+        }
+    }
+
+    @Override
+    public boolean releaseUnstableProvider(IContentProvider icp) {
+        if (!mFallbackToExisting) return true;
+        if (mInUse.contains(icp)) {
+            mInUse.remove(icp);
+            return true;
+        }
+        return mParent.releaseUnstableProvider(icp);
+    }
+
+    @Override
+    public void unstableProviderDied(IContentProvider icp) {
+        if (!mFallbackToExisting) return;
+        if (mInUse.contains(icp)) {
+            return;
+        }
+        mParent.unstableProviderDied(icp);
+    }
+
+    @Override
+    public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+        if (!mFallbackToExisting) return;
+        if (!mProviders.containsKey(uri.getAuthority())) {
+            super.notifyChange(uri, observer, syncToNetwork);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java
new file mode 100644
index 0000000..f40fe4c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java
@@ -0,0 +1,298 @@
+/*
+ * 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.utils;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.test.mock.MockContentProvider;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider.Builder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Allows calls to android.provider.Settings to be tested easier.  A SettingOverride
+ * can be acquired and a set of specific settings can be set to a value (and not changed
+ * in the system when set), so that they can be tested without breaking the test device.
+ * <p>
+ * To use, in the before method acquire the override add all settings that will affect if
+ * your test passes or not.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder()
+ *         .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0")
+ *         .build();
+ * }
+ * </pre>
+ *
+ * Then in the after free up the settings.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * mSettingOverride.release();
+ * }
+ * </pre>
+ */
+public class FakeSettingsProvider extends MockContentProvider {
+
+    private static final String TAG = "FakeSettingsProvider";
+    private static final boolean DEBUG = false;
+
+    // Number of times to try to acquire a setting if in use.
+    private static final int MAX_TRIES = 10;
+    // Time to wait for each setting.  WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time
+    // for a setting.
+    private static final long WAIT_TIMEOUT = 1000;
+
+    private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>();
+    private final Map<SysuiTestCase, List<SettingOverrider>> mOwners = new ArrayMap<>();
+
+    private static FakeSettingsProvider sInstance;
+    private final ContentProviderClient mSettings;
+    private final ContentResolver mResolver;
+
+    private FakeSettingsProvider(ContentProviderClient settings, ContentResolver resolver) {
+        mSettings = settings;
+        mResolver = resolver;
+    }
+
+    public Builder acquireOverridesBuilder(SysuiTestCase test) {
+        return new Builder(this, test);
+    }
+
+    public void clearOverrides(SysuiTestCase test) {
+        List<SettingOverrider> overrides = mOwners.remove(test);
+        if (overrides != null) {
+            overrides.forEach(override -> override.ensureReleased());
+        }
+    }
+
+    public Bundle call(String method, String arg, Bundle extras) {
+        // Methods are "GET_system", "GET_global", "PUT_secure", etc.
+        final String[] commands = method.split("_", 2);
+        final String op = commands[0];
+        final String table = commands[1];
+
+        synchronized (mOverrideMap) {
+            SettingOverrider overrider = mOverrideMap.get(key(table, arg));
+            if (overrider == null) {
+                // Fall through to real settings.
+                try {
+                    if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
+                    // TODO: Add our own version of caching to handle this.
+                    Bundle call = mSettings.call(method, arg, extras);
+                    call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
+                    return call;
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            String value;
+            Bundle out = new Bundle();
+            switch (op) {
+                case "GET":
+                    value = overrider.get(table, arg);
+                    if (value != null) {
+                        out.putString(Settings.NameValueTable.VALUE, value);
+                    }
+                    break;
+                case "PUT":
+                    value = extras.getString(Settings.NameValueTable.VALUE, null);
+                    if (value != null) {
+                        overrider.put(table, arg, value);
+                    } else {
+                        overrider.remove(table, arg);
+                    }
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown command " + method);
+            }
+            return out;
+        }
+    }
+
+    private void acquireSettings(SettingOverrider overridder, Set<String> keys,
+            SysuiTestCase owner) throws AcquireTimeoutException {
+        synchronized (mOwners) {
+            List<SettingOverrider> list = mOwners.get(owner);
+            if (list == null) {
+                list = new ArrayList<>();
+                mOwners.put(owner, list);
+            }
+            list.add(overridder);
+        }
+        synchronized (mOverrideMap) {
+            for (int i = 0; i < MAX_TRIES; i++) {
+                if (checkKeys(keys, false)) break;
+                try {
+                    if (DEBUG) Log.d(TAG, "Waiting for contention to finish");
+                    mOverrideMap.wait(WAIT_TIMEOUT);
+                } catch (InterruptedException e) {
+                }
+            }
+            checkKeys(keys, true);
+            for (String key : keys) {
+                if (DEBUG) Log.d(TAG, "Acquiring " + key);
+                mOverrideMap.put(key, overridder);
+            }
+        }
+    }
+
+    private void releaseSettings(Set<String> keys) {
+        synchronized (mOverrideMap) {
+            for (String key : keys) {
+                if (DEBUG) Log.d(TAG, "Releasing " + key);
+                mOverrideMap.remove(key);
+            }
+            if (DEBUG) Log.d(TAG, "Notifying");
+            mOverrideMap.notify();
+        }
+    }
+
+    @VisibleForTesting
+    public Object getLock() {
+        return mOverrideMap;
+    }
+
+    private boolean checkKeys(Set<String> keys, boolean shouldThrow)
+            throws AcquireTimeoutException {
+        for (String key : keys) {
+            if (mOverrideMap.containsKey(key)) {
+                if (shouldThrow) {
+                    throw new AcquireTimeoutException("Could not acquire " + key);
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static class SettingOverrider {
+        private final Set<String> mValidKeys;
+        private final Map<String, String> mValueMap = new ArrayMap<>();
+        private final FakeSettingsProvider mProvider;
+        private boolean mReleased;
+
+        private SettingOverrider(Set<String> keys, FakeSettingsProvider provider) {
+            mValidKeys = new ArraySet<>(keys);
+            mProvider = provider;
+        }
+
+        private void ensureReleased() {
+            if (!mReleased) {
+                release();
+            }
+        }
+
+        public void release() {
+            mProvider.releaseSettings(mValidKeys);
+            mReleased = true;
+        }
+
+        private void putDirect(String key, String value) {
+            mValueMap.put(key, value);
+        }
+
+        public void put(String table, String key, String value) {
+            if (!mValidKeys.contains(key(table, key))) {
+                throw new IllegalArgumentException("Key " + table + " " + key
+                        + " not acquired for this overrider");
+            }
+            mValueMap.put(key(table, key), value);
+        }
+
+        public void remove(String table, String key) {
+            if (!mValidKeys.contains(key(table, key))) {
+                throw new IllegalArgumentException("Key " + table + " " + key
+                        + " not acquired for this overrider");
+            }
+            mValueMap.remove(key(table, key));
+        }
+
+        public String get(String table, String key) {
+            if (!mValidKeys.contains(key(table, key))) {
+                throw new IllegalArgumentException("Key " + table + " " + key
+                        + " not acquired for this overrider");
+            }
+            Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key)));
+            return mValueMap.get(key(table, key));
+        }
+
+        public static class Builder {
+            private final FakeSettingsProvider mProvider;
+            private final SysuiTestCase mOwner;
+            private Set<String> mKeys = new ArraySet<>();
+            private Map<String, String> mValues = new ArrayMap<>();
+
+            private Builder(FakeSettingsProvider provider, SysuiTestCase test) {
+                mProvider = provider;
+                mOwner = test;
+            }
+
+            public Builder addSetting(String table, String key) {
+                mKeys.add(key(table, key));
+                return this;
+            }
+
+            public Builder addSetting(String table, String key, String value) {
+                addSetting(table, key);
+                mValues.put(key(table, key), value);
+                return this;
+            }
+
+            public SettingOverrider build() throws AcquireTimeoutException {
+                SettingOverrider overrider = new SettingOverrider(mKeys, mProvider);
+                mProvider.acquireSettings(overrider, mKeys, mOwner);
+                mValues.forEach((key, value) -> overrider.putDirect(key, value));
+                return overrider;
+            }
+        }
+    }
+
+    public static class AcquireTimeoutException extends Exception {
+        public AcquireTimeoutException(String str) {
+            super(str);
+        }
+    }
+
+    private static String key(String table, String key) {
+        return table + "_" + key;
+    }
+
+    /**
+     * Since the settings provider is cached inside android.provider.Settings, this must
+     * be gotten statically to ensure there is only one instance referenced.
+     * @param settings
+     */
+    public static FakeSettingsProvider getFakeSettingsProvider(ContentProviderClient settings,
+            ContentResolver resolver) {
+        if (sInstance == null) {
+            sInstance = new FakeSettingsProvider(settings, resolver);
+        }
+        return sInstance;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
new file mode 100644
index 0000000..63bb5e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.utils.FakeSettingsProvider.AcquireTimeoutException;
+import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FakeSettingsProviderTest extends SysuiTestCase {
+
+    public static final String NONEXISTENT_SETTING = "nonexistent_setting";
+    private static final String TAG = "FakeSettingsProviderTest";
+    private SettingOverrider mOverrider;
+    private ContentResolver mContentResolver;
+
+    @Before
+    public void setup() throws AcquireTimeoutException {
+        mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this)
+                .addSetting("secure", NONEXISTENT_SETTING)
+                .addSetting("global", NONEXISTENT_SETTING, "initial value")
+                .addSetting("global", Global.DEVICE_PROVISIONED)
+                .build();
+        mContentResolver = mContext.getContentResolver();
+    }
+
+    @After
+    public void teardown() {
+        if (mOverrider != null) {
+            mOverrider.release();
+        }
+    }
+
+    @Test
+    public void testInitialValueSecure() {
+        String value = Secure.getString(mContentResolver, NONEXISTENT_SETTING);
+        assertNull(value);
+    }
+
+    @Test
+    public void testInitialValueGlobal() {
+        String value = Global.getString(mContentResolver, NONEXISTENT_SETTING);
+        assertEquals("initial value", value);
+    }
+
+    @Test
+    public void testSeparateTables() {
+        Secure.putString(mContentResolver, NONEXISTENT_SETTING, "something");
+        Global.putString(mContentResolver, NONEXISTENT_SETTING, "else");
+        assertEquals("something", Secure.getString(mContentResolver, NONEXISTENT_SETTING));
+        assertEquals("something", mOverrider.get("secure", NONEXISTENT_SETTING));
+        assertEquals("else", Global.getString(mContentResolver, NONEXISTENT_SETTING));
+        assertEquals("else", mOverrider.get("global", NONEXISTENT_SETTING));
+    }
+
+    @Test
+    public void testPassThrough() {
+        // Grab the value of a setting that is not overridden.
+        assertTrue(Secure.getInt(mContentResolver, Secure.USER_SETUP_COMPLETE, 0) != 0);
+    }
+
+    @Test
+    public void testOverrideExisting() {
+        // Grab the value of a setting that is overridden and will be different than the actual
+        // value.
+        assertNull(Global.getString(mContentResolver, Global.DEVICE_PROVISIONED));
+    }
+
+    @Test
+    public void testRelease() {
+        // Verify different value.
+        assertNull(Global.getString(mContentResolver, Global.DEVICE_PROVISIONED));
+        mOverrider.release();
+        mOverrider = null;
+        // Verify actual value after release.
+        assertEquals("1", Global.getString(mContentResolver, Global.DEVICE_PROVISIONED));
+    }
+
+    @Test
+    public void testAutoRelease() throws Exception {
+        super.cleanup();
+        mContext.getSettingsProvider().acquireOverridesBuilder(this)
+                .addSetting("global", Global.DEVICE_PROVISIONED)
+                .build();
+    }
+
+    @Test
+    public void testContention() throws AcquireTimeoutException, InterruptedException {
+        SettingOverrider[] overriders = new SettingOverrider[2];
+        Object lock = new Object();
+        String secure = "secure";
+        String key = "something shared";
+        String[] result = new String[1];
+        overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder(this)
+                .addSetting(secure, key, "Some craziness")
+                .build();
+        synchronized (lock) {
+            HandlerThread t = runOnHandler(() -> {
+                try {
+                    // Grab the lock that will be used for the settings ownership to ensure
+                    // we have some contention going on.
+                    synchronized (mContext.getSettingsProvider().getLock()) {
+                        synchronized (lock) {
+                            // Let the other thread know to release the settings, but it won't
+                            // be able to until this thread waits in the build() method.
+                            lock.notify();
+                        }
+                        overriders[1] = mContext.getSettingsProvider()
+                                .acquireOverridesBuilder(FakeSettingsProviderTest.this)
+                                .addSetting(secure, key, "default value")
+                                .build();
+                        // Ensure that the default is the one we set, and not left over from
+                        // the other setting override.
+                        result[0] = Settings.Secure.getString(mContentResolver, key);
+                        synchronized (lock) {
+                            // Let the main thread know we are done.
+                            lock.notify();
+                        }
+                    }
+                } catch (AcquireTimeoutException e) {
+                    Log.e(TAG, "Couldn't acquire setting", e);
+                }
+            });
+            // Wait for the thread to hold the acquire lock, then release the settings.
+            lock.wait();
+            overriders[0].release();
+            // Wait for the thread to be done getting the value.
+            lock.wait();
+            // Quit and cleanup.
+            t.quitSafely();
+            assertNotNull(overriders[1]);
+            overriders[1].release();
+        }
+        // Verify the value was the expected one from the thread's SettingOverride.
+        assertEquals("default value", result[0]);
+    }
+
+    private HandlerThread runOnHandler(Runnable r) {
+        HandlerThread t = new HandlerThread("Test Thread");
+        t.start();
+        new Handler(t.getLooper()).post(r);
+        return t;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
new file mode 100644
index 0000000..5179823
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
@@ -0,0 +1,52 @@
+/*
+ * 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.utils;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.provider.Settings;
+
+public class TestableContext extends ContextWrapper {
+
+    private final FakeContentResolver mFakeContentResolver;
+    private final FakeSettingsProvider mSettingsProvider;
+
+    public TestableContext(Context base) {
+        super(base);
+        mFakeContentResolver = new FakeContentResolver(base);
+        ContentProviderClient settings = base.getContentResolver()
+                .acquireContentProviderClient(Settings.AUTHORITY);
+        mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings,
+                mFakeContentResolver);
+        mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+    }
+
+    public FakeSettingsProvider getSettingsProvider() {
+        return mSettingsProvider;
+    }
+
+    @Override
+    public FakeContentResolver getContentResolver() {
+        return mFakeContentResolver;
+    }
+
+    @Override
+    public Context getApplicationContext() {
+        // Return this so its always a TestableContext.
+        return this;
+    }
+}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 82897fb..2558045 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1940,13 +1940,9 @@
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
             final Alarm a = mPendingWhileIdleAlarms.get(i);
-            try {
-                if (a.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
-                        uid, a.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
-                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
-                    mPendingWhileIdleAlarms.remove(i);
-                }
-            } catch (RemoteException e) {
+            if (a.uid == uid) {
+                // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                mPendingWhileIdleAlarms.remove(i);
             }
         }
 
@@ -2807,15 +2803,22 @@
         @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
         }
 
-        @Override public void onUidGone(int uid) throws RemoteException {
+        @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
+            if (disabled) {
+                synchronized (mLock) {
+                    removeForStoppedLocked(uid);
+                }
+            }
         }
 
         @Override public void onUidActive(int uid) throws RemoteException {
         }
 
-        @Override public void onUidIdle(int uid) throws RemoteException {
-            synchronized (mLock) {
-                removeForStoppedLocked(uid);
+        @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+            if (disabled) {
+                synchronized (mLock) {
+                    removeForStoppedLocked(uid);
+                }
             }
         }
     };
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 9b3fac3..f7068cf 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -59,6 +59,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -118,7 +119,6 @@
     private static final int SERVICE_IBLUETOOTHGATT = 2;
 
     private final Context mContext;
-    private static int mBleAppCount = 0;
 
     // Locks are not provided for mName and mAddress.
     // They are accessed in handler or broadcast receiver, same thread context.
@@ -212,10 +212,7 @@
 
                     if (isAirplaneModeOn()) {
                         // Clear registered LE apps to force shut-off
-                        synchronized (this) {
-                            mBleAppCount = 0;
-                            mBleApps.clear();
-                        }
+                        clearBleApps();
                         if (st == BluetoothAdapter.STATE_BLE_ON) {
                             //if state is BLE_ON make sure you trigger disableBLE part
                             try {
@@ -460,28 +457,28 @@
     class ClientDeathRecipient implements IBinder.DeathRecipient {
         public void binderDied() {
             if (DBG) Slog.d(TAG, "Binder is dead - unregister Ble App");
-            if (mBleAppCount > 0) --mBleAppCount;
-
-            if (mBleAppCount == 0) {
-                if (DBG) Slog.d(TAG, "Disabling LE only mode after application crash");
-                try {
-                    mBluetoothLock.readLock().lock();
-                    if (mBluetooth != null &&
-                        mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
-                        mEnable = false;
-                        mBluetooth.onBrEdrDown();
-                    }
-                } catch (RemoteException e) {
-                     Slog.e(TAG,"Unable to call onBrEdrDown", e);
-                } finally {
-                    mBluetoothLock.readLock().unlock();
+            if (isBleAppPresent()) {
+              // Nothing to do, another app is here.
+              return;
+            }
+            if (DBG) Slog.d(TAG, "Disabling LE only mode after application crash");
+            try {
+                mBluetoothLock.readLock().lock();
+                if (mBluetooth != null &&
+                    mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+                    mEnable = false;
+                    mBluetooth.onBrEdrDown();
                 }
+            } catch (RemoteException e) {
+                 Slog.e(TAG,"Unable to call onBrEdrDown", e);
+            } finally {
+                mBluetoothLock.readLock().unlock();
             }
         }
     }
 
     /** Internal death rec list */
-    Map<IBinder, ClientDeathRecipient> mBleApps = new HashMap<IBinder, ClientDeathRecipient>();
+    Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<IBinder, ClientDeathRecipient>();
 
     @Override
     public boolean isBleScanAlwaysAvailable() {
@@ -501,17 +498,20 @@
         ContentObserver contentObserver = new ContentObserver(null) {
             @Override
             public void onChange(boolean selfChange) {
-                if (!isBleScanAlwaysAvailable()) {
-                    disableBleScanMode();
-                    clearBleApps();
-                    try {
-                        mBluetoothLock.readLock().lock();
-                        if (mBluetooth != null) mBluetooth.onBrEdrDown();
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "error when disabling bluetooth", e);
-                    } finally {
-                        mBluetoothLock.readLock().unlock();
-                    }
+                if (isBleScanAlwaysAvailable()) {
+                  // Nothing to do
+                  return;
+                }
+                // BLE scan is not available.
+                disableBleScanMode();
+                clearBleApps();
+                try {
+                    mBluetoothLock.readLock().lock();
+                    if (mBluetooth != null) mBluetooth.onBrEdrDown();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error when disabling bluetooth", e);
+                } finally {
+                    mBluetoothLock.readLock().unlock();
                 }
             }
         };
@@ -547,9 +547,6 @@
                     throw new IllegalArgumentException("Wake lock is already dead.");
                 }
                 mBleApps.put(token, deathRec);
-                synchronized (this) {
-                    ++mBleAppCount;
-                }
                 if (DBG) Slog.d(TAG, "Registered for death Notification");
             }
 
@@ -559,31 +556,26 @@
                 // Unregister death recipient as the app goes away.
                 token.unlinkToDeath(r, 0);
                 mBleApps.remove(token);
-                synchronized (this) {
-                    if (mBleAppCount > 0) --mBleAppCount;
-                }
                 if (DBG) Slog.d(TAG, "Unregistered for death Notification");
             }
         }
-        if (DBG) Slog.d(TAG, "Updated BleAppCount" + mBleAppCount);
-        if (mBleAppCount == 0 && mEnable) {
+        int appCount = mBleApps.size();
+        if (DBG) Slog.d(TAG, appCount + " registered Ble Apps");
+        if (appCount == 0 && mEnable) {
             disableBleScanMode();
         }
-        return mBleAppCount;
+        return appCount;
     }
 
     // Clear all apps using BLE scan only mode.
     private void clearBleApps() {
-        synchronized (this) {
-            mBleApps.clear();
-            mBleAppCount = 0;
-        }
+        mBleApps.clear();
     }
 
     /** @hide*/
     public boolean isBleAppPresent() {
-        if (DBG) Slog.d(TAG, "isBleAppPresent() count: " + mBleAppCount);
-        return (mBleAppCount > 0);
+        if (DBG) Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size());
+        return mBleApps.size() > 0;
     }
 
     /**
@@ -1449,12 +1441,12 @@
                     if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) &&
                             (newState == BluetoothAdapter.STATE_OFF) &&
                             (mBluetooth != null) && mEnable) {
-                        recoverBluetoothServiceFromError();
+                        recoverBluetoothServiceFromError(false);
                     }
                     if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
                             (newState == BluetoothAdapter.STATE_BLE_ON) &&
                             (mBluetooth != null) && mEnable) {
-                        recoverBluetoothServiceFromError();
+                        recoverBluetoothServiceFromError(true);
                     }
                     // If we tried to enable BT while BT was in the process of shutting down,
                     // wait for the BT process to fully tear down and then force a restart
@@ -1870,7 +1862,7 @@
                              quietMode ? 1 : 0, 0));
     }
 
-    private void recoverBluetoothServiceFromError() {
+    private void recoverBluetoothServiceFromError(boolean clearBle) {
         Slog.e(TAG,"recoverBluetoothServiceFromError");
         try {
             mBluetoothLock.readLock().lock();
@@ -1908,6 +1900,10 @@
         mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
         mState = BluetoothAdapter.STATE_OFF;
 
+        if (clearBle) {
+          clearBleApps();
+        }
+
         mEnable = false;
 
         if (mErrorRecoveryRetryCounter++ < MAX_ERROR_RESTART_RETRIES) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 2bf5866..d1f07a5 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -60,6 +60,7 @@
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -3969,10 +3970,22 @@
                     + mCurAttribute.packageName + " packageName=" + packageName);
                 return null;
             }
+            // This user ID can never bee spoofed.
             final int imeUserId = UserHandle.getUserId(uid);
+            // This user ID can never bee spoofed.
             final int appUserId = UserHandle.getUserId(mCurClient.uid);
-            return new InputContentUriTokenHandler(contentUri, uid, packageName, imeUserId,
-                    appUserId);
+            // This user ID may be invalid if "contentUri" embedded an invalid user ID.
+            final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
+                    imeUserId);
+            final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
+            // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
+            // actually has the right to grant a read permission for "contentUriWithoutUserId" that
+            // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
+            // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
+            // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
+            // actually allowed to "uid", which is guaranteed to be the IME's one.
+            return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
+                    packageName, contentUriOwnerUserId, appUserId);
         }
     }
 
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2825cf9..6268697 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -39,19 +39,24 @@
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.Sandman;
 import android.util.Slog;
+import android.view.WindowManagerInternal;
+import android.view.WindowManagerPolicy;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import com.android.internal.R;
 import com.android.internal.app.DisableCarModeActivity;
+import com.android.server.power.ShutdownThread;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
+import com.android.server.wm.WindowManagerService;
 
 final class UiModeManagerService extends SystemService {
     private static final String TAG = UiModeManager.class.getSimpleName();
@@ -297,6 +302,30 @@
         }
 
         @Override
+        public void setTheme(String theme) {
+            if (getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_THEME_OVERLAY)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
+                return;
+            }
+            SystemProperties.set("persist.vendor.overlay.theme", theme);
+            mHandler.post(() -> ShutdownThread.reboot(getContext(),
+                    PowerManager.SHUTDOWN_USER_REQUESTED, false));
+        }
+
+        @Override
+        public String getTheme() {
+            if (getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_THEME_OVERLAY)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
+                return null;
+            }
+            return SystemProperties.get("persist.vendor.overlay.theme");
+        }
+
+        @Override
         public int getNightMode() {
             synchronized (mLock) {
                 return mNightMode;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dc4a52d..136e02c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -348,7 +348,7 @@
                 // Before going further -- if this app is not allowed to run in the
                 // background, then at this point we aren't going to let it period.
                 final int allowed = mAm.checkAllowBackgroundLocked(
-                        r.appInfo.uid, r.packageName, callingPid, true);
+                        r.appInfo.uid, r.packageName, callingPid, false);
                 if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                     Slog.w(TAG, "Background start not allowed: service "
                             + service + " to " + r.name.flattenToShortString()
@@ -594,7 +594,8 @@
             for (int i=services.mServicesByName.size()-1; i>=0; i--) {
                 ServiceRecord service = services.mServicesByName.valueAt(i);
                 if (service.appInfo.uid == uid && service.startRequested) {
-                    if (mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+                    if (service.appInfo.isEphemeralApp() ||
+                            mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
                             uid, service.packageName) != AppOpsManager.MODE_ALLOWED) {
                         if (stopping == null) {
                             stopping = new ArrayList<>();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ca60f49..d60f115 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -21,6 +21,7 @@
 import android.app.ContentProviderHolder;
 import android.app.IActivityManager;
 import android.app.WaitResult;
+import android.graphics.PointF;
 import android.os.IDeviceIdentifiersPolicyService;
 import com.android.internal.telephony.TelephonyIntents;
 import com.google.android.collect.Lists;
@@ -290,10 +291,8 @@
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
-import static android.provider.Settings.Global.LENIENT_BACKGROUND_CHECK;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.provider.Settings.System.FONT_SCALE;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -1360,7 +1359,6 @@
     String mOrigDebugApp = null;
     boolean mOrigWaitForDebugger = false;
     boolean mAlwaysFinishActivities = false;
-    boolean mLenientBackgroundCheck = false;
     boolean mForceResizableActivities;
     boolean mSupportsMultiWindow;
     boolean mSupportsFreeformWindowManagement;
@@ -1575,6 +1573,10 @@
     int mThumbnailHeight;
     float mFullscreenThumbnailScale;
 
+    /** The aspect ratio bounds of the PIP. */
+    float mMinPipAspectRatio;
+    float mMaxPipAspectRatio;
+
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
     final UiHandler mUiHandler;
@@ -4145,7 +4147,7 @@
                             if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
                                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                                         "UID idle uid=" + item.uid);
-                                observer.onUidIdle(item.uid);
+                                observer.onUidIdle(item.uid, item.ephemeral);
                             }
                             if (VALIDATE_UID_STATES && i == 0) {
                                 if (validateUid != null) {
@@ -4167,7 +4169,7 @@
                             if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) {
                                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                                         "UID gone uid=" + item.uid);
-                                observer.onUidGone(item.uid);
+                                observer.onUidGone(item.uid, item.ephemeral);
                             }
                             if (reg.lastProcStates != null) {
                                 reg.lastProcStates.delete(item.uid);
@@ -7470,6 +7472,15 @@
 
     @Override
     public void enterPictureInPictureMode(IBinder token) {
+        enterPictureInPictureMode(token, DEFAULT_DISPLAY, null /* aspectRatio */);
+    }
+
+    @Override
+    public void enterPictureInPictureModeWithAspectRatio(IBinder token, float aspectRatio) {
+        enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio);
+    }
+
+    public void enterPictureInPictureMode(IBinder token, int displayId, Float aspectRatio) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized(this) {
@@ -7479,7 +7490,6 @@
                 }
 
                 final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-
                 if (r == null) {
                     throw new IllegalStateException("enterPictureInPictureMode: "
                             + "Can't find activity for token=" + token);
@@ -7490,21 +7500,55 @@
                             + "Picture-In-Picture not supported for r=" + r);
                 }
 
-                // Use the default launch bounds for pinned stack if it doesn't exist yet or use the
-                // current bounds.
-                final ActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID);
-                final Rect bounds = (pinnedStack != null)
-                        ? pinnedStack.mBounds
-                        : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
+                if (aspectRatio != null && !isValidPictureInPictureAspectRatio(aspectRatio)) {
+                    throw new IllegalArgumentException(String.format("enterPictureInPictureMode: "
+                            + "Aspect ratio is too extreme (must be between %f and %f).",
+                                    mMinPipAspectRatio, mMaxPipAspectRatio));
+                }
 
-                mStackSupervisor.moveActivityToPinnedStackLocked(
-                        r, "enterPictureInPictureMode", bounds);
+                final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
+                        ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio)
+                        : mWindowManager.getPictureInPictureDefaultBounds(displayId);
+                mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
+                        bounds);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    @Override
+    public void setPictureInPictureAspectRatio(IBinder token, float aspectRatio) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+                if (r == null || r.getStack().mStackId != PINNED_STACK_ID) {
+                    throw new IllegalStateException("setPictureInPictureAspectRatio: "
+                            + "Requesting activity must be in picture-in-picture mode.");
+                }
+
+                if (!isValidPictureInPictureAspectRatio(aspectRatio)) {
+                    throw new IllegalArgumentException(String.format(
+                            "setPictureInPictureAspectRatio: Aspect ratio is too extreme (must be "
+                                    + "between %f and %f).", mMinPipAspectRatio,
+                            mMaxPipAspectRatio));
+                }
+
+                mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    private boolean isValidPictureInPictureAspectRatio(Float aspectRatio) {
+        if (aspectRatio == null) {
+            return false;
+        }
+        return mMinPipAspectRatio <= aspectRatio && aspectRatio <= mMaxPipAspectRatio;
+    }
+
     // =========================================================
     // PROCESS INFO
     // =========================================================
@@ -7792,38 +7836,43 @@
 
     public int getAppStartMode(int uid, String packageName) {
         synchronized (this) {
-            return checkAllowBackgroundLocked(uid, packageName, -1, true);
+            return checkAllowBackgroundLocked(uid, packageName, -1, false);
         }
     }
 
     int checkAllowBackgroundLocked(int uid, String packageName, int callingPid,
-            boolean allowWhenForeground) {
+            boolean alwaysRestrict) {
         UidRecord uidRec = mActiveUids.get(uid);
-        if (!mLenientBackgroundCheck) {
-            if (!allowWhenForeground || uidRec == null
-                    || uidRec.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+        if (uidRec == null || alwaysRestrict || uidRec.idle) {
+            boolean ephemeral;
+            if (uidRec == null) {
+                ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
+                        UserHandle.getUserId(uid), packageName);
+            } else {
+                ephemeral = uidRec.ephemeral;
+            }
+
+            if (ephemeral) {
+                // We are hard-core about ephemeral apps not running in the background.
+                return ActivityManager.APP_START_MODE_DISABLED;
+            } else {
+                if (callingPid >= 0) {
+                    ProcessRecord proc;
+                    synchronized (mPidsSelfLocked) {
+                        proc = mPidsSelfLocked.get(callingPid);
+                    }
+                    if (proc != null && proc.curProcState
+                            < ActivityManager.PROCESS_STATE_RECEIVER) {
+                        // Whoever is instigating this is in the foreground, so we will allow it
+                        // to go through.
+                        return ActivityManager.APP_START_MODE_NORMAL;
+                    }
+                }
                 if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid,
                         packageName) != AppOpsManager.MODE_ALLOWED) {
                     return ActivityManager.APP_START_MODE_DELAYED;
                 }
             }
-
-        } else if (uidRec == null || uidRec.idle) {
-            if (callingPid >= 0) {
-                ProcessRecord proc;
-                synchronized (mPidsSelfLocked) {
-                    proc = mPidsSelfLocked.get(callingPid);
-                }
-                if (proc != null && proc.curProcState < ActivityManager.PROCESS_STATE_RECEIVER) {
-                    // Whoever is instigating this is in the foreground, so we will allow it
-                    // to go through.
-                    return ActivityManager.APP_START_MODE_NORMAL;
-                }
-            }
-            if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName)
-                    != AppOpsManager.MODE_ALLOWED) {
-                return ActivityManager.APP_START_MODE_DELAYED;
-            }
         }
         return ActivityManager.APP_START_MODE_NORMAL;
     }
@@ -11870,25 +11919,6 @@
     }
 
     @Override
-    public void setLenientBackgroundCheck(boolean enabled) {
-        enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
-                "setLenientBackgroundCheck()");
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            Settings.Global.putInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.LENIENT_BACKGROUND_CHECK, enabled ? 1 : 0);
-
-            synchronized (this) {
-                mLenientBackgroundCheck = enabled;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
     public void setActivityController(IActivityController controller, boolean imAMonkey) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "setActivityController()");
@@ -12987,8 +13017,6 @@
         final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
         final boolean alwaysFinishActivities =
                 Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
-        final boolean lenientBackgroundCheck =
-                Settings.Global.getInt(resolver, LENIENT_BACKGROUND_CHECK, 0) != 0;
         final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
         final boolean forceResizable = Settings.Global.getInt(
                 resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
@@ -13009,7 +13037,6 @@
             mDebugApp = mOrigDebugApp = debugApp;
             mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
             mAlwaysFinishActivities = alwaysFinishActivities;
-            mLenientBackgroundCheck = lenientBackgroundCheck;
             mSupportsLeanbackOnly = supportsLeanbackOnly;
             mForceResizableActivities = forceResizable;
             if (supportsMultiWindow || forceResizable) {
@@ -13036,6 +13063,10 @@
                     com.android.internal.R.dimen.thumbnail_width);
             mThumbnailHeight = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.thumbnail_height);
+            mMinPipAspectRatio = res.getFloat(
+                    com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
+            mMaxPipAspectRatio = res.getFloat(
+                    com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
             mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
                     com.android.internal.R.string.config_appsNotReportingCrashes));
             mUserController.mUserSwitchUiEnabled = !res.getBoolean(
@@ -14782,9 +14813,8 @@
             }
         }
         if (dumpPackage == null) {
-            if (mAlwaysFinishActivities || mLenientBackgroundCheck) {
-                pw.println("  mAlwaysFinishActivities=" + mAlwaysFinishActivities
-                        + " mLenientBackgroundCheck=" + mLenientBackgroundCheck);
+            if (mAlwaysFinishActivities) {
+                pw.println("  mAlwaysFinishActivities=" + mAlwaysFinishActivities);
             }
             if (mController != null) {
                 pw.println("  mController=" + mController
@@ -20709,6 +20739,7 @@
         pendingChange.change = change;
         pendingChange.processState = uidRec != null
                 ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
+        pendingChange.ephemeral = uidRec.ephemeral;
 
         // Directly update the power manager, since we sit on top of it and it is critical
         // it be kept in sync (so wake locks will be held as soon as appropriate).
@@ -21076,8 +21107,11 @@
                 } else {
                     // Keeping this process, update its uid.
                     final UidRecord uidRec = app.uidRecord;
-                    if (uidRec != null && uidRec.curProcState > app.curProcState) {
-                        uidRec.curProcState = app.curProcState;
+                    if (uidRec != null) {
+                        uidRec.ephemeral = app.info.isEphemeralApp();
+                        if (uidRec.curProcState > app.curProcState) {
+                            uidRec.curProcState = app.curProcState;
+                        }
                     }
                 }
 
@@ -21336,6 +21370,63 @@
         }
     }
 
+    @Override
+    public void makePackageIdle(String packageName, int userId) {
+        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: makePackageIdle() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        final int callingPid = Binder.getCallingPid();
+        userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
+                userId, true, ALLOW_FULL_ONLY, "makePackageIdle", null);
+        long callingId = Binder.clearCallingIdentity();
+        synchronized(this) {
+            try {
+                IPackageManager pm = AppGlobals.getPackageManager();
+                int pkgUid = -1;
+                try {
+                    pkgUid = pm.getPackageUid(packageName, MATCH_UNINSTALLED_PACKAGES
+                            | MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM);
+                } catch (RemoteException e) {
+                }
+                if (pkgUid == -1) {
+                    throw new IllegalArgumentException("Unknown package name " + packageName);
+                }
+
+                if (mLocalPowerManager != null) {
+                    mLocalPowerManager.startUidChanges();
+                }
+                final int appId = UserHandle.getAppId(pkgUid);
+                final int N = mActiveUids.size();
+                for (int i=N-1; i>=0; i--) {
+                    final UidRecord uidRec = mActiveUids.valueAt(i);
+                    final long bgTime = uidRec.lastBackgroundTime;
+                    if (bgTime > 0 && !uidRec.idle) {
+                        if (UserHandle.getAppId(uidRec.uid) == appId) {
+                            if (userId == UserHandle.USER_ALL ||
+                                    userId == UserHandle.getUserId(uidRec.uid)) {
+                                uidRec.idle = true;
+                                Slog.w(TAG, "Idling uid " + UserHandle.formatUid(uidRec.uid)
+                                        + " from package " + packageName + " user " + userId);
+                                doStopUidLocked(uidRec.uid, uidRec);
+                            }
+                        }
+                    }
+                }
+            } finally {
+                if (mLocalPowerManager != null) {
+                    mLocalPowerManager.finishUidChanges();
+                }
+                Binder.restoreCallingIdentity(callingId);
+            }
+        }
+    }
+
     final void idleUids() {
         synchronized (this) {
             final int N = mActiveUids.size();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index abeea74..52ad72d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -169,6 +169,8 @@
                     return runKill(pw);
                 case "kill-all":
                     return runKillAll(pw);
+                case "make-idle":
+                    return runMakeIdle(pw);
                 case "monitor":
                     return runMonitor(pw);
                 case "hang":
@@ -205,8 +207,6 @@
                     return runTrackAssociations(pw);
                 case "untrack-associations":
                     return runUntrackAssociations(pw);
-                case "lenient-background-check":
-                    return runLenientBackgroundCheck(pw);
                 case "get-uid-state":
                     return getUidState(pw);
                 case "get-config":
@@ -853,6 +853,22 @@
         return 0;
     }
 
+    int runMakeIdle(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInterface.makePackageIdle(getNextArgRequired(), userId);
+        return 0;
+    }
+
     static final class MyActivityController extends IActivityController.Stub {
         final IActivityManager mInterface;
         final PrintWriter mPw;
@@ -1432,22 +1448,6 @@
         return 0;
     }
 
-    int runLenientBackgroundCheck(PrintWriter pw) throws RemoteException {
-        String arg = getNextArg();
-        if (arg != null) {
-            boolean state = Boolean.valueOf(arg) || "1".equals(arg);
-            mInterface.setLenientBackgroundCheck(state);
-        }
-        synchronized (mInternal) {
-            if (mInternal.mLenientBackgroundCheck) {
-                pw.println("Lenient background check enabled");
-            } else {
-                pw.println("Lenient background check disabled");
-            }
-        }
-        return 0;
-    }
-
     int getUidState(PrintWriter pw) throws RemoteException {
         mInternal.enforceCallingPermission(android.Manifest.permission.DUMP,
                 "getUidState()");
@@ -2478,8 +2478,6 @@
             pw.println("      Enable association tracking.");
             pw.println("  untrack-associations");
             pw.println("      Disable and clear association tracking.");
-            pw.println("  lenient-background-check [<true|false>]");
-            pw.println("      Optionally controls lenient background check mode, returns current mode.");
             pw.println("  get-uid-state <UID>");
             pw.println("      Gets the process state of an app given its <UID>.");
             pw.println("  attach-agent <PROCESS> <FILE>");
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8bb0e1a..282ec50 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2120,6 +2120,14 @@
             final int size = tasks.size();
             if (onTop) {
                 for (int i = 0; i < size; i++) {
+                    final TaskRecord task = tasks.get(i);
+                    if (fromStackId == PINNED_STACK_ID) {
+                        // Update the return-to to reflect where the pinned stack task was moved
+                        // from so that we retain the stack that was previously visible if the
+                        // pinned stack is recreated. See moveActivityToPinnedStackLocked().
+                        task.setTaskToReturnTo(getFocusedStack().getStackId() == HOME_STACK_ID
+                                ? HOME_ACTIVITY_TYPE : APPLICATION_ACTIVITY_TYPE);
+                    }
                     moveTaskToStackLocked(tasks.get(i).taskId,
                             FULLSCREEN_WORKSPACE_STACK_ID, onTop, onTop /*forceFocus*/,
                             "moveTasksToFullscreenStack", ANIMATE, DEFER_RESUME);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 4e69162..8104a43 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -36,7 +36,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -595,8 +594,12 @@
         }
         if (!skip) {
             final int allowed = mService.checkAllowBackgroundLocked(filter.receiverList.uid,
-                    filter.packageName, -1, true);
-            if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
+                    filter.packageName, -1, false);
+            if (false && allowed == ActivityManager.APP_START_MODE_DISABLED) {
+                // XXX should we really not allow this?  It means that while we are
+                // keeping an ephemeral app cached, its registered receivers will stop
+                // receiving broadcasts after it goes idle...  so if it comes back to
+                // the foreground, it won't know what the current state of those broadcasts is.
                 Slog.w(TAG, "Background execution not allowed: receiving "
                         + r.intent
                         + " to " + filter.receiverList.app
@@ -1155,7 +1158,7 @@
             if (!skip) {
                 final int allowed = mService.checkAllowBackgroundLocked(
                         info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, -1,
-                        false);
+                        true);
                 if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                     // We won't allow this receiver to be launched if the app has been
                     // completely disabled from launches, or it was not explicitly sent
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index d24c3a5..d1a15bd 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -29,6 +29,7 @@
     int curProcState;
     int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
     long lastBackgroundTime;
+    boolean ephemeral;
     boolean idle;
     int numProcs;
 
@@ -43,6 +44,7 @@
         int uid;
         int change;
         int processState;
+        boolean ephemeral;
     }
 
     ChangeItem pendingChange;
@@ -64,6 +66,9 @@
         UserHandle.formatUid(sb, uid);
         sb.append(' ');
         sb.append(ProcessList.makeProcStateString(curProcState));
+        if (ephemeral) {
+            sb.append(" ephemeral");
+        }
         if (lastBackgroundTime > 0) {
             sb.append(" bg:");
             TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 0beb227..6d96a10 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -71,6 +71,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.connectivity.tethering.IControlsTethering;
 import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
+import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
 import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.net.BaseNetworkObserver;
 
@@ -1939,7 +1940,8 @@
     private void trackNewTetherableInterface(String iface, int interfaceType) {
         TetherState tetherState;
         tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
-                interfaceType, mNMService, mStatsService, this));
+                interfaceType, mNMService, mStatsService, this,
+                new IPv6TetheringInterfaceServices(iface, mNMService)));
         mTetherStates.put(iface, tetherState);
         tetherState.mStateMachine.start();
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
index c2c1a8c..dec2f77 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -45,7 +45,7 @@
 /**
  * @hide
  */
-class IPv6TetheringInterfaceServices {
+public class IPv6TetheringInterfaceServices {
     private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
     private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
     private static final int RFC7421_IP_PREFIX_LENGTH = 64;
@@ -59,7 +59,7 @@
     private RouterAdvertisementDaemon mRaDaemon;
     private RaParams mLastRaParams;
 
-    IPv6TetheringInterfaceServices(String ifname, INetworkManagementService nms) {
+    public IPv6TetheringInterfaceServices(String ifname, INetworkManagementService nms) {
         mIfName = ifname;
         mNMService = nms;
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 6ca4e27..37221a9 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -94,14 +94,14 @@
 
     public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
                     INetworkManagementService nMService, INetworkStatsService statsService,
-                    IControlsTethering tetherController) {
+                    IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) {
         super(ifaceName, looper);
         mNMService = nMService;
         mStatsService = statsService;
         mTetherController = tetherController;
         mIfaceName = ifaceName;
         mInterfaceType = interfaceType;
-        mIPv6TetherSvc = new IPv6TetheringInterfaceServices(mIfaceName, mNMService);
+        mIPv6TetherSvc = ipv6Svc;
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
 
         mInitialState = new InitialState();
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index df64447..b878099 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -426,7 +426,7 @@
                                             Slog.d(TAG, "Removing jobs for package " + pkgName
                                                     + " in user " + userId);
                                         }
-                                        cancelJobsForUid(pkgUid, true);
+                                        cancelJobsForUid(pkgUid);
                                     }
                                 } catch (RemoteException|IllegalArgumentException e) {
                                     /*
@@ -455,7 +455,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
-                    cancelJobsForUid(uidRemoved, true);
+                    cancelJobsForUid(uidRemoved);
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -509,15 +509,20 @@
             updateUidState(uid, procState);
         }
 
-        @Override public void onUidGone(int uid) throws RemoteException {
+        @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
             updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+            if (disabled) {
+                cancelJobsForUid(uid);
+            }
         }
 
         @Override public void onUidActive(int uid) throws RemoteException {
         }
 
-        @Override public void onUidIdle(int uid) throws RemoteException {
-            cancelJobsForUid(uid, false);
+        @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+            if (disabled) {
+                cancelJobsForUid(uid);
+            }
         }
     };
 
@@ -646,26 +651,15 @@
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
      * @param uid Uid to check against for removal of a job.
-     * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
-     * whose apps are stopped.
+     *
      */
-    public void cancelJobsForUid(int uid, boolean forceAll) {
+    public void cancelJobsForUid(int uid) {
         List<JobStatus> jobsForUid;
         synchronized (mLock) {
             jobsForUid = mJobs.getJobsByUid(uid);
         }
         for (int i=0; i<jobsForUid.size(); i++) {
             JobStatus toRemove = jobsForUid.get(i);
-            if (!forceAll) {
-                String packageName = toRemove.getServiceComponent().getPackageName();
-                try {
-                    if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
-                            != ActivityManager.APP_START_MODE_DISABLED) {
-                        continue;
-                    }
-                } catch (RemoteException e) {
-                }
-            }
             cancelJobImpl(toRemove, null);
         }
     }
@@ -1698,7 +1692,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJobsForUid(uid, true);
+                JobSchedulerService.this.cancelJobsForUid(uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fd99f57..c1506b9 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -689,7 +689,7 @@
             }
         }
 
-        @Override public void onUidGone(int uid) throws RemoteException {
+        @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
             synchronized (mUidRulesFirstLock) {
                 removeUidStateUL(uid);
             }
@@ -698,7 +698,7 @@
         @Override public void onUidActive(int uid) throws RemoteException {
         }
 
-        @Override public void onUidIdle(int uid) throws RemoteException {
+        @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
         }
     };
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index df5d6e7..90ede6f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,23 +17,23 @@
 package com.android.server.notification;
 
 import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.service.notification.NotificationAssistantService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationAssistantService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationAssistantService.REASON_CHANNEL_BANNED;
-import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_CANCEL;
-import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_CANCEL_ALL;
-import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_CLICK;
-import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_ERROR;
-import static android.service.notification.NotificationAssistantService.REASON_GROUP_SUMMARY_CANCELED;
-import static android.service.notification.NotificationAssistantService.REASON_LISTENER_CANCEL;
-import static android.service.notification.NotificationAssistantService.REASON_LISTENER_CANCEL_ALL;
-import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_BANNED;
-import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_CHANGED;
-import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_SUSPENDED;
-import static android.service.notification.NotificationAssistantService.REASON_PROFILE_TURNED_OFF;
-import static android.service.notification.NotificationAssistantService.REASON_SNOOZED;
-import static android.service.notification.NotificationAssistantService.REASON_UNAUTOBUNDLED;
-import static android.service.notification.NotificationAssistantService.REASON_USER_STOPPED;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_DELEGATE_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_DELEGATE_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_DELEGATE_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_DELEGATE_ERROR;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_CHANGED;
+import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
+import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
+import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
+import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
@@ -2722,7 +2722,7 @@
                     + " id=" + id + " notification=" + notification);
         }
         final NotificationChannel channel =  mRankingHelper.getNotificationChannelWithFallback(pkg,
-                callingUid, notification.getNotificationChannel());
+                callingUid, notification.getChannel());
         final StatusBarNotification n = new StatusBarNotification(
                 pkg, opPkg, channel, id, tag, callingUid, callingPid, notification,
                 user, null, System.currentTimeMillis());
@@ -2917,7 +2917,8 @@
                 } else {
                     Slog.e(TAG, "Not posting notification without small icon: " + notification);
                     if (old != null && !old.isCanceled) {
-                        mListeners.notifyRemovedLocked(n);
+                        mListeners.notifyRemovedLocked(n,
+                                NotificationListenerService.REASON_DELEGATE_ERROR);
                         mHandler.post(new Runnable() {
                             @Override
                             public void run() {
@@ -3529,7 +3530,7 @@
         // status bar
         if (r.getNotification().getSmallIcon() != null) {
             r.isCanceled = true;
-            mListeners.notifyRemovedLocked(r.sbn);
+            mListeners.notifyRemovedLocked(r.sbn, reason);
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -4232,7 +4233,7 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(info, oldSbnLightClone, update);
+                            notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
                         }
                     });
                     continue;
@@ -4251,7 +4252,7 @@
         /**
          * asynchronously notify all listeners about a removed notification
          */
-        public void notifyRemovedLocked(StatusBarNotification sbn) {
+        public void notifyRemovedLocked(StatusBarNotification sbn, int reason) {
             // make a copy in case changes are made to the underlying Notification object
             // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
             // notification
@@ -4264,7 +4265,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyRemoved(info, sbnLight, update);
+                        notifyRemoved(info, sbnLight, update, reason);
                     }
                 });
             }
@@ -4328,14 +4329,14 @@
         }
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
-                NotificationRankingUpdate rankingUpdate) {
+                NotificationRankingUpdate rankingUpdate, int reason) {
             if (!info.enabledAndUserMatches(sbn.getUserId())) {
                 return;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                listener.onNotificationRemoved(sbnHolder, rankingUpdate);
+                listener.onNotificationRemoved(sbnHolder, rankingUpdate, reason);
             } catch (RemoteException ex) {
                 Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
             }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 965257c..5eacba6 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -283,7 +283,7 @@
         pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
         pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
         pw.println(prefix + "  mSuppressedVisualEffects= " + mSuppressedVisualEffects);
-        pw.println(prefix + "  notificationChannel= " + notification.getNotificationChannel());
+        pw.println(prefix + "  notificationChannel= " + notification.getChannel());
     }
 
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09b6177..79ef486 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -21257,6 +21257,14 @@
         }
 
         @Override
+        public boolean isPackageEphemeral(int userId, String packageName) {
+            synchronized (mPackages) {
+                PackageParser.Package p = mPackages.get(packageName);
+                return p != null ? p.applicationInfo.isEphemeralApp() : false;
+            }
+        }
+
+        @Override
         public boolean wasPackageEverLaunched(String packageName, int userId) {
             synchronized (mPackages) {
                 return mSettings.wasPackageEverLaunchedLPr(packageName, userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ad31a32..5a0bee1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -467,7 +467,7 @@
         }
 
         @Override
-        public void onUidGone(int uid) throws RemoteException {
+        public void onUidGone(int uid, boolean disabled) throws RemoteException {
             handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
         }
 
@@ -476,7 +476,7 @@
         }
 
         @Override
-        public void onUidIdle(int uid) throws RemoteException {
+        public void onUidIdle(int uid, boolean disabled) throws RemoteException {
         }
     };
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 622eece..05e6f96 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -66,6 +66,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 class AppTokenList extends ArrayList<AppWindowToken> {
 }
@@ -1269,6 +1270,22 @@
     }
 
     @Override
+    void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+        // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
+        // before the non-exiting app tokens. So, we skip the exiting app tokens here.
+        // TODO: Investigate if we need to continue to do this or if we can just process them
+        // in-order.
+        if (mIsExiting && !waitingForReplacement()) {
+            return;
+        }
+        forAllWindowsUnchecked(callback, traverseTopToBottom);
+    }
+
+    void forAllWindowsUnchecked(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+        super.forAllWindows(callback, traverseTopToBottom);
+    }
+
+    @Override
     AppWindowToken asAppWindowToken() {
         // I am an app window token!
         return this;
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 5bfece4..cf5cecda 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -96,8 +96,8 @@
     private final class BoundsAnimator extends ValueAnimator
             implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
         private final AnimateBoundsUser mTarget;
-        private final Rect mFrom;
-        private final Rect mTo;
+        private final Rect mFrom = new Rect();
+        private final Rect mTo = new Rect();
         private final Rect mTmpRect = new Rect();
         private final Rect mTmpTaskBounds = new Rect();
         private final boolean mMoveToFullScreen;
@@ -117,8 +117,8 @@
                 boolean moveToFullScreen, boolean replacement) {
             super();
             mTarget = target;
-            mFrom = from;
-            mTo = to;
+            mFrom.set(from);
+            mTo.set(to);
             mMoveToFullScreen = moveToFullScreen;
             mReplacement = replacement;
             addUpdateListener(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ec4cdf2..6a625f4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -140,6 +140,7 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -1121,8 +1122,7 @@
         for (int i = 0; i < windowCount; i++) {
             final WindowState window = mWindows.get(i);
             if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
-                    && !window.mPermanentlyHidden && !window.mAnimatingExit
-                    && !window.mRemoveOnExit) {
+                    && !window.mPermanentlyHidden && !window.mWindowRemovalAllowed) {
                 return false;
             }
         }
@@ -3243,6 +3243,42 @@
         }
 
         @Override
+        void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+            if (traverseTopToBottom) {
+                super.forAllWindows(callback, traverseTopToBottom);
+                forAllExitingAppTokenWindows(callback, traverseTopToBottom);
+            } else {
+                forAllExitingAppTokenWindows(callback, traverseTopToBottom);
+                super.forAllWindows(callback, traverseTopToBottom);
+            }
+        }
+
+        private void forAllExitingAppTokenWindows(Consumer<WindowState> callback,
+                boolean traverseTopToBottom) {
+            // For legacy reasons we process the TaskStack.mExitingAppTokens first here before the
+            // app tokens.
+            // TODO: Investigate if we need to continue to do this or if we can just process them
+            // in-order.
+            if (traverseTopToBottom) {
+                for (int i = mChildren.size() - 1; i >= 0; --i) {
+                    final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                    for (int j = appTokens.size() - 1; j >= 0; --j) {
+                        appTokens.get(j).forAllWindowsUnchecked(callback, traverseTopToBottom);
+                    }
+                }
+            } else {
+                final int count = mChildren.size();
+                for (int i = 0; i < count; ++i) {
+                    final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                    final int appTokensCount = appTokens.size();
+                    for (int j = 0; j < appTokensCount; j++) {
+                        appTokens.get(j).forAllWindowsUnchecked(callback, traverseTopToBottom);
+                    }
+                }
+            }
+        }
+
+        @Override
         int getOrientation() {
             if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
                     || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 1ccf722..c711b39 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -26,6 +26,7 @@
 import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
@@ -43,6 +44,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.policy.PipMotionHelper;
 import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.server.UiThread;
 
 import java.io.PrintWriter;
 
@@ -55,7 +57,7 @@
 
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
-    private final Handler mHandler = new Handler();
+    private final Handler mHandler = UiThread.getHandler();
 
     private IPinnedStackListener mPinnedStackListener;
     private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
@@ -67,6 +69,7 @@
 
     // States that affect how the PIP can be manipulated
     private boolean mInInteractiveMode;
+    private boolean mIsMinimized;
     private boolean mIsImeShowing;
     private int mImeHeight;
     private ValueAnimator mBoundsAnimator = null;
@@ -101,6 +104,13 @@
         }
 
         @Override
+        public void setIsMinimized(final boolean isMinimized) {
+            mHandler.post(() -> {
+                mIsMinimized = isMinimized;
+            });
+        }
+
+        @Override
         public void setSnapToEdge(final boolean snapToEdge) {
             mHandler.post(() -> {
                 mSnapAlgorithm.setSnapToEdge(snapToEdge);
@@ -167,6 +177,25 @@
     }
 
     /**
+     * Returns the current bounds (or the default bounds if there are no current bounds) with the
+     * specified aspect ratio.
+     */
+    Rect getAspectRatioBounds(Rect stackBounds, float aspectRatio) {
+        // Save the snap fraction, calculate the aspect ratio based on the current bounds
+        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
+                getMovementBounds(stackBounds));
+        final float radius = PointF.length(stackBounds.width(), stackBounds.height());
+        final int height = (int) Math.round(Math.sqrt((radius * radius) /
+                (aspectRatio * aspectRatio + 1)));
+        final int width = Math.round(height * aspectRatio);
+        final int left = (int) (stackBounds.centerX() - width / 2f);
+        final int top = (int) (stackBounds.centerY() - height / 2f);
+        stackBounds.set(left, top, left + width, top + height);
+        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
+        return stackBounds;
+    }
+
+    /**
      * @return the default bounds to show the PIP when there is no active PIP.
      */
     Rect getDefaultBounds() {
@@ -314,5 +343,6 @@
         pw.println();
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
         pw.println(prefix + "  mInInteractiveMode=" + mInInteractiveMode);
+        pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a0270c6..203ba72 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -132,6 +132,7 @@
     // perfectly fit the region it would have been cropped to. We may also avoid certain logic we
     // would otherwise apply while resizing, while resizing in the bounds animating mode.
     private boolean mBoundsAnimating = false;
+    private Rect mBoundsAnimationTarget = new Rect();
 
     // Temporary storage for the new bounds that should be used after the configuration change.
     // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration().
@@ -329,6 +330,30 @@
         mDisplayContent.getLogicalDisplayRect(out);
     }
 
+    /**
+     * Sets the bounds animation target bounds.  This can't currently be done in onAnimationStart()
+     * since that is started on the UiThread.
+     */
+    void setAnimatingBounds(Rect bounds) {
+        if (bounds != null) {
+            mBoundsAnimationTarget.set(bounds);
+        } else {
+            mBoundsAnimationTarget.setEmpty();
+        }
+    }
+
+    /**
+     * @return the bounds that the task stack is currently being animated towards, or the current
+     *         stack bounds if there is no animation in progress.
+     */
+    void getAnimatingBounds(Rect outBounds) {
+        if (!mBoundsAnimationTarget.isEmpty()) {
+            outBounds.set(mBoundsAnimationTarget);
+            return;
+        }
+        getBounds(outBounds);
+    }
+
     /** Bounds of the stack with other system factors taken into consideration. */
     @Override
     public void getDimBounds(Rect out) {
@@ -1391,6 +1416,7 @@
     public void onAnimationEnd() {
         synchronized (mService.mWindowMap) {
             mBoundsAnimating = false;
+            mBoundsAnimationTarget.setEmpty();
             mService.requestTraversal();
         }
         if (mStackId == PINNED_STACK_ID) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e30ebcb..62ad217 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -22,6 +22,7 @@
 
 import java.util.Comparator;
 import java.util.LinkedList;
+import java.util.function.Consumer;
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -494,6 +495,19 @@
         return addIndex;
     }
 
+    void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+        if (traverseTopToBottom) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                mChildren.get(i).forAllWindows(callback, traverseTopToBottom);
+            }
+        } else {
+            final int count = mChildren.size();
+            for (int i = 0; i < count; i++) {
+                mChildren.get(i).forAllWindows(callback, traverseTopToBottom);
+            }
+        }
+    }
+
     /**
      * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
      * the input container in terms of z-order.
@@ -562,8 +576,7 @@
     void dumpChildrenNames(StringBuilder out, String prefix) {
         final String childPrefix = prefix + " ";
         out.append(getName() + "\n");
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
             out.append(childPrefix + "#" + i + " ");
             wc.dumpChildrenNames(out, childPrefix);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e1c8c72..5c9dc10 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -172,7 +172,6 @@
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -3402,7 +3401,7 @@
     public Rect getPictureInPictureDefaultBounds(int displayId) {
         synchronized (mWindowMap) {
             if (!mSupportsPictureInPicture) {
-                return new Rect();
+                return null;
             }
 
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
@@ -3414,7 +3413,7 @@
     public Rect getPictureInPictureMovementBounds(int displayId) {
         synchronized (mWindowMap) {
             if (!mSupportsPictureInPicture) {
-                return new Rect();
+                return null;
             }
 
             final Rect stackBounds = new Rect();
@@ -3428,6 +3427,47 @@
         }
     }
 
+    public void setPictureInPictureAspectRatio(float aspectRatio) {
+        synchronized (mWindowMap) {
+            if (!mSupportsPictureInPicture) {
+                return;
+            }
+
+            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+            if (stack == null) {
+                return;
+            }
+
+            animateResizePinnedStack(getPictureInPictureBounds(
+                    stack.getDisplayContent().getDisplayId(), aspectRatio), -1);
+        }
+    }
+
+    public Rect getPictureInPictureBounds(int displayId, float aspectRatio) {
+        synchronized (mWindowMap) {
+            if (!mSupportsPictureInPicture) {
+                return null;
+            }
+
+            final Rect stackBounds;
+            final DisplayContent displayContent;
+            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+            if (stack != null) {
+                // If the stack exists, then use its final bounds to calculate the new aspect ratio
+                // bounds.
+                displayContent = stack.getDisplayContent();
+                stackBounds = new Rect();
+                stack.getAnimatingBounds(stackBounds);
+            } else {
+                // Otherwise, just calculate the aspect ratio bounds from the default bounds
+                displayContent = mRoot.getDisplayContent(displayId);
+                stackBounds = displayContent.getPinnedStackController().getDefaultBounds();
+            }
+            return displayContent.getPinnedStackController().getAspectRatioBounds(stackBounds,
+                    aspectRatio);
+        }
+    }
+
     /**
      * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
      * specified stackId.
@@ -8212,6 +8252,8 @@
                     StringBuilder output = new StringBuilder();
                     mRoot.dumpChildrenNames(output, " ");
                     pw.println(output.toString());
+                    pw.println(" ");
+                    mRoot.forAllWindows(pw::println, true /* traverseTopToBottom */);
                 }
                 return;
             } else {
@@ -8465,6 +8507,7 @@
             }
             final Rect originalBounds = new Rect();
             stack.getBounds(originalBounds);
+            stack.setAnimatingBounds(bounds);
             UiThread.getHandler().post(new Runnable() {
                 @Override
                 public void run() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 972c359..1a56518 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -59,6 +59,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.LinkedList;
+import java.util.function.Consumer;
 
 import static android.app.ActivityManager.StackId;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
@@ -488,7 +489,7 @@
      * or some other higher level component said so (e.g. activity manager).
      * TODO: We should either have different booleans for the removal reason or use a bit-field.
      */
-    private boolean mWindowRemovalAllowed;
+    boolean mWindowRemovalAllowed;
 
     /**
      * Temp for keeping track of windows that have been removed when
@@ -3876,6 +3877,78 @@
         return index;
     }
 
+    @Override
+    void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+        if (mChildren.isEmpty()) {
+            // The window has no children so we just return it.
+            callback.accept(this);
+            return;
+        }
+
+        if (traverseTopToBottom) {
+            forAllWindowTopToBottom(callback);
+        } else {
+            forAllWindowBottomToTop(callback);
+        }
+    }
+
+    private void forAllWindowBottomToTop(Consumer<WindowState> callback) {
+        // We want to consumer the negative sublayer children first because they need to appear
+        // below the parent, then this window (the parent), and then the positive sublayer children
+        // because they need to appear above the parent.
+        int i = 0;
+        final int count = mChildren.size();
+        WindowState child = mChildren.get(i);
+
+        while (i < count && child.mSubLayer < 0) {
+            callback.accept(child);
+            i++;
+            if (i >= count) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        callback.accept(this);
+
+        while (i < count) {
+            callback.accept(child);
+            i++;
+            if (i >= count) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+    }
+
+    private void forAllWindowTopToBottom(Consumer<WindowState> callback) {
+        // We want to consumer the positive sublayer children first because they need to appear
+        // above the parent, then this window (the parent), and then the negative sublayer children
+        // because they need to appear above the parent.
+        int i = mChildren.size() - 1;
+        WindowState child = mChildren.get(i);
+
+        while (i >= 0 && child.mSubLayer >= 0) {
+            callback.accept(child);
+            --i;
+            if (i < 0) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        callback.accept(this);
+
+        while (i >= 0) {
+            callback.accept(child);
+            --i;
+            if (i < 0) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+    }
+
     boolean isWindowAnimationSet() {
         if (mWinAnimator.isWindowAnimationSet()) {
             return true;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 93f1610..402bcfb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -196,9 +196,8 @@
      */
     protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
             WindowState existingWindow) {
-        // By default the first window isn't greater than the second to preserve existing logic of
-        // how new windows are added to the token
-        return false;
+        // New window is considered greater if it has a higher or equal base layer.
+        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
     }
 
     void addWindow(final WindowState win) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index d933cb3..772b2a2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -33,12 +33,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
- * Tests for the {@link WindowState} class.
+ * Tests for the {@link AppWindowToken} class.
  *
  * Build/Install/Run:
  *  bit FrameworksServicesTests:com.android.server.wm.AppWindowTokenTests
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
new file mode 100644
index 0000000..0533efc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -0,0 +1,159 @@
+/*
+ * 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.wm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.res.Configuration.EMPTY;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for the {@link DisplayContent} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.DisplayContentTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DisplayContentTests {
+
+    private static WindowManagerService sWm = null;
+    private final IWindow mIWindow = new TestIWindow();
+    private final Session mMockSession = mock(Session.class);
+    private Display mDisplay;
+    private int mNextStackId = FIRST_DYNAMIC_STACK_ID;
+    private int mNextTaskId = 0;
+
+    @Before
+    public void setUp() throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        sWm = TestWindowManagerPolicy.getWindowManagerService(context);
+        mDisplay = context.getDisplay();
+    }
+
+    @Test
+    public void testForAllWindows() throws Exception {
+        final DisplayContent dc = new DisplayContent(mDisplay, sWm, null, null);
+        final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, dc, "wallpaper");
+        final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, dc, "ime");
+        final WindowState imeDialogWindow = createWindow(null, TYPE_INPUT_METHOD_DIALOG, dc,
+                "ime dialog");
+        final WindowState statusBarWindow = createWindow(null, TYPE_STATUS_BAR, dc, "status bar");
+        final WindowState navBarWindow = createWindow(null, TYPE_NAVIGATION_BAR,
+                statusBarWindow.mToken, "nav bar");
+        final WindowState appWindow = createWindow(null, TYPE_BASE_APPLICATION, dc, "app");
+        final WindowState negChildAppWindow = createWindow(appWindow, TYPE_APPLICATION_MEDIA,
+                appWindow.mToken, "negative app child");
+        final WindowState posChildAppWindow = createWindow(appWindow,
+                TYPE_APPLICATION_ATTACHED_DIALOG, appWindow.mToken, "positive app child");
+        final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, dc,
+                "exiting app");
+        final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken;
+        exitingAppToken.mIsExiting = true;
+        exitingAppToken.mTask.mStack.mExitingAppTokens.add(exitingAppToken);
+
+        final ArrayList<WindowState> windows = new ArrayList();
+
+        // Test forward traversal.
+        dc.forAllWindows(windows::add, false /* traverseTopToBottom */);
+
+        assertEquals(wallpaperWindow, windows.get(0));
+        assertEquals(exitingAppWindow, windows.get(1));
+        assertEquals(negChildAppWindow, windows.get(2));
+        assertEquals(appWindow, windows.get(3));
+        assertEquals(posChildAppWindow, windows.get(4));
+        assertEquals(statusBarWindow, windows.get(5));
+        assertEquals(navBarWindow, windows.get(6));
+        assertEquals(imeWindow, windows.get(7));
+        assertEquals(imeDialogWindow, windows.get(8));
+
+        // Test backward traversal.
+        windows.clear();
+        dc.forAllWindows(windows::add, true /* traverseTopToBottom */);
+
+        assertEquals(wallpaperWindow, windows.get(8));
+        assertEquals(exitingAppWindow, windows.get(7));
+        assertEquals(negChildAppWindow, windows.get(6));
+        assertEquals(appWindow, windows.get(5));
+        assertEquals(posChildAppWindow, windows.get(4));
+        assertEquals(statusBarWindow, windows.get(3));
+        assertEquals(navBarWindow, windows.get(2));
+        assertEquals(imeWindow, windows.get(1));
+        assertEquals(imeDialogWindow, windows.get(0));
+    }
+
+    private WindowToken createWindowToken(DisplayContent dc, int type) {
+        if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
+            return new WindowToken(sWm, mock(IBinder.class), type, false, dc);
+        }
+
+        final int stackId = mNextStackId++;
+        dc.addStackToDisplay(stackId, true);
+        final TaskStack stack = sWm.mStackIdToStack.get(stackId);
+        final Task task = new Task(mNextTaskId++, stack, 0, sWm, null, EMPTY, false);
+        stack.addTask(task, true);
+        final AppWindowToken token = new AppWindowToken(sWm, null, false, dc);
+        task.addAppToken(0, token, 0, false);
+        return token;
+    }
+
+    private WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
+        final WindowToken token = createWindowToken(dc, type);
+        return createWindow(parent, type, token, name);
+    }
+
+    private WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
+        attrs.setTitle(name);
+
+        final WindowState w = new WindowState(sWm, mMockSession, mIWindow, token, parent, OP_NONE,
+                0, attrs, 0, 0);
+        // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
+        // adding it to the token...
+        token.addWindow(w);
+        return w;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
index 5326a19..d12d672a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -39,7 +39,7 @@
 import static org.mockito.Mockito.mock;
 
 /**
- * Tests for the {@link WindowState} class.
+ * Tests for the {@link WindowToken} class.
  *
  * Build/Install/Run:
  *  bit FrameworksServicesTests:com.android.server.wm.WindowTokenTests
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 62625bdf..c99e22a 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -843,6 +843,7 @@
     private String mParentId = null;
     private int mState;
     private List<String> mCannedTextResponses = null;
+    private String mCallingPackage;
     private String mRemainingPostDialSequence;
     private VideoCallImpl mVideoCallImpl;
     private Details mDetails;
@@ -1330,19 +1331,22 @@
     }
 
     /** {@hide} */
-    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) {
+    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage) {
         mPhone = phone;
         mTelecomCallId = telecomCallId;
         mInCallAdapter = inCallAdapter;
         mState = STATE_NEW;
+        mCallingPackage = callingPackage;
     }
 
     /** {@hide} */
-    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state) {
+    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state,
+            String callingPackage) {
         mPhone = phone;
         mTelecomCallId = telecomCallId;
         mInCallAdapter = inCallAdapter;
         mState = state;
+        mCallingPackage = callingPackage;
     }
 
     /** {@hide} */
@@ -1352,6 +1356,7 @@
 
     /** {@hide} */
     final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) {
+
         // First, we update the internal state as far as possible before firing any updates.
         Details details = Details.createFromParcelableCall(parcelableCall);
         boolean detailsChanged = !Objects.equals(mDetails, details);
@@ -1367,7 +1372,7 @@
             cannedTextResponsesChanged = true;
         }
 
-        VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl();
+        VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage);
         boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() &&
                 !Objects.equals(mVideoCallImpl, newVideoCallImpl);
         if (videoCallChanged) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8f9c758..2b9a508 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -785,7 +786,7 @@
         public static final int SESSION_EVENT_TX_STOP = 4;
 
         /**
-         * A camera failure has occurred for the selected camera.  The {@link InCallService} can use
+         * A camera failure has occurred for the selected camera.  The {@link VideoProvider} can use
          * this as a cue to inform the user the camera is not available.
          * @see #handleCallSessionEvent(int)
          */
@@ -793,13 +794,21 @@
 
         /**
          * Issued after {@link #SESSION_EVENT_CAMERA_FAILURE} when the camera is once again ready
-         * for operation.  The {@link InCallService} can use this as a cue to inform the user that
+         * for operation.  The {@link VideoProvider} can use this as a cue to inform the user that
          * the camera has become available again.
          * @see #handleCallSessionEvent(int)
          */
         public static final int SESSION_EVENT_CAMERA_READY = 6;
 
         /**
+         * Session event raised by Telecom when
+         * {@link android.telecom.InCallService.VideoCall#setCamera(String)} is called and the
+         * caller does not have the necessary {@link android.Manifest.permission#CAMERA} permission.
+         * @see #handleCallSessionEvent(int)
+         */
+        public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7;
+
+        /**
          * Session modify request was successful.
          * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile)
          */
@@ -848,6 +857,8 @@
         private static final String SESSION_EVENT_TX_STOP_STR = "TX_STOP";
         private static final String SESSION_EVENT_CAMERA_FAILURE_STR = "CAMERA_FAIL";
         private static final String SESSION_EVENT_CAMERA_READY_STR = "CAMERA_READY";
+        private static final String SESSION_EVENT_CAMERA_PERMISSION_ERROR_STR =
+                "CAMERA_PERMISSION_ERROR";
         private static final String SESSION_EVENT_UNKNOWN_STR = "UNKNOWN";
 
         private VideoProvider.VideoProviderHandler mMessageHandler;
@@ -906,8 +917,17 @@
                         break;
                     }
                     case MSG_SET_CAMERA:
-                        onSetCamera((String) msg.obj);
-                        break;
+                    {
+                        SomeArgs args = (SomeArgs) msg.obj;
+                        try {
+                            onSetCamera((String) args.arg1);
+                            onSetCamera((String) args.arg1, (String) args.arg2, args.argi1,
+                                    args.argi2);
+                        } finally {
+                            args.recycle();
+                        }
+                    }
+                    break;
                     case MSG_SET_PREVIEW_SURFACE:
                         onSetPreviewSurface((Surface) msg.obj);
                         break;
@@ -962,8 +982,19 @@
                         MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
             }
 
-            public void setCamera(String cameraId) {
-                mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget();
+            public void setCamera(String cameraId, String callingPackageName) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = cameraId;
+                // Propagate the calling package; originally determined in
+                // android.telecom.InCallService.VideoCall#setCamera(String) from the calling
+                // process.
+                args.arg2 = callingPackageName;
+                // Pass along the uid and pid of the calling app; this gets lost when we put the
+                // message onto the handler.  These are required for Telecom to perform a permission
+                // check to see if the calling app is able to use the camera.
+                args.argi1 = Binder.getCallingUid();
+                args.argi2 = Binder.getCallingPid();
+                mMessageHandler.obtainMessage(MSG_SET_CAMERA, args).sendToTarget();
             }
 
             public void setPreviewSurface(Surface surface) {
@@ -1048,6 +1079,29 @@
         public abstract void onSetCamera(String cameraId);
 
         /**
+         * Sets the camera to be used for the outgoing video.
+         * <p>
+         * The {@link VideoProvider} should respond by communicating the capabilities of the chosen
+         * camera via
+         * {@link VideoProvider#changeCameraCapabilities(VideoProfile.CameraCapabilities)}.
+         * <p>
+         * This prototype is used internally to ensure that the calling package name, UID and PID
+         * are sent to Telecom so that can perform a camera permission check on the caller.
+         * <p>
+         * Sent from the {@link InCallService} via
+         * {@link InCallService.VideoCall#setCamera(String)}.
+         *
+         * @param cameraId The id of the camera (use ids as reported by
+         * {@link CameraManager#getCameraIdList()}).
+         * @param callingPackageName The AppOpps package name of the caller.
+         * @param callingUid The UID of the caller.
+         * @param callingPid The PID of the caller.
+         * @hide
+         */
+        public void onSetCamera(String cameraId, String callingPackageName, int callingUid,
+                int callingPid) {}
+
+        /**
          * Sets the surface to be used for displaying a preview of what the user's camera is
          * currently capturing.  When video transmission is enabled, this is the video signal which
          * is sent to the remote device.
@@ -1233,7 +1287,8 @@
          *      {@link VideoProvider#SESSION_EVENT_TX_START},
          *      {@link VideoProvider#SESSION_EVENT_TX_STOP},
          *      {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
-         *      {@link VideoProvider#SESSION_EVENT_CAMERA_READY}.
+         *      {@link VideoProvider#SESSION_EVENT_CAMERA_READY},
+         *      {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE}.
          */
         public void handleCallSessionEvent(int event) {
             if (mVideoCallbacks != null) {
@@ -1382,6 +1437,8 @@
                     return SESSION_EVENT_TX_START_STR;
                 case SESSION_EVENT_TX_STOP:
                     return SESSION_EVENT_TX_STOP_STR;
+                case SESSION_EVENT_CAMERA_PERMISSION_ERROR:
+                    return SESSION_EVENT_CAMERA_PERMISSION_ERROR_STR;
                 default:
                     return SESSION_EVENT_UNKNOWN_STR + " " + event;
             }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 69de89d..5d68aae 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -87,7 +87,8 @@
 
             switch (msg.what) {
                 case MSG_SET_IN_CALL_ADAPTER:
-                    mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
+                    String callingPackage = getApplicationContext().getOpPackageName();
+                    mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage);
                     mPhone.addListener(mPhoneListener);
                     onPhoneCreated(mPhone);
                     break;
@@ -664,7 +665,8 @@
              *      {@link Connection.VideoProvider#SESSION_EVENT_TX_START},
              *      {@link Connection.VideoProvider#SESSION_EVENT_TX_STOP},
              *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
-             *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY}.
+             *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY},
+             *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_PERMISSION_ERROR}.
              */
             public abstract void onCallSessionEvent(int event);
 
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 4a6fd7c..1900cb9 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -182,10 +182,10 @@
 
      * @return The video call.
      */
-    public VideoCallImpl getVideoCallImpl() {
+    public VideoCallImpl getVideoCallImpl(String callingPackageName) {
         if (mVideoCall == null && mVideoCallProvider != null) {
             try {
-                mVideoCall = new VideoCallImpl(mVideoCallProvider);
+                mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName);
             } catch (RemoteException ignored) {
                 // Ignore RemoteException.
             }
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index a4ef560..30ec5b3 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -125,13 +125,16 @@
 
     private boolean mCanAddCall = true;
 
-    Phone(InCallAdapter adapter) {
+    private final String mCallingPackage;
+
+    Phone(InCallAdapter adapter, String callingPackage) {
         mInCallAdapter = adapter;
+        mCallingPackage = callingPackage;
     }
 
     final void internalAddCall(ParcelableCall parcelableCall) {
         Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
-                parcelableCall.getState());
+                parcelableCall.getState(), mCallingPackage);
         mCallByTelecomCallId.put(parcelableCall.getId(), call);
         mCalls.add(call);
         checkCallTree(parcelableCall);
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 473e394..0457d63 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -114,7 +114,10 @@
     public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;
 
     /**
-     * Flag indicating that this {@code PhoneAccount} is capable of placing video calls.
+     * Flag indicating that this {@code PhoneAccount} is currently able to place video calls.
+     * <p>
+     * See also {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING} which indicates whether the
+     * {@code PhoneAccount} supports placing video calls.
      * <p>
      * See {@link #getCapabilities}
      */
@@ -179,6 +182,23 @@
     public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 0x200;
 
     /**
+     * Flag indicating that this {@link PhoneAccount} supports video calling.
+     * This is not an indication that the {@link PhoneAccount} is currently able to make a video
+     * call, but rather that it has the ability to make video calls (but not necessarily at this
+     * time).
+     * <p>
+     * Whether a {@link PhoneAccount} can make a video call is ultimately controlled by
+     * {@link #CAPABILITY_VIDEO_CALLING}, which indicates whether the {@link PhoneAccount} is
+     * currently capable of making a video call.  Consider a case where, for example, a
+     * {@link PhoneAccount} supports making video calls (e.g.
+     * {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING}), but a current lack of network connectivity
+     * prevents video calls from being made (e.g. {@link #CAPABILITY_VIDEO_CALLING}).
+     * <p>
+     * See {@link #getCapabilities}
+     */
+    public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 0x400;
+
+    /**
      * URI scheme for telephone number URIs.
      */
     public static final String SCHEME_TEL = "tel";
@@ -762,6 +782,9 @@
      */
     private String capabilitiesToString(int capabilities) {
         StringBuilder sb = new StringBuilder();
+        if (hasCapabilities(CAPABILITY_SUPPORTS_VIDEO_CALLING)) {
+            sb.append("SuppVideo ");
+        }
         if (hasCapabilities(CAPABILITY_VIDEO_CALLING)) {
             sb.append("Video ");
         }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 0e4f53e..77e0e54 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -408,6 +408,8 @@
 
         private final IVideoProvider mVideoProviderBinder;
 
+        private final String mCallingPackage;
+
         /**
          * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
          * load factor before resizing, 1 means we only expect a single thread to
@@ -416,8 +418,9 @@
         private final Set<Callback> mCallbacks = Collections.newSetFromMap(
                 new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
 
-        VideoProvider(IVideoProvider videoProviderBinder) {
+        VideoProvider(IVideoProvider videoProviderBinder, String callingPackage) {
             mVideoProviderBinder = videoProviderBinder;
+            mCallingPackage = callingPackage;
             try {
                 mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
             } catch (RemoteException e) {
@@ -452,7 +455,7 @@
          */
         public void setCamera(String cameraId) {
             try {
-                mVideoProviderBinder.setCamera(cameraId);
+                mVideoProviderBinder.setCamera(cameraId, mCallingPackage);
             } catch (RemoteException e) {
             }
         }
@@ -628,7 +631,7 @@
      * @hide
      */
     RemoteConnection(String callId, IConnectionService connectionService,
-            ParcelableConnection connection) {
+            ParcelableConnection connection, String callingPackage) {
         mConnectionId = callId;
         mConnectionService = connectionService;
         mConnected = true;
@@ -640,7 +643,7 @@
         mVideoState = connection.getVideoState();
         IVideoProvider videoProvider = connection.getVideoProvider();
         if (videoProvider != null) {
-            mVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
+            mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage);
         } else {
             mVideoProvider = null;
         }
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 0a8470a..d8a226a 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -283,9 +283,13 @@
         @Override
         public void setVideoProvider(String callId, IVideoProvider videoProvider,
                 Session.Info sessionInfo) {
+
+            String callingPackage = mOurConnectionServiceImpl.getApplicationContext()
+                    .getOpPackageName();
             RemoteConnection.VideoProvider remoteVideoProvider = null;
             if (videoProvider != null) {
-                remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
+                remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider,
+                        callingPackage);
             }
             findConnectionForAction(callId, "setVideoProvider")
                     .setVideoProvider(remoteVideoProvider);
@@ -351,8 +355,10 @@
         @Override
         public void addExistingConnection(String callId, ParcelableConnection connection,
                 Session.Info sessionInfo) {
+            String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
+                    getOpPackageName();
             RemoteConnection remoteConnection = new RemoteConnection(callId,
-                    mOutgoingConnectionServiceRpc, connection);
+                    mOutgoingConnectionServiceRpc, connection, callingPackage);
             mConnectionById.put(callId, remoteConnection);
             remoteConnection.registerCallback(new RemoteConnection.Callback() {
                 @Override
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index e54abee..d8ede5c 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -43,6 +43,7 @@
     private VideoCall.Callback mCallback;
     private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN;
     private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
+    private final String mCallingPackageName;
 
     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
@@ -197,12 +198,13 @@
 
     private Handler mHandler;
 
-    VideoCallImpl(IVideoProvider videoProvider) throws RemoteException {
+    VideoCallImpl(IVideoProvider videoProvider, String callingPackageName) throws RemoteException {
         mVideoProvider = videoProvider;
         mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
 
         mBinder = new VideoCallListenerBinder();
         mVideoProvider.addVideoCallback(mBinder);
+        mCallingPackageName = callingPackageName;
     }
 
     public void destroy() {
@@ -240,7 +242,8 @@
     /** {@inheritDoc} */
     public void setCamera(String cameraId) {
         try {
-            mVideoProvider.setCamera(cameraId);
+            Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName);
+            mVideoProvider.setCamera(cameraId, mCallingPackageName);
         } catch (RemoteException e) {
         }
     }
diff --git a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
index 68e5fd4..a109e90 100644
--- a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
+++ b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
@@ -30,7 +30,7 @@
 
     void removeVideoCallback(IBinder videoCallbackBinder);
 
-    void setCamera(String cameraId);
+    void setCamera(String cameraId, in String mCallingPackageName);
 
     void setPreviewSurface(in Surface surface);
 
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 3c0a8d6..434caad 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -168,20 +168,34 @@
     }
 
     /**
-     * @hide
+     * Get reference signal received quality
      */
     public int getRsrq() {
         return mRsrq;
     }
 
     /**
-     * @hide
+     * Get reference signal signal-to-noise ratio
      */
     public int getRssnr() {
         return mRssnr;
     }
 
     /**
+     * Get reference signal received power
+     */
+    public int getRsrp() {
+        return mRsrp;
+    }
+
+    /**
+     * Get channel quality indicator
+     */
+    public int getCqi() {
+        return mCqi;
+    }
+
+    /**
      * Get signal strength as dBm
      */
     @Override
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index bb2b447..32f487b 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -233,7 +233,7 @@
      * @hide
      */
     /** @hide */
-    protected int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    protected Integer mSubId;
 
     private final Handler mHandler;
 
@@ -242,7 +242,7 @@
      * This class requires Looper.myLooper() not return null.
      */
     public PhoneStateListener() {
-        this(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, Looper.myLooper());
+        this(null, Looper.myLooper());
     }
 
     /**
@@ -251,7 +251,7 @@
      * @hide
      */
     public PhoneStateListener(Looper looper) {
-        this(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, looper);
+        this(null, looper);
     }
 
     /**
@@ -260,7 +260,7 @@
      * own non-null Looper use PhoneStateListener(int subId, Looper looper) below.
      * @hide
      */
-    public PhoneStateListener(int subId) {
+    public PhoneStateListener(Integer subId) {
         this(subId, Looper.myLooper());
     }
 
@@ -269,7 +269,7 @@
      * and non-null Looper.
      * @hide
      */
-    public PhoneStateListener(int subId, Looper looper) {
+    public PhoneStateListener(Integer subId, Looper looper) {
         if (DBG) log("ctor: subId=" + subId + " looper=" + looper);
         mSubId = subId;
         mHandler = new Handler(looper) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index dcf2f06..20dd012 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -263,6 +263,22 @@
       return new TelephonyManager(mContext, subId);
     }
 
+    /**
+     * Create a new TelephonyManager object pinned to the subscription ID associated with the given
+     * phone account.
+     *
+     * @return a TelephonyManager that uses the given phone account for all calls, or {@code null}
+     * if the phone account does not correspond to a valid subscription ID.
+     */
+    @Nullable
+    public TelephonyManager createForPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+        int subId = getSubIdForPhoneAccountHandle(phoneAccountHandle);
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            return null;
+        }
+        return new TelephonyManager(mContext, subId);
+    }
+
     /** {@hide} */
     public boolean isMultiSimEnabled() {
         return (multiSimConfig.equals("dsds") || multiSimConfig.equals("dsda") ||
@@ -3019,6 +3035,12 @@
         if (mContext == null) return;
         try {
             Boolean notifyNow = (getITelephony() != null);
+            // If the listener has not explicitly set the subId (for example, created with the
+            // default constructor), replace the subId so it will listen to the account the
+            // telephony manager is created with.
+            if (listener.mSubId == null) {
+                listener.mSubId = mSubId;
+            }
             sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),
                     listener.callback, events, notifyNow);
         } catch (RemoteException ex) {
@@ -5446,6 +5468,19 @@
         return retval;
     }
 
+    private int getSubIdForPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+        int retval = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        try {
+            ITelecomService service = getTelecomService();
+            if (service != null) {
+                retval = getSubIdForPhoneAccount(service.getPhoneAccount(phoneAccountHandle));
+            }
+        } catch (RemoteException e) {
+        }
+
+        return retval;
+    }
+
     /**
      * Resets telephony manager settings back to factory defaults.
      *
@@ -5495,6 +5530,16 @@
     }
 
     /**
+     * Returns the current {@link ServiceState} information.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     */
+    public ServiceState getServiceState() {
+        return getServiceStateForSubscriber(getSubId());
+    }
+
+    /**
      * Returns the service state information on specified subscription. Callers require
      * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
      * @hide
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 5ef71df..e443911 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -147,6 +147,12 @@
         public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
             return MockContentProvider.this.uncanonicalize(uri);
         }
+
+        @Override
+        public boolean refresh(String callingPkg, Uri url, Bundle args,
+                ICancellationSignal cancellationSignal) throws RemoteException {
+            return MockContentProvider.this.refresh(url, args);
+        }
     }
     private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
 
@@ -251,6 +257,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean refresh(Uri url, Bundle args) {
+        throw new UnsupportedOperationException("unimplemented mock method call");
+    }
+
+    /**
      * Returns IContentProvider which calls back same methods in this class.
      * By overriding this class, we avoid the mechanism hidden behind ContentProvider
      * (IPC, etc.)
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index ee8c376..09d45d1 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -125,4 +125,10 @@
     public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
+
+    @Override
+    public boolean refresh(String callingPkg, Uri url, Bundle args,
+            ICancellationSignal cancellationSignal) throws RemoteException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index a30b362..9f7261d 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -59,13 +59,14 @@
     @Mock private INetworkStatsService mStatsService;
     @Mock private IControlsTethering mTetherHelper;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
+    @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices;
 
     private final TestLooper mLooper = new TestLooper();
     private TetherInterfaceStateMachine mTestedSm;
 
     private void initStateMachine(int interfaceType) throws Exception {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
-                mNMService, mStatsService, mTetherHelper);
+                mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the test of the world that we've changed from an unknown to available state.
@@ -91,7 +92,8 @@
     @Test
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
-                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
+                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper,
+                mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         mLooper.dispatchAll();
         verify(mTetherHelper).notifyInterfaceStateChange(
@@ -274,4 +276,4 @@
                 upstreamIface);
         mLooper.dispatchAll();
     }
-}
\ No newline at end of file
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
index e4cbb2f..3471165 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
@@ -145,4 +145,10 @@
     public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
         return null;
     }
+
+    @Override
+    public boolean refresh(String callingPkg, Uri url, Bundle args,
+                    ICancellationSignal cancellationSignal) throws RemoteException {
+        return false;
+    }
 }
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
index 9bf302a..7b58539 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
index 88c9cbc..c8a5fec 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
@@ -3,7 +3,7 @@
     <!-- Base application theme. -->
     <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
         <item name="myattr">@integer/ten</item>
-        <!-- Customize your theme here. -->
+        <item name="android:animateFirstView">false</item>
     </style>
 
 </resources>
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index e7c6cc9..55cfd6d 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -309,15 +309,7 @@
     /** Test activity.xml */
     @Test
     public void testActivity() throws ClassNotFoundException {
-        try {
-            renderAndVerify("activity.xml", "activity.png");
-        } catch (AssertionError e) {
-            // This is a KI in CalendarWidget and DatePicker rendering.
-            // Tracker bug: http://b.android.com/214370
-            if (!e.getLocalizedMessage().startsWith("Images differ (by 6.5%)")) {
-                throw e;
-            }
-        }
+        renderAndVerify("activity.xml", "activity.png");
     }
 
     @Test
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 017525b..bc38284 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.ScanSettings;
@@ -63,6 +64,12 @@
     int modifyPasspointManagementObject(String fqdn,
                                         in List<PasspointManagementObjectDefinition> mos);
 
+    boolean addPasspointConfiguration(in PasspointConfiguration config);
+
+    boolean removePasspointConfiguration(in String fqdn);
+
+    List<PasspointConfiguration> getPasspointConfigurations();
+
     void queryPasspointIcon(long bssid, String fileName);
 
     int matchProviderWithCurrentNetwork(String fqdn);
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 8d5efba..e48f7bd 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -22,6 +22,7 @@
 import android.net.NetworkUtils;
 import android.text.TextUtils;
 
+import java.lang.Math;
 import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.UnknownHostException;
@@ -136,6 +137,15 @@
      */
     public double rxSuccessRate;
 
+    private static final long RESET_TIME_STAMP = Long.MIN_VALUE;
+    private static final long FILTER_TIME_CONSTANT = 3000;
+    /**
+     * This factor is used to adjust the rate output under the new algorithm
+     * such that the result is comparable to the previous algorithm.
+     */
+    private static final long OUTPUT_SCALE_FACTOR = 5000;
+    private long mLastPacketCountUpdateTimeStamp;
+
     /**
      * @hide
      */
@@ -157,10 +167,9 @@
     public int score;
 
     /**
-     * TODO: get actual timestamp and calculate true rates
      * @hide
      */
-    public void updatePacketRates(WifiLinkLayerStats stats) {
+    public void updatePacketRates(WifiLinkLayerStats stats, long timeStamp) {
         if (stats != null) {
             long txgood = stats.txmpdu_be + stats.txmpdu_bk + stats.txmpdu_vi + stats.txmpdu_vo;
             long txretries = stats.retries_be + stats.retries_bk
@@ -169,18 +178,28 @@
             long txbad = stats.lostmpdu_be + stats.lostmpdu_bk
                     + stats.lostmpdu_vi + stats.lostmpdu_vo;
 
-            if (txBad <= txbad
+            if (mLastPacketCountUpdateTimeStamp != RESET_TIME_STAMP
+                    && mLastPacketCountUpdateTimeStamp < timeStamp
+                    && txBad <= txbad
                     && txSuccess <= txgood
                     && rxSuccess <= rxgood
                     && txRetries <= txretries) {
-                txBadRate = (txBadRate * 0.5)
-                        + ((double) (txbad - txBad) * 0.5);
-                txSuccessRate = (txSuccessRate * 0.5)
-                        + ((double) (txgood - txSuccess) * 0.5);
-                rxSuccessRate = (rxSuccessRate * 0.5)
-                        + ((double) (rxgood - rxSuccess) * 0.5);
-                txRetriesRate = (txRetriesRate * 0.5)
-                        + ((double) (txretries - txRetries) * 0.5);
+                    long timeDelta = timeStamp - mLastPacketCountUpdateTimeStamp;
+                    double lastSampleWeight = Math.exp(-1.0 * timeDelta / FILTER_TIME_CONSTANT);
+                    double currentSampleWeight = 1.0 - lastSampleWeight;
+
+                    txBadRate = txBadRate * lastSampleWeight
+                        + (txbad - txBad) * OUTPUT_SCALE_FACTOR / timeDelta
+                        * currentSampleWeight;
+                    txSuccessRate = txSuccessRate * lastSampleWeight
+                        + (txgood - txSuccess) * OUTPUT_SCALE_FACTOR / timeDelta
+                        * currentSampleWeight;
+                    rxSuccessRate = rxSuccessRate * lastSampleWeight
+                        + (rxgood - rxSuccess) * OUTPUT_SCALE_FACTOR / timeDelta
+                        * currentSampleWeight;
+                    txRetriesRate = txRetriesRate * lastSampleWeight
+                        + (txretries - txRetries) * OUTPUT_SCALE_FACTOR / timeDelta
+                        * currentSampleWeight;
             } else {
                 txBadRate = 0;
                 txSuccessRate = 0;
@@ -191,6 +210,7 @@
             txSuccess = txgood;
             rxSuccess = rxgood;
             txRetries = txretries;
+            mLastPacketCountUpdateTimeStamp = timeStamp;
         } else {
             txBad = 0;
             txSuccess = 0;
@@ -200,6 +220,7 @@
             txSuccessRate = 0;
             rxSuccessRate = 0;
             txRetriesRate = 0;
+            mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
         }
     }
 
@@ -243,6 +264,7 @@
         mRssi = INVALID_RSSI;
         mLinkSpeed = -1;
         mFrequency = -1;
+        mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
     }
 
     /** @hide */
@@ -268,6 +290,7 @@
         badRssiCount = 0;
         linkStuckCount = 0;
         score = 0;
+        mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
     }
 
     /**
@@ -295,6 +318,8 @@
             txRetriesRate = source.txRetriesRate;
             txSuccessRate = source.txSuccessRate;
             rxSuccessRate = source.rxSuccessRate;
+            mLastPacketCountUpdateTimeStamp =
+                source.mLastPacketCountUpdateTimeStamp;
             score = source.score;
             badRssiCount = source.badRssiCount;
             lowRssiCount = source.lowRssiCount;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 18a580e..98a6ca5 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -26,6 +26,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -871,6 +872,56 @@
     }
 
     /**
+     * Add a Passpoint configuration.  The configuration provides a credential
+     * for connecting to Passpoint networks that are operated by the Passpoint
+     * service provider specified in the configuration.
+     *
+     * Each configuration is uniquely identified by its FQDN (Fully Qualified Domain
+     * Name).  In the case when there is an existing configuration with the same base
+     * domain, the new configuration will replace the existing configuration.
+     *
+     * @param config The Passpoint configuration to be added
+     * @return true on success or false on failure
+     * @hide
+     */
+    public boolean addPasspointConfiguration(PasspointConfiguration config) {
+        try {
+            return mService.addPasspointConfiguration(config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove a Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
+     *
+     * @param fqdn The FQDN of the passpoint configuration to be removed
+     * @return true on success or false on failure
+     * @hide
+     */
+    public boolean removePasspointConfiguration(String fqdn) {
+        try {
+            return mService.removePasspointConfiguration(fqdn);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the list of installed Passpoint configurations.
+     *
+     * @return A list of PasspointConfiguration or null
+     * @hide
+     */
+    public List<PasspointConfiguration> getPasspointConfigurations() {
+        try {
+            return mService.getPasspointConfigurations();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Query for a Hotspot 2.0 release 2 OSU icon
      * @param bssid The BSSID of the AP
      * @param fileName Icon file name
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigBuilder.java b/wifi/java/android/net/wifi/hotspot2/ConfigBuilder.java
new file mode 100644
index 0000000..96db5d0
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigBuilder.java
@@ -0,0 +1,473 @@
+/**
+ * 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.wifi.hotspot2;
+
+import android.net.wifi.hotspot2.omadm.PPSMOParser;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class for building PasspointConfiguration from an installation file.
+ *
+ * @hide
+ */
+public final class ConfigBuilder {
+    private static final String TAG = "ConfigBuilder";
+
+    // Header names.
+    private static final String CONTENT_TYPE = "Content-Type";
+    private static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+
+    // MIME types.
+    private static final String TYPE_MULTIPART_MIXED = "multipart/mixed";
+    private static final String TYPE_WIFI_CONFIG = "application/x-wifi-config";
+    private static final String TYPE_PASSPOINT_PROFILE = "application/x-passpoint-profile";
+    private static final String TYPE_CA_CERT = "application/x-x509-ca-cert";
+    private static final String TYPE_PKCS12 = "application/x-pkcs12";
+
+    private static final String ENCODING_BASE64 = "base64";
+    private static final String BOUNDARY = "boundary=";
+
+    /**
+     * Class represent a MIME (Multipurpose Internet Mail Extension) part.
+     */
+    private static class MimePart {
+        /**
+         * Content type of the part.
+         */
+        public String type = null;
+
+        /**
+         * Decoded data.
+         */
+        public byte[] data = null;
+
+        /**
+         * Flag indicating if this is the last part (ending with --{boundary}--).
+         */
+        public boolean isLast = false;
+    }
+
+    /**
+     * Class represent the MIME (Multipurpose Internet Mail Extension) header.
+     */
+    private static class MimeHeader {
+        /**
+         * Content type.
+         */
+        public String contentType = null;
+
+        /**
+         * Boundary string (optional), only applies for the outter MIME header.
+         */
+        public String boundary = null;
+
+        /**
+         * Encoding type.
+         */
+        public String encodingType = null;
+    }
+
+
+    /**
+     * Parse the Hotspot 2.0 Release 1 configuration data into a {@link PasspointConfiguration}
+     * object.  The configuration data is a base64 encoded MIME multipart data.  Below is
+     * the format of the decoded message:
+     *
+     * Content-Type: multipart/mixed; boundary={boundary}
+     * Content-Transfer-Encoding: base64
+     *
+     * --{boundary}
+     * Content-Type: application/x-passpoint-profile
+     * Content-Transfer-Encoding: base64
+     *
+     * [base64 encoded Passpoint profile data]
+     * --{boundary}
+     * Content-Type: application/x-x509-ca-cert
+     * Content-Transfer-Encoding: base64
+     *
+     * [base64 encoded X509 CA certificate data]
+     * --{boundary}
+     * Content-Type: application/x-pkcs12
+     * Content-Transfer-Encoding: base64
+     *
+     * [base64 encoded PKCS#12 ASN.1 structure containing client certificate chain]
+     * --{boundary}
+     *
+     * @param mimeType MIME type of the encoded data.
+     * @param data A base64 encoded MIME multipart message containing the Passpoint profile
+     *             (required), CA (Certificate Authority) certificate (optional), and client
+     *             certificate chain (optional).
+     * @return {@link PasspointConfiguration}
+     */
+    public static PasspointConfiguration buildPasspointConfig(String mimeType, byte[] data) {
+        // Verify MIME type.
+        if (!TextUtils.equals(mimeType, TYPE_WIFI_CONFIG)) {
+            Log.e(TAG, "Unexpected MIME type: " + mimeType);
+            return null;
+        }
+
+        try {
+            // Decode the data.
+            byte[] decodedData = Base64.decode(new String(data, StandardCharsets.ISO_8859_1),
+                    Base64.DEFAULT);
+            Map<String, byte[]> mimeParts = parseMimeMultipartMessage(new LineNumberReader(
+                    new InputStreamReader(new ByteArrayInputStream(decodedData),
+                            StandardCharsets.ISO_8859_1)));
+            return createPasspointConfig(mimeParts);
+        } catch (IOException | IllegalArgumentException e) {
+            Log.e(TAG, "Failed to parse installation file: " + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * Create a {@link PasspointConfiguration} object from list of MIME (Multipurpose Internet
+     * Mail Extension) parts.
+     *
+     * @param mimeParts Map of content type and content data.
+     * @return {@link PasspointConfiguration}
+     * @throws IOException
+     */
+    private static PasspointConfiguration createPasspointConfig(Map<String, byte[]> mimeParts)
+            throws IOException {
+        byte[] profileData = mimeParts.get(TYPE_PASSPOINT_PROFILE);
+        if (profileData == null) {
+            throw new IOException("Missing Passpoint Profile");
+        }
+
+        PasspointConfiguration config = PPSMOParser.parseMOText(new String(profileData));
+        if (config == null) {
+            throw new IOException("Failed to parse Passpoint profile");
+        }
+
+        // Credential is needed for storing the certificates and private client key.
+        if (config.credential == null) {
+            throw new IOException("Passpoint profile missing credential");
+        }
+
+        // Parse CA (Certificate Authority) certificate.
+        byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
+        if (caCertData != null) {
+            try {
+                config.credential.caCertificate = parseCACert(caCertData);
+            } catch (CertificateException e) {
+                throw new IOException("Failed to parse CA Certificate");
+            }
+        }
+
+        // Parse PKCS12 data for client private key and certificate chain.
+        byte[] pkcs12Data = mimeParts.get(TYPE_PKCS12);
+        if (pkcs12Data != null) {
+            try {
+                Pair<PrivateKey, List<X509Certificate>> clientKey = parsePkcs12(pkcs12Data);
+                config.credential.clientPrivateKey = clientKey.first;
+                config.credential.clientCertificateChain =
+                        clientKey.second.toArray(new X509Certificate[clientKey.second.size()]);
+            } catch(GeneralSecurityException | IOException e) {
+                throw new IOException("Failed to parse PCKS12 string");
+            }
+        }
+        return config;
+    }
+
+    /**
+     * Parse a MIME (Multipurpose Internet Mail Extension) multipart message from the given
+     * input stream.
+     *
+     * @param in The input stream for reading the message data
+     * @return A map of a content type and content data pair
+     * @throws IOException
+     */
+    private static Map<String, byte[]> parseMimeMultipartMessage(LineNumberReader in)
+            throws IOException {
+        // Parse the outer MIME header.
+        MimeHeader header = parseHeaders(in);
+        if (!TextUtils.equals(header.contentType, TYPE_MULTIPART_MIXED)) {
+            throw new IOException("Invalid content type: " + header.contentType);
+        }
+        if (TextUtils.isEmpty(header.boundary)) {
+            throw new IOException("Missing boundary string");
+        }
+        if (!TextUtils.equals(header.encodingType, ENCODING_BASE64)) {
+            throw new IOException("Unexpected encoding: " + header.encodingType);
+        }
+
+        // Read pass the first boundary string.
+        for (;;) {
+            String line = in.readLine();
+            if (line == null) {
+                throw new IOException("Unexpected EOF before first boundary @ " +
+                        in.getLineNumber());
+            }
+            if (line.equals("--" + header.boundary)) {
+                break;
+            }
+        }
+
+        // Parse each MIME part.
+        Map<String, byte[]> mimeParts = new HashMap<>();
+        boolean isLast = false;
+        do {
+            MimePart mimePart = parseMimePart(in, header.boundary);
+            mimeParts.put(mimePart.type, mimePart.data);
+            isLast = mimePart.isLast;
+        } while(!isLast);
+        return mimeParts;
+    }
+
+    /**
+     * Parse a MIME (Multipurpose Internet Mail Extension) part.  We expect the data to
+     * be encoded in base64.
+     *
+     * @param in Input stream to read the data from
+     * @param boundary Boundary string indicate the end of the part
+     * @return {@link MimePart}
+     * @throws IOException
+     */
+    private static MimePart parseMimePart(LineNumberReader in, String boundary)
+            throws IOException {
+        MimeHeader header = parseHeaders(in);
+        // Expect encoding type to be base64.
+        if (!TextUtils.equals(header.encodingType, ENCODING_BASE64)) {
+            throw new IOException("Unexpected encoding type: " + header.encodingType);
+        }
+
+        // Check for a valid content type.
+        if (!TextUtils.equals(header.contentType, TYPE_PASSPOINT_PROFILE) &&
+                !TextUtils.equals(header.contentType, TYPE_CA_CERT) &&
+                !TextUtils.equals(header.contentType, TYPE_PKCS12)) {
+            throw new IOException("Unexpected content type: " + header.contentType);
+        }
+
+        StringBuilder text = new StringBuilder();
+        boolean isLast = false;
+        String partBoundary = "--" + boundary;
+        String endBoundary = partBoundary + "--";
+        for (;;) {
+            String line = in.readLine();
+            if (line == null) {
+                throw new IOException("Unexpected EOF file in body @ " + in.getLineNumber());
+            }
+            // Check for boundary line.
+            if (line.startsWith(partBoundary)) {
+                if (line.equals(endBoundary)) {
+                    isLast = true;
+                }
+                break;
+            }
+            text.append(line);
+        }
+
+        MimePart part = new MimePart();
+        part.type = header.contentType;
+        part.data = Base64.decode(text.toString(), Base64.DEFAULT);
+        part.isLast = isLast;
+        return part;
+    }
+
+    /**
+     * Parse a MIME (Multipurpose Internet Mail Extension) header from the input stream.
+     * @param in Input stream to read from.
+     * @return {@link MimeHeader}
+     * @throws IOException
+     */
+    private static MimeHeader parseHeaders(LineNumberReader in)
+            throws IOException {
+        MimeHeader header = new MimeHeader();
+
+        // Read the header from the input stream.
+        Map<String, String> headers = readHeaders(in);
+
+        // Parse each header.
+        for (Map.Entry<String, String> entry : headers.entrySet()) {
+            switch (entry.getKey()) {
+                case CONTENT_TYPE:
+                    Pair<String, String> value = parseContentType(entry.getValue());
+                    header.contentType = value.first;
+                    header.boundary = value.second;
+                    break;
+                case CONTENT_TRANSFER_ENCODING:
+                    header.encodingType = entry.getValue();
+                    break;
+                default:
+                    throw new IOException("Unexpected header: " + entry.getKey());
+            }
+        }
+        return header;
+    }
+
+    /**
+     * Parse the Content-Type header value.  The value will contain the content type string and
+     * an optional boundary string separated by a ";".  Below are examples of valid Content-Type
+     * header value:
+     *   multipart/mixed; boundary={boundary}
+     *   application/x-passpoint-profile
+     *
+     * @param contentType The Content-Type value string
+     * @return A pair of content type and boundary string
+     * @throws IOException
+     */
+    private static Pair<String, String> parseContentType(String contentType) throws IOException {
+        String[] attributes = contentType.toString().split(";");
+        String type = null;
+        String boundary = null;
+
+        if (attributes.length < 1 || attributes.length > 2) {
+            throw new IOException("Invalid Content-Type: " + contentType);
+        }
+
+        type = attributes[0].trim();
+        if (attributes.length == 2) {
+            boundary = attributes[1].trim();
+            if (!boundary.startsWith(BOUNDARY)) {
+                throw new IOException("Invalid Content-Type: " + contentType);
+            }
+            boundary = boundary.substring(BOUNDARY.length());
+            // Remove the leading and trailing quote if present.
+            if (boundary.length() > 1 && boundary.startsWith("\"") && boundary.endsWith("\"")) {
+                boundary = boundary.substring(1, boundary.length()-1);
+            }
+        }
+
+        return new Pair<String, String>(type, boundary);
+    }
+
+    /**
+     * Read the headers from the given input stream.  The header section is terminated by
+     * an empty line.
+     *
+     * @param in The input stream to read from
+     * @return Map of key-value pairs.
+     * @throws IOException
+     */
+    private static Map<String, String> readHeaders(LineNumberReader in)
+            throws IOException {
+        Map<String, String> headers = new HashMap<>();
+        String line;
+        String name = null;
+        StringBuilder value = null;
+        for (;;) {
+            line = in.readLine();
+            if (line == null) {
+                throw new IOException("Missing line @ " + in.getLineNumber());
+            }
+
+            // End of headers section.
+            if (line.length() == 0 || line.trim().length() == 0) {
+                // Save the previous header line.
+                if (name != null) {
+                    headers.put(name, value.toString());
+                }
+                break;
+            }
+
+            int nameEnd = line.indexOf(':');
+            if (nameEnd < 0) {
+                if (value != null) {
+                    // Continuation line for the header value.
+                    value.append(' ').append(line.trim());
+                } else {
+                    throw new IOException("Bad header line: '" + line + "' @ " +
+                            in.getLineNumber());
+                }
+            } else {
+                // New header line detected, make sure it doesn't start with a whitespace.
+                if (Character.isWhitespace(line.charAt(0))) {
+                    throw new IOException("Illegal blank prefix in header line '" + line +
+                            "' @ " + in.getLineNumber());
+                }
+
+                if (name != null) {
+                    // Save the previous header line.
+                    headers.put(name, value.toString());
+                }
+
+                // Setup the current header line.
+                name = line.substring(0, nameEnd).trim();
+                value = new StringBuilder();
+                value.append(line.substring(nameEnd+1).trim());
+            }
+        }
+        return headers;
+    }
+
+    /**
+     * Parse a CA (Certificate Authority) certificate data and convert it to a
+     * X509Certificate object.
+     *
+     * @param octets Certificate data
+     * @return X509Certificate
+     * @throws CertificateException
+     */
+    private static X509Certificate parseCACert(byte[] octets) throws CertificateException {
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(octets));
+    }
+
+    private static Pair<PrivateKey, List<X509Certificate>> parsePkcs12(byte[] octets)
+            throws GeneralSecurityException, IOException {
+        KeyStore ks = KeyStore.getInstance("PKCS12");
+        ByteArrayInputStream in = new ByteArrayInputStream(octets);
+        ks.load(in, new char[0]);
+        in.close();
+
+        // Only expects one set of key and certificate chain.
+        if (ks.size() != 1) {
+            throw new IOException("Unexpected key size: " + ks.size());
+        }
+
+        String alias = ks.aliases().nextElement();
+        if (alias == null) {
+            throw new IOException("No alias found");
+        }
+
+        PrivateKey clientKey = (PrivateKey) ks.getKey(alias, null);
+        List<X509Certificate> clientCertificateChain = null;
+        Certificate[] chain = ks.getCertificateChain(alias);
+        if (chain != null) {
+            clientCertificateChain = new ArrayList<>();
+            for (Certificate certificate : chain) {
+                if (!(certificate instanceof X509Certificate)) {
+                    throw new IOException("Unexpceted certificate type: " +
+                            certificate.getClass());
+                }
+                clientCertificateChain.add((X509Certificate) certificate);
+            }
+        }
+        return new Pair<PrivateKey, List<X509Certificate>>(clientKey, clientCertificateChain);
+    }
+}
\ No newline at end of file
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
new file mode 100644
index 0000000..8c1eb08
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -0,0 +1,85 @@
+Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
+dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
+cHBsaWNhdGlvbi94LXBhc3Nwb2ludC1wcm9maWxlCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6
+IGJhc2U2NAoKUEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29n
+SUR4V1pYSkVWRVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVo
+YldVK1VHVnlVSEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0Fn
+UEZKVVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhi
+V1UrZFhKdU9uZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZ
+M0pwY0hScGIyNDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThM
+MUpVVUhKdmNHVnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSth
+VEF3TVR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRt
+RnRaVDVJYjIxbFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
+QWdJQ0FnSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklD
+QWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrWlJSRTQ4TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1
+MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdv
+Z0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2
+ClpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpo
+YkhWbFBnb2dJQ0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4
+VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVo
+YldVK0NpQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhi
+RzA4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHlj
+bVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9i
+MlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2Iy
+UmxUbUZ0ClpUNVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lD
+QWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BD
+OU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3
+dlZtRnNkV1UrCkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0
+S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldV
+K0NpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1G
+dFpUNUZRVkJVZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1Ur
+TWpFOEwxWmhiSFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0Fn
+SUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2
+WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0
+VmpJOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThM
+MDV2WkdVK0NpQWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJ
+Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhi
+V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
+a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
+VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
+bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
+OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4
+VG05awpaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFs
+UGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2
+WkdWT1lXMWxQa1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpROApMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZU
+bTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQ
+QzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94
+LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFD
+UlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtR
+VWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5U
+bFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpU
+azFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5V
+VEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMw
+TkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZY
+TldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01T
+dHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJV
+RkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJo
+U1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdK
+ck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtz
+M2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxh
+CmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVX
+TTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZH
+U1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFF
+YlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJk
+MFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldV
+akJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZ
+MDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZD
+amxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNY
+ZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1Ew
+OTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JX
+MVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVF
+MXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhw
+aFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpG
+S1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNS
+a1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmts
+RFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
new file mode 100644
index 0000000..6d86dd5
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -0,0 +1,73 @@
+Content-Type: multipart/mixed; boundary={boundary}
+Content-Transfer-Encoding: base64
+
+--{boundary}
+Content-Type: application/x-passpoint-profile
+Content-Transfer-Encoding: base64
+
+PE1nbXRUcmVlIHhtbG5zPSJzeW5jbWw6ZG1kZGYxLjIiPgogIDxWZXJEVEQ+MS4yPC9WZXJEVEQ+
+CiAgPE5vZGU+CiAgICA8Tm9kZU5hbWU+UGVyUHJvdmlkZXJTdWJzY3JpcHRpb248L05vZGVOYW1l
+PgogICAgPFJUUHJvcGVydGllcz4KICAgICAgPFR5cGU+CiAgICAgICAgPERERk5hbWU+dXJuOndm
+YTptbzpob3RzcG90MmRvdDAtcGVycHJvdmlkZXJzdWJzY3JpcHRpb246MS4wPC9EREZOYW1lPgog
+ICAgICA8L1R5cGU+CiAgICA8L1JUUHJvcGVydGllcz4KICAgIDxOb2RlPgogICAgICA8Tm9kZU5h
+bWU+aTAwMTwvTm9kZU5hbWU+CiAgICAgIDxOb2RlPgogICAgICAgIDxOb2RlTmFtZT5Ib21lU1A8
+L05vZGVOYW1lPgogICAgICAgIDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPkZyaWVuZGx5TmFt
+ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8VmFsdWU+Q2VudHVyeSBIb3VzZTwvVmFsdWU+CiAgICAg
+ICAgPC9Ob2RlPgogICAgICAgIDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPkZRRE48L05vZGVO
+YW1lPgogICAgICAgICAgPFZhbHVlPm1pNi5jby51azwvVmFsdWU+CiAgICAgICAgPC9Ob2RlPgog
+ICAgICAgIDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlJvYW1pbmdDb25zb3J0aXVtT0k8L05v
+ZGVOYW1lPgogICAgICAgICAgPFZhbHVlPjExMjIzMyw0NDU1NjY8L1ZhbHVlPgogICAgICAgIDwv
+Tm9kZT4KICAgICAgPC9Ob2RlPgogICAgICA8Tm9kZT4KICAgICAgICA8Tm9kZU5hbWU+Q3JlZGVu
+dGlhbDwvTm9kZU5hbWU+CiAgICAgICAgPE5vZGU+CiAgICAgICAgICA8Tm9kZU5hbWU+UmVhbG08
+L05vZGVOYW1lPgogICAgICAgICAgPFZhbHVlPnNoYWtlbi5zdGlycmVkLmNvbTwvVmFsdWU+CiAg
+ICAgICAgPC9Ob2RlPgogICAgICAgIDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlVzZXJuYW1l
+UGFzc3dvcmQ8L05vZGVOYW1lPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2RlTmFt
+ZT5Vc2VybmFtZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT5qYW1lczwvVmFsdWU+CiAg
+ICAgICAgICA8L05vZGU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1lPlBh
+c3N3b3JkPC9Ob2RlTmFtZT4KICAgICAgICAgICAgPFZhbHVlPlltOXVaREF3Tnc9PTwvVmFsdWU+
+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
+PkVBUE1ldGhvZDwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxOb2RlPgogICAgICAgICAgICAgIDxO
+b2RlTmFtZT5FQVBUeXBlPC9Ob2RlTmFtZT4KICAgICAgICAgICAgICA8VmFsdWU+MjE8L1ZhbHVl
+PgogICAgICAgICAgICA8L05vZGU+CiAgICAgICAgICAgIDxOb2RlPgogICAgICAgICAgICAgIDxO
+b2RlTmFtZT5Jbm5lck1ldGhvZDwvTm9kZU5hbWU+CiAgICAgICAgICAgICAgPFZhbHVlPk1TLUNI
+QVAtVjI8L1ZhbHVlPgogICAgICAgICAgICA8L05vZGU+CiAgICAgICAgICA8L05vZGU+CiAgICAg
+ICAgPC9Ob2RlPgogICAgICAgIDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPkRpZ2l0YWxDZXJ0
+aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
+PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
+bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
+ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
+IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
+ZT4KICAgICAgICAgICAgPE5vZGVOYW1lPklNU0k8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFs
+dWU+aW1zaTwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgICA8Tm9kZT4KICAgICAg
+ICAgICAgPE5vZGVOYW1lPkVBUFR5cGU8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+MjQ8
+L1ZhbHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgIDwvTm9kZT4KICAgICAgPC9Ob2RlPgog
+ICAgPC9Ob2RlPgogIDwvTm9kZT4KPC9NZ210VHJlZT4K
+
+--{boundary}
+Content-Type: application/x-x509-ca-cert
+Content-Transfer-Encoding: base64
+
+LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLRENDQWhDZ0F3SUJBZ0lKQUlMbEZkd3pM
+VnVyTUEwR0NTcUdTSWIzRFFFQkN3VUFNQkl4RURBT0JnTlYKQkFNVEIwVkJVQ0JEUVRFd0hoY05N
+VFl3TVRFeU1URTFNREUxV2hjTk1qWXdNVEE1TVRFMU1ERTFXakFTTVJBdwpEZ1lEVlFRREV3ZEZR
+VkFnUTBFeE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCnpuQVBV
+ejI2TXNhZTR3czQzY3pSNDEvSjJRdHJTSVpVS21WVXNWdW1EYllIclBOdlRYS1NNWEFjZXdPUkRR
+WVgKUnF2SHZwbjhDc2NCMStvR1hadkh3eGo0elYwV0tvSzJ6ZVhrYXUzdmN5bDNISUt1cEpmcTJU
+RUFDZWZWamowdApKVytYMzVQR1dwOS9INXpJVU5WTlZqUzdVbXM4NEl2S2hSQjg1MTJQQjlVeUhh
+Z1hZVlg1R1dwQWNWcHlmcmxSCkZJOVFkaGgrUGJrMHV5a3RkYmYvQ2RmZ0hPb2ViclR0d1Jsak0w
+b0R0WCsyQ3Y2ajB3Qks3aEQ4cFB2ZjErdXkKR3pjemlnQVUvNEt3N2VacXlkZjlCKzVSdXBSK0la
+aXBYNDF4RWlJcktSd3FpNTE3V1d6WGNqYUcyY05iZjQ1MQp4cEg1UG5WM2kxdHEwNGpNR1FVekZ3
+SURBUUFCbzRHQU1INHdIUVlEVlIwT0JCWUVGSXdYNHZzOEJpQmNTY29kCjVub1pIUk04RTQraU1F
+SUdBMVVkSXdRN01EbUFGSXdYNHZzOEJpQmNTY29kNW5vWkhSTThFNCtpb1Jha0ZEQVMKTVJBd0Rn
+WURWUVFERXdkRlFWQWdRMEV4Z2drQWd1VVYzRE10VzZzd0RBWURWUjBUQkFVd0F3RUIvekFMQmdO
+VgpIUThFQkFNQ0FRWXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRmZRcU9UQTdSdjdLK2x1UTdw
+bmFzNEJZd0hFCjlHRVAvdW9odjZLT3kwVEdRRmJyUlRqRm9MVk5COUJaMXltTURaMC9USXdJVWM3
+d2k3YTh0NW1FcVlIMTUzd1cKYVdvb2lTanlMTGh1STRzTnJOQ090aXNkQnEycjJNRlh0NmgwbUFR
+WU9QdjhSOEs3L2ZnU3hHRnF6aHlObW1WTAoxcUJKbGR4MzRTcHdzVEFMUVZQYjRoR3dKelpmcjFQ
+Y3BFUXg2eE1uVGw4eEVXWkUzTXM5OXVhVXhiUXFJd1J1CkxnQU9rTkNtWTJtODlWaHphSEoxdVY4
+NUFkTS90RCtZc21sbm5qdDlMUkNlamJCaXBqSUdqT1hyZzFKUCtseFYKbXVNNHZIK1AvbWxteHNQ
+UHowZDY1YitFR21KWnBvTGtPL3RkTk52Q1l6akpwVEVXcEVzTzZOTWhLWW89Ci0tLS0tRU5EIENF
+UlRJRklDQVRFLS0tLS0K
+--{boundary}--
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithInvalidContentType.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithInvalidContentType.base64
new file mode 100644
index 0000000..906bfb3
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithInvalidContentType.base64
@@ -0,0 +1,85 @@
+Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
+dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
+cHBsaWNhdGlvbi9wYXNzcG9pbnQtcHJvZmlsZQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBi
+YXNlNjQKClBFMW5iWFJVY21WbElIaHRiRzV6UFNKemVXNWpiV3c2Wkcxa1pHWXhMaklpUGdvZ0lE
+eFdaWEpFVkVRK01TNHlQQzlXWlhKRVZFUSsKQ2lBZ1BFNXZaR1UrQ2lBZ0lDQThUbTlrWlU1aGJX
+VStVR1Z5VUhKdmRtbGtaWEpUZFdKelkzSnBjSFJwYjI0OEwwNXZaR1ZPWVcxbApQZ29nSUNBZ1BG
+SlVVSEp2Y0dWeWRHbGxjejRLSUNBZ0lDQWdQRlI1Y0dVK0NpQWdJQ0FnSUNBZ1BFUkVSazVoYldV
+K2RYSnVPbmRtCllUcHRienBvYjNSemNHOTBNbVJ2ZERBdGNHVnljSEp2ZG1sa1pYSnpkV0p6WTNK
+cGNIUnBiMjQ2TVM0d1BDOUVSRVpPWVcxbFBnb2cKSUNBZ0lDQThMMVI1Y0dVK0NpQWdJQ0E4TDFK
+VVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQThUbTlrWlU1aApiV1UrYVRB
+d01Ud3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0
+WlQ1SWIyMWxVMUE4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0Fn
+SUNBZ1BFNXZaR1ZPWVcxbFBrWnlhV1Z1Wkd4NVRtRnQKWlR3dlRtOWtaVTVoYldVK0NpQWdJQ0Fn
+SUNBZ0lDQThWbUZzZFdVK1EyVnVkSFZ5ZVNCSWIzVnpaVHd2Vm1Gc2RXVStDaUFnSUNBZwpJQ0Fn
+UEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQ
+a1pSUkU0OEwwNXZaR1ZPCllXMWxQZ29nSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbTFwTmk1amJ5NTFh
+end2Vm1Gc2RXVStDaUFnSUNBZ0lDQWdQQzlPYjJSbFBnb2cKSUNBZ0lDQWdJRHhPYjJSbFBnb2dJ
+Q0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQbEp2WVcxcGJtZERiMjV6YjNKMGFYVnRUMGs4TDA1dgpa
+R1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBqRXhNakl6TXl3ME5EVTFOalk4TDFaaGJI
+VmxQZ29nSUNBZ0lDQWdJRHd2ClRtOWtaVDRLSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBOFRt
+OWtaVDRLSUNBZ0lDQWdJQ0E4VG05a1pVNWhiV1UrUTNKbFpHVnUKZEdsaGJEd3ZUbTlrWlU1aGJX
+VStDaUFnSUNBZ0lDQWdQRTV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStVbVZoYkcw
+OApMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbk5vWVd0bGJpNXpkR2x5Y21W
+a0xtTnZiVHd2Vm1Gc2RXVStDaUFnCklDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJS
+bFBnb2dJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQbFZ6WlhKdVlXMWwKVUdGemMzZHZjbVE4TDA1
+dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBZ0lEeE9iMlJs
+VG1GdApaVDVWYzJWeWJtRnRaVHd2VG05a1pVNWhiV1UrQ2lBZ0lDQWdJQ0FnSUNBZ0lEeFdZV3gx
+WlQ1cVlXMWxjend2Vm1Gc2RXVStDaUFnCklDQWdJQ0FnSUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0Fn
+SUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsQmgKYzNOM2IzSmtQQzlP
+YjJSbFRtRnRaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGxsdE9YVmFSRUYzVG5jOVBUd3ZW
+bUZzZFdVKwpDaUFnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0E4VG05a1pUNEtJ
+Q0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsClBrVkJVRTFsZEdodlpEd3ZUbTlrWlU1aGJXVStD
+aUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRa
+VDVGUVZCVWVYQmxQQzlPYjJSbFRtRnRaVDRLSUNBZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVK01q
+RThMMVpoYkhWbApQZ29nSUNBZ0lDQWdJQ0FnSUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBZ0lE
+eE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ0lDQWdJRHhPCmIyUmxUbUZ0WlQ1SmJtNWxjazFsZEdodlpE
+d3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGsxVExVTkkKUVZBdFZq
+SThMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0E4TDA1
+dlpHVStDaUFnSUNBZwpJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNB
+Z0lDQWdQRTV2WkdWT1lXMWxQa1JwWjJsMFlXeERaWEowCmFXWnBZMkYwWlR3dlRtOWtaVTVoYldV
+K0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtO
+bGNuUnBabWxqWVhSbFZIbHdaVHd2VG05a1pVNWhiV1UrQ2lBZ0lDQWdJQ0FnSUNBZ0lEeFdZV3gx
+WlQ1NE5UQTVkak04TDFaaApiSFZsUGdvZ0lDQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0Fn
+SUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBZ0lEeE9iMlJsClRtRnRaVDVEWlhKMFUwaEJNalUy
+Um1sdVoyVnlVSEpwYm5ROEwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdJQ0E4Vm1Gc2RXVSsK
+TVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1N
+V1l4WmpGbU1XWXhaakZtTVdZeApaakZtTVdZeFpqd3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ0lDQThM
+MDV2WkdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnCklEeE9iMlJsUGdvZ0lDQWdJ
+Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBsTkpUVHd2VG05a1pVNWhiV1UrQ2lBZ0lDQWdJQ0FnSUNBOFRt
+OWsKWlQ0S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQa2xOVTBrOEwwNXZaR1ZPWVcxbFBn
+b2dJQ0FnSUNBZ0lDQWdJQ0E4Vm1GcwpkV1UrYVcxemFUd3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ0lD
+QThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0S0lDQWdJQ0FnCklDQWdJQ0FnUEU1dlpH
+Vk9ZVzFsUGtWQlVGUjVjR1U4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+K01qUTgKTDFaaGJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHd2VG05
+a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnUEM5T2IyUmxQZ29nSUR3dlRtOWtaVDRLUEM5
+TloyMTBWSEpsWlQ0SwoKLS17Ym91bmRhcnl9CkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24veC14
+NTA5LWNhLWNlcnQKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgpMUzB0TFMxQ1JV
+ZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VSTFJFTkRRV2hEWjBGM1NVSkJaMGxLUVVs
+TWJFWmtkM3BNClZuVnlUVUV3UjBOVGNVZFRTV0l6UkZGRlFrTjNWVUZOUWtsNFJVUkJUMEpuVGxZ
+S1FrRk5WRUl3VmtKVlEwSkVVVlJGZDBob1kwNU4KVkZsM1RWUkZlVTFVUlRGTlJFVXhWMmhqVGsx
+cVdYZE5WRUUxVFZSRk1VMUVSVEZYYWtGVFRWSkJkd3BFWjFsRVZsRlJSRVYzWkVaUgpWa0ZuVVRC
+RmVFMUpTVUpKYWtGT1FtZHJjV2hyYVVjNWR6QkNRVkZGUmtGQlQwTkJVVGhCVFVsSlFrTm5TME5C
+VVVWQkNucHVRVkJWCmVqSTJUWE5oWlRSM2N6UXpZM3BTTkRFdlNqSlJkSEpUU1ZwVlMyMVdWWE5X
+ZFcxRVlsbEljbEJPZGxSWVMxTk5XRUZqWlhkUFVrUlIKV1ZnS1VuRjJTSFp3YmpoRGMyTkNNU3R2
+UjFoYWRraDNlR28wZWxZd1YwdHZTeko2WlZocllYVXpkbU41YkROSVNVdDFjRXBtY1RKVQpSVUZE
+WldaV2Ftb3dkQXBLVnl0WU16VlFSMWR3T1M5SU5YcEpWVTVXVGxacVV6ZFZiWE00TkVsMlMyaFNR
+amcxTVRKUVFqbFZlVWhoCloxaFpWbGcxUjFkd1FXTldjSGxtY214U0NrWkpPVkZrYUdnclVHSnJN
+SFY1YTNSa1ltWXZRMlJtWjBoUGIyVmljbFIwZDFKc2FrMHcKYjBSMFdDc3lRM1kyYWpCM1FrczNh
+RVE0Y0ZCMlpqRXJkWGtLUjNwamVtbG5RVlV2TkV0M04yVmFjWGxrWmpsQ0t6VlNkWEJTSzBsYQph
+WEJZTkRGNFJXbEpja3RTZDNGcE5URTNWMWQ2V0dOcVlVY3lZMDVpWmpRMU1RcDRjRWcxVUc1V00y
+a3hkSEV3TkdwTlIxRlZla1ozClNVUkJVVUZDYnpSSFFVMUlOSGRJVVZsRVZsSXdUMEpDV1VWR1NY
+ZFlOSFp6T0VKcFFtTlRZMjlrQ2pWdWIxcElVazA0UlRRcmFVMUYKU1VkQk1WVmtTWGRSTjAxRWJV
+RkdTWGRZTkhaek9FSnBRbU5UWTI5a05XNXZXa2hTVFRoRk5DdHBiMUpoYTBaRVFWTUtUVkpCZDBS
+bgpXVVJXVVZGRVJYZGtSbEZXUVdkUk1FVjRaMmRyUVdkMVZWWXpSRTEwVnpaemQwUkJXVVJXVWpC
+VVFrRlZkMEYzUlVJdmVrRk1RbWRPClZncElVVGhGUWtGTlEwRlJXWGRFVVZsS1MyOWFTV2gyWTA1
+QlVVVk1RbEZCUkdkblJVSkJSbVpSY1U5VVFUZFNkamRMSzJ4MVVUZHcKYm1Gek5FSlpkMGhGQ2ps
+SFJWQXZkVzlvZGpaTFQza3dWRWRSUm1KeVVsUnFSbTlNVms1Q09VSmFNWGx0VFVSYU1DOVVTWGRK
+VldNMwpkMmszWVRoME5XMUZjVmxJTVRVemQxY0tZVmR2YjJsVGFubE1UR2gxU1RSelRuSk9RMDkw
+YVhOa1FuRXljakpOUmxoME5tZ3diVUZSCldVOVFkamhTT0VzM0wyWm5VM2hIUm5GNmFIbE9iVzFX
+VEFveGNVSktiR1I0TXpSVGNIZHpWRUZNVVZaUVlqUm9SM2RLZWxwbWNqRlEKWTNCRlVYZzJlRTF1
+Vkd3NGVFVlhXa1V6VFhNNU9YVmhWWGhpVVhGSmQxSjFDa3huUVU5clRrTnRXVEp0T0RsV2FIcGhT
+RW94ZFZZNApOVUZrVFM5MFJDdFpjMjFzYm01cWREbE1Va05sYW1KQ2FYQnFTVWRxVDFoeVp6RktV
+Q3RzZUZZS2JYVk5OSFpJSzFBdmJXeHRlSE5RClVIb3daRFkxWWl0RlIyMUtXbkJ2VEd0UEwzUmtU
+azUyUTFsNmFrcHdWRVZYY0VWelR6Wk9UV2hMV1c4OUNpMHRMUzB0UlU1RUlFTkYKVWxSSlJrbERR
+VlJGTFMwdExTMEsKLS17Ym91bmRhcnl9LS0K
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithMissingBoundary.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithMissingBoundary.base64
new file mode 100644
index 0000000..3fa97d1
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithMissingBoundary.base64
@@ -0,0 +1,85 @@
+Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
+dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
+cHBsaWNhdGlvbi94LXBhc3Nwb2ludC1wcm9maWxlCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6
+IGJhc2U2NAoKUEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29n
+SUR4V1pYSkVWRVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVo
+YldVK1VHVnlVSEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0Fn
+UEZKVVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhi
+V1UrZFhKdU9uZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZ
+M0pwY0hScGIyNDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThM
+MUpVVUhKdmNHVnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSth
+VEF3TVR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRt
+RnRaVDVJYjIxbFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
+QWdJQ0FnSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklD
+QWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrWlJSRTQ4TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1
+MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdv
+Z0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2
+ClpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpo
+YkhWbFBnb2dJQ0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4
+VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVo
+YldVK0NpQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhi
+RzA4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHlj
+bVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9i
+MlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2Iy
+UmxUbUZ0ClpUNVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lD
+QWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BD
+OU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3
+dlZtRnNkV1UrCkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0
+S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldV
+K0NpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1G
+dFpUNUZRVkJVZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1Ur
+TWpFOEwxWmhiSFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0Fn
+SUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2
+WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0
+VmpJOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThM
+MDV2WkdVK0NpQWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJ
+Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhi
+V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
+a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
+VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
+bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
+OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4
+VG05awpaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFs
+UGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2
+WkdWT1lXMWxQa1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpROApMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZU
+bTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQ
+QzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94
+LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFD
+UlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtR
+VWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5U
+bFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpU
+azFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5V
+VEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMw
+TkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZY
+TldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01T
+dHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJV
+RkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJo
+U1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdK
+ck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtz
+M2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxh
+CmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVX
+TTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZH
+U1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFF
+YlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJk
+MFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldV
+akJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZ
+MDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZD
+amxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNY
+ZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1Ew
+OTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JX
+MVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVF
+MXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhw
+aFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpG
+S1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNS
+a1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmts
+RFFWUkZMUzB0TFMwSwo=
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithNonBase64Part.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithNonBase64Part.base64
new file mode 100644
index 0000000..975f8e5
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithNonBase64Part.base64
@@ -0,0 +1,85 @@
+Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
+dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTMyCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
+cHBsaWNhdGlvbi94LXBhc3Nwb2ludC1wcm9maWxlCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6
+IGJhc2U2NAoKUEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29n
+SUR4V1pYSkVWRVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVo
+YldVK1VHVnlVSEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0Fn
+UEZKVVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhi
+V1UrZFhKdU9uZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZ
+M0pwY0hScGIyNDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThM
+MUpVVUhKdmNHVnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSth
+VEF3TVR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRt
+RnRaVDVJYjIxbFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
+QWdJQ0FnSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklD
+QWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrWlJSRTQ4TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1
+MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdv
+Z0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2
+ClpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpo
+YkhWbFBnb2dJQ0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4
+VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVo
+YldVK0NpQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhi
+RzA4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHlj
+bVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9i
+MlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2Iy
+UmxUbUZ0ClpUNVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lD
+QWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BD
+OU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3
+dlZtRnNkV1UrCkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0
+S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldV
+K0NpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1G
+dFpUNUZRVkJVZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1Ur
+TWpFOEwxWmhiSFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0Fn
+SUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2
+WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0
+VmpJOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThM
+MDV2WkdVK0NpQWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJ
+Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhi
+V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
+a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
+eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
+QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
+VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
+bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
+OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4
+VG05awpaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFs
+UGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2
+WkdWT1lXMWxQa1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpROApMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZU
+bTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQ
+QzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94
+LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFD
+UlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtR
+VWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5U
+bFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpU
+azFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5V
+VEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMw
+TkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZY
+TldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01T
+dHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJV
+RkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJo
+U1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdK
+ck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtz
+M2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxh
+CmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVX
+TTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZH
+U1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFF
+YlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJk
+MFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldV
+akJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZ
+MDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZD
+amxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNY
+ZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1Ew
+OTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JX
+MVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVF
+MXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhw
+aFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpG
+S1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNS
+a1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmts
+RFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithoutProfile.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithoutProfile.base64
new file mode 100644
index 0000000..833c527
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithoutProfile.base64
@@ -0,0 +1,31 @@
+Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
+dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
+cHBsaWNhdGlvbi94LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNl
+NjQKCkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERa
+MEYzU1VKQlowbEtRVWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5R
+a2w0UlVSQlQwSm5UbFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVS
+VEZOUkVVeFYyaGpUazFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJF
+VjNaRVpSClZrRm5VVEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVU
+aEJUVWxKUWtOblMwTkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRI
+SlRTVnBWUzIxV1ZYTldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNI
+WndiamhEYzJOQ01TdHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0
+MWNFcG1jVEpVClJVRkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpk
+VmJYTTRORWwyUzJoU1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2ta
+Sk9WRmthR2dyVUdKck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0Nz
+eVEzWTJhakIzUWtzM2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxD
+S3pWU2RYQlNLMGxhCmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalEx
+TVFwNGNFZzFVRzVXTTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxF
+VmxJd1QwSkNXVVZHU1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRC
+TVZWa1NYZFJOMDFFYlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhh
+MFpFUVZNS1RWSkJkMFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBW
+elp6ZDBSQldVUldVakJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVV
+VmxLUzI5YVNXaDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpi
+bUZ6TkVKWmQwaEZDamxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1Y
+bHRUVVJhTUM5VVNYZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRH
+aDFTVFJ6VG5KT1EwOTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUz
+aEhSbkY2YUhsT2JXMVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pG
+UQpZM0JGVVhnMmVFMXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtO
+dFdUSnRPRGxXYUhwaFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhC
+cVNVZHFUMWh5WnpGS1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIx
+S1duQnZUR3RQTDNSa1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVF
+SUVORgpVbFJKUmtsRFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
diff --git a/wifi/tests/assets/hsr1/README.txt b/wifi/tests/assets/hsr1/README.txt
new file mode 100644
index 0000000..d1f8384
--- /dev/null
+++ b/wifi/tests/assets/hsr1/README.txt
@@ -0,0 +1,5 @@
+HSR1ProfileWithCACert.conf - unencoded installation file that contains a Passpoint profile and a CA Certificate
+HSR1ProfileWithCACert.base64 - base64 encoded of the data contained in HSR1ProfileWithCAWith.conf
+HSR1ProfileWithNonBase64Part.base64 - base64 encoded installation file that contains a part of non-base64 encoding type
+HSR1ProfileWithMissingBoundary.base64 - base64 encoded installation file with missing end-boundary in the MIME data
+HSR1ProfileWithInvalidContentType.base64 - base64 encoded installation file with that contains a MIME part with an invalid content type.
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/ConfigBuilderTest.java b/wifi/tests/src/android/net/wifi/hotspot2/ConfigBuilderTest.java
new file mode 100644
index 0000000..6095929
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/ConfigBuilderTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.wifi.hotspot2;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.FakeKeys;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSP;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.ConfigBuilder}.
+ */
+@SmallTest
+public class ConfigBuilderTest {
+    /**
+     * Hotspot 2.0 Release 1 installation file that contains a Passpoint profile and a
+     * CA (Certificate Authority) X.509 certificate {@link FakeKeys#CA_CERT0}.
+     */
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT =
+            "assets/hsr1/HSR1ProfileWithCACert.base64";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_UNENCODED_DATA =
+            "assets/hsr1/HSR1ProfileWithCACert.conf";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_INVALID_PART =
+            "assets/hsr1/HSR1ProfileWithNonBase64Part.base64";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_MISSING_BOUNDARY =
+            "assets/hsr1/HSR1ProfileWithMissingBoundary.base64";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_INVALID_CONTENT_TYPE =
+            "assets/hsr1/HSR1ProfileWithInvalidContentType.base64";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE =
+            "assets/hsr1/HSR1ProfileWithoutProfile.base64";
+
+    /**
+     * Read the content of the given resource file into a String.
+     *
+     * @param filename String name of the file
+     * @return String
+     * @throws IOException
+     */
+    private String loadResourceFile(String filename) throws IOException {
+        InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        StringBuilder builder = new StringBuilder();
+        String line;
+        while ((line = reader.readLine()) != null) {
+            builder.append(line).append("\n");
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * Generate a {@link PasspointConfiguration} that matches the configuration specified in the
+     * XML file {@link #PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT}.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private PasspointConfiguration generateConfigurationFromProfile() {
+        PasspointConfiguration config = new PasspointConfiguration();
+
+        // HomeSP configuration.
+        config.homeSp = new HomeSP();
+        config.homeSp.friendlyName = "Century House";
+        config.homeSp.fqdn = "mi6.co.uk";
+        config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
+
+        // Credential configuration.
+        config.credential = new Credential();
+        config.credential.realm = "shaken.stirred.com";
+        config.credential.userCredential = new Credential.UserCredential();
+        config.credential.userCredential.username = "james";
+        config.credential.userCredential.password = "Ym9uZDAwNw==";
+        config.credential.userCredential.eapType = 21;
+        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
+        config.credential.certCredential = new Credential.CertificateCredential();
+        config.credential.certCredential.certType = "x509v3";
+        config.credential.certCredential.certSha256FingerPrint = new byte[32];
+        Arrays.fill(config.credential.certCredential.certSha256FingerPrint, (byte)0x1f);
+        config.credential.simCredential = new Credential.SimCredential();
+        config.credential.simCredential.imsi = "imsi";
+        config.credential.simCredential.eapType = 24;
+        config.credential.caCertificate = FakeKeys.CA_CERT0;
+        return config;
+    }
+
+    /**
+     * Verify a valid installation file is parsed successfully with the matching contents.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFile() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT);
+        PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
+        PasspointConfiguration actualConfig =
+                ConfigBuilder.buildPasspointConfig(
+                        "application/x-wifi-config", configStr.getBytes());
+        assertTrue(actualConfig.equals(expectedConfig));
+    }
+
+    /**
+     * Verify that parsing an installation file with invalid MIME type will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithInvalidMimeType() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT);
+        assertNull(ConfigBuilder.buildPasspointConfig(
+                "application/wifi-config", configStr.getBytes()));
+    }
+
+    /**
+     * Verify that parsing an un-encoded installation file will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithUnencodedData() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_UNENCODED_DATA);
+        assertNull(ConfigBuilder.buildPasspointConfig(
+                "application/x-wifi-config", configStr.getBytes()));
+    }
+
+    /**
+     * Verify that parsing an installation file that contains a non-base64 part will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithInvalidPart() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_INVALID_PART);
+        assertNull(ConfigBuilder.buildPasspointConfig(
+                "application/x-wifi-config", configStr.getBytes()));
+    }
+
+    /**
+     * Verify that parsing an installation file that contains a missing boundary string will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithMissingBoundary() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_MISSING_BOUNDARY);
+        assertNull(ConfigBuilder.buildPasspointConfig(
+                "application/x-wifi-config", configStr.getBytes()));
+    }
+
+    /**
+     * Verify that parsing an installation file that contains a MIME part with an invalid content
+     * type will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithInvalidContentType() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_INVALID_CONTENT_TYPE);
+        assertNull(ConfigBuilder.buildPasspointConfig(
+                "application/x-wifi-config", configStr.getBytes()));
+    }
+
+    /**
+     * Verify that parsing an installation file that doesn't contain a Passpoint profile will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithoutPasspointProfile() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE);
+        assertNull(ConfigBuilder.buildPasspointConfig(
+                "application/x-wifi-config", configStr.getBytes()));
+    }
+}
\ No newline at end of file